diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bacabb7..68a7fbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -159,8 +159,11 @@ jobs: run: | registered=$(ctest --test-dir out/build-debug -N | sed -n 's/Total Tests: //p') discovered=$(./out/debug/bin/pjsontest --list-tests | wc -l | tr -d ' ') + benchmark_tools=$(ctest --test-dir out/build-debug -N \ + -R '^pjson\.benchmark_report_tools$' | sed -n 's/Total Tests: //p') test "$registered" -gt 0 - test "$registered" = "$discovered" + test "$benchmark_tools" = 1 + test "$registered" = "$((discovered + benchmark_tools))" msvc: name: Build and test (Windows / MSVC) @@ -186,9 +189,13 @@ jobs: Select-Object -First 1 if (-not $runner) { throw "pjsontest.exe was not produced" } $discovered = (& $runner.FullName --list-tests | Measure-Object -Line).Lines + $benchmarkTools = (ctest --test-dir out/build-msvc -C Debug -N ` + -R '^pjson\.benchmark_report_tools$' | + Select-String 'Total Tests: (\d+)').Matches.Groups[1].Value if ([int]$registered -le 0) { throw "No CTest cases were registered" } - if ([int]$registered -ne [int]$discovered) { - throw "CTest registered $registered cases, harness discovered $discovered" + if ([int]$benchmarkTools -ne 1) { throw "Benchmark tool test was not registered" } + if ([int]$registered -ne ([int]$discovered + [int]$benchmarkTools)) { + throw "CTest registered $registered cases, harness discovered $discovered plus $benchmarkTools tool test" } format: @@ -228,7 +235,17 @@ jobs: - name: Build and run baseline benchmarks shell: bash - run: ./build.sh --clean --bench --release-only --auto + env: + PJSON_BENCH_ENVIRONMENT: github-hosted-ubuntu-latest + run: ./build.sh --clean --bench --release-only --auto --bench-json out/benchmark-baseline.json + + - name: Retain baseline benchmark report + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: benchmark-baseline-${{ github.sha }} + path: out/benchmark-baseline.json + if-no-files-found: error + retention-days: 30 benchmark-compare: name: Benchmark comparison smoke test @@ -244,7 +261,17 @@ jobs: - name: Build and run comparison benchmarks shell: bash - run: ./build.sh --clean --bench-compare --release-only --auto + env: + PJSON_BENCH_ENVIRONMENT: github-hosted-ubuntu-latest + run: ./build.sh --clean --bench-compare --release-only --auto --bench-json out/benchmark-compare.json + + - name: Retain comparison benchmark report + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: benchmark-compare-${{ github.sha }} + path: out/benchmark-compare.json + if-no-files-found: error + retention-days: 30 conformance: name: Conformance corpus diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d50a1e..ada5de1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,219 @@ on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases follow ## [Unreleased] +## [2.0.0] - 2026-09-05 + +This release contains the complete audited change set since 1.0.0. It includes +source- and ABI-breaking API improvements, so it is a major version bump. + +### Added + +- Added an exact unsigned-integer representation (`jsonNumberUInt`): `uint64_t` + assignment/append/vectors, `isUInt()`, `isInteger()`, `tryGet(uint64_t&)`, the + `SaxHandler::onUInt(uint64_t)` event, and exact signed/unsigned/double + comparison and decimal serialization without converting through `double`. +- Added a structured `ParseError::Code` category (syntax, invalid encoding, + duplicate key, number range, depth/input/node limits, allocation failure, + stream error, callback error, invalid argument) alongside the existing + message and byte/line/column coordinates. +- Added non-allocating traversal: `forEachMember` and `forEachElement` + (const and mutable) that visit borrowed children without copying keys. +- Added construction and mutation primitives: `null()`, `object()`, `array()` + factories, `operator=(std::nullptr_t)`, `pushBack()` (copy and move), + `insertOrAssign()`, `reserve()`, checked `at()` for keys and indices, and + `contains()`. +- Added `SerializeOptions::NonFinitePolicy` (`RejectNonFinite` default, + `NonFiniteToNull`, `NonFiniteToString`) governing NaN/infinity output. +- Added `ParseOptions::NumberPolicy` (`RejectUnrepresentableNumbers` default, + `AllowLossyNumbers`) governing numbers outside the exact 64-bit and binary64 + ranges. +- Added JSON Schema Draft 2020-12 applicator keywords to the validator: + `if`/`then`/`else`, `prefixItems`, `contains`/`minContains`/`maxContains`, and + `dependentSchemas`, plus a strict fail-closed subset mode. + +### Changed + +- **BREAKING (behavior):** object members now use private, process-seeded + hash-table storage. Lookup and insertion are average constant time; + `keys()`, `forEachMember()`, and serialization use unspecified native storage + order. Object equality remains independent of storage order. +- **BREAKING (API):** removed `SerializeOptions::KeyOrder` and `keyOrder`; JSON + objects are unordered by specification, so the serializer no longer pays to + impose an order. +- **BREAKING (ABI):** established `PJSON_ABI_VERSION` and shared-library + `SOVERSION` 2 for the new opaque object layouts and public option structures. + +- Restored the primary builder syntax for native integral and floating-point + values and common vectors. Numeric literals can again be assigned and + appended without casts, while preserving signed versus unsigned storage. + +- **BREAKING (API):** parsing is now provided by the standalone + `ByteDance::pJsonParser` class in ``. Parser `Options`, + `Error`, and `SaxHandler` are nested under that class; `pjson` no longer + declares parsing members and has no dependency on the parser. Configure the + allocator and options when constructing a reusable parser, then call + `parse`, `parseStream`, `parseSax`, or `parseSaxStream`. DOM parse results + remain ordinary `pjson` values. +- Split the implementation into focused DOM, parser, serializer, JSON Pointer, + JSON Patch/Merge Patch, and existing schema translation units while retaining + one `pjson::pjson` library target. +- **BREAKING (ABI):** `pjson` is now a fixed two-pointer handle containing its + allocator identity and an opaque implementation pointer. Null values share an + allocation-free private sentinel; non-null representation changes no longer + alter `sizeof(pjson)`. `pJsonParser` is likewise a one-pointer PImpl. Explicit + symbol visibility replaces automatic Windows export, and ABI generation 2 is + declared by `PJSON_ABI_VERSION` and shared-library `SOVERSION`. +- Added `Allocator::ImplementationAllocation` for non-null `pjson` private-state + allocation. Custom allocators must accept the appended allocation kind. +- Default construction is now explicitly `noexcept`, matching its + allocation-free null-sentinel implementation and the 2.0 ABI contract. +- Added non-vivifying `findIndex(size_t)` lookup so large non-negative indexes + never narrow through the signed `find(int)` API. +- Shared-library consumers now receive `PJSON_SHARED` through both exported + CMake targets, pkg-config, and Conan metadata, and public declarations retain + default visibility even when the consumer compiles with hidden visibility. +- SAX parsing now reports `AllocationFailure` rather than `CallbackError` when + allocation fails in parser state or a callback, and reports exception-enabled + input-stream failures as `StreamError`. +- **BREAKING (behavior):** mutable array subscripting no longer clamps a + negative index before the beginning to element zero. It now throws + `std::out_of_range` without mutation; valid negative indexes still count from + the end, and a new `operator[](size_t)` serves non-negative builder access. +- Added non-throwing `toString(out, SerializeError&, options)` and + `write(stream, SerializeError&, options)` overloads with stable error codes. + String output is transactional; logical stream failures are preflighted. +- Expanded `pJsonSchemaValidator::Error` with stable codes, separate instance + and schema locations, keyword names, optional combinator causes, and a + first-error validation mode. +- Strict schema mode now rejects malformed values for every supported keyword + during validator construction, including invalid and unsafe regular + expressions; permissive mode retains its ignore-malformed behavior. +- Added dedicated serialization, JSON Pointer, and JSON Merge Patch fuzzers; + local and OSS-Fuzz smoke inputs now reach 64 KiB and include checked-in + inputs larger than 4 KiB. +- Finite doubles now use pinned Ryu shortest-round-trip conversion while + preserving pjson's fixed/scientific spelling policy; default parsing rejects + a nonzero decimal token that underflows to zero, with explicit lossy opt-in. +- Split stateless JSON Schema value/numeric/regex, format, and URI helpers into + focused private translation units while retaining one public schema API. +- Unified DOM and SAX numeric-token classification/conversion behind one + internal routine, including exact integer, overflow, underflow, and lossy + policy decisions. +- Unified DOM and SAX JSON-number grammar scanning through cursor adapters and + extracted per-resource schema dialect/vocabulary policy into a focused private + translation unit. +- CTest cases are now discovered from the executable's compiled registry after + linking instead of scraping `TEST(...)` tokens from source text. +- **BREAKING (API):** JSON Schema validation is no longer a member of `pjson`. + The `pjson::validate()` overloads and the nested `pjson::SchemaError` / + `pjson::SchemaOptions` types are removed. Validation now lives in a standalone + `ByteDance::pJsonSchemaValidator` class declared in the new `` + header (built from `pjson_schema.cpp`). Compile a schema once — + `pJsonSchemaValidator v(schema[, pJsonSchemaValidator::Options()]);` — then + call `v.validate(instance[, errors])`. The former `SchemaError` and + `SchemaOptions` are now `pJsonSchemaValidator::Error` and + `pJsonSchemaValidator::Options`. The validator is a pure consumer of pjson's + public API and touches no library internals, so the core DOM no longer links + the schema/regex machinery. A new public `pjson::tryCompareNumber()` exposes + the exact cross-kind numeric ordering the validator needs. +- Added an explicit schema-compilation contract. pjson now names its supported + subset dialect/vocabulary, honors root `$schema`, supports a configurable + default dialect, rejects unsupported dialects and required vocabularies, and + exposes `isSchemaValid()`, `schemaErrors()`, and `dialect()`. Schema errors are + categorized as `SchemaCompilation` versus `InstanceValidation`. +- Added a pinned, manifest-driven Draft 2020-12 conformance gate. It now + explicitly accounts for all 80 pinned files, runs 1,773 applicable cases + across 437 groups, and records every selected-group and whole-file deferral. + A bidirectional manifest check prevents corpus additions from disappearing. +- Added `$id` resource bases, `$anchor`, `$dynamicAnchor`, `$ref`, `$dynamicRef`, + and an explicit function-pointer resolver. pjson performs no implicit I/O; + resolution is bounded by reference, document, byte, work, and depth limits. + `Options::modernSubset()` enables modern `$ref` sibling semantics and the + Draft 2020-12 annotation-only `format` default; the normal defaults retain + prior Draft 7-compatible behavior. +- Added Draft 2020-12 evaluation-annotation propagation and enforcement for + `unevaluatedItems` and `unevaluatedProperties` across references, dynamic + references, combinators, conditionals, `contains`, and container applicators. + The official gate now executes 1,773 cases across 437 groups. +- Added opt-in `Options::draft2020()` with bundled official meta-schemas, schema + compilation against the selected meta-schema, and per-resource vocabulary + activation. Custom meta-schemas use the explicit resolver and existing + resolution budgets. The optional complete format-assertion vocabulary remains + unsupported and fails closed when required. +- Published one versioned pjson 2.0 behavioral contract consolidating ownership, + parsing, numeric, mutation, invalidation, allocator, thread-safety, error, and + standards guarantees. +- Expanded benchmarks with wide-object, large-array, string-heavy, escape-heavy, + integer-heavy, and floating-heavy workloads. Added versioned JSON reports with + source/build/environment/methodology metadata and advisory CI artifacts. +- Aligned SAX null-span diagnostics with DOM parsing by reporting + `pJsonParser::Error::InvalidArgument`. +- Replaced schema `std::regex` use with private, pinned SRELL 2026.06, adding + Unicode ECMAScript property/non-BMP support, bounded backend work, and the + asserted `regex` format without changing the public dependency surface. +- Replaced repeated locale-stream double formatting with pinned Ryu shortest + conversion while preserving pjson's fixed/scientific spelling policy. On the + controlled local shape benchmark, floating-heavy serialization improved by + approximately 88%, with all bit-round-trip tests unchanged. +- Added an environment-checking benchmark report comparator and separate + artifact-size metadata tool. Threshold failures remain opt-in for controlled + runners. +- Moved pJsonSchemaValidator storage behind a private implementation pointer; + schemas are copied to the default allocator, removing dependence on the + caller's schema allocator lifetime. +- Hardened move assignment, `pushBack`, `insertOrAssign`, and `swap` for + ancestor/descendant aliasing. Overlapping swaps are rejected by `canSwap()`, + and insertion cannot create an ownership cycle or consume a source before a + potentially throwing container insertion commits. +- Count custom meta-schema documents and bytes once when the same resolved URI + is subsequently compiled as a schema resource, and normalize relative URI + dot segments before invoking an external resolver. +- Hardened benchmark report comparison to reject missing, extra, duplicate, or + invalid result rows instead of silently comparing only their intersection. + +- **BREAKING (API):** `parse()` and `parseStream()` now return a `pjson` value + instead of `pjson::unique_ptr`; the `pjson::unique_ptr` typedef and + `ValueDeleter` are removed. Detect failure with a `ParseError` out-param + (`err.ok`) rather than a null check — the terse overloads return a JSON `null` + value on failure. Move the returned value to transfer ownership. This removes + the only smart pointer from the public API. +- **BREAKING (ABI):** `pjson::jsonType` gained `jsonNumberUInt` and the value + storage grew a `uint64_t` member. Existing enumerator values are unchanged, but + the class layout changed; dependents must be rebuilt against this header. +- **BREAKING (behavior):** integer tokens above `INT64_MAX` now parse to the + exact unsigned representation (up to `UINT64_MAX`) instead of a lossy `double`. + Tokens outside `[INT64_MIN, UINT64_MAX]`, and non-finite floating values, are + now rejected by default; opt in with `ParseOptions::AllowLossyNumbers`. +- **BREAKING (behavior):** serializing a stored non-finite `double` now fails + with a structured error by default instead of silently emitting `null`. Use + `SerializeOptions::NonFiniteToNull` to keep the old behavior. +- Object key access, lookup, `hasKey`, `erase`, and keyed `tryGet` are now + length-aware for `std::string`, preserving names containing embedded U+0000; + `const char*` overloads keep documented NUL-terminated behavior. +- The parser now clamps a configured `maxDepth` to a stack-safe hard ceiling, so + even an `INT_MAX` request cannot overflow the native stack. + +### Fixed + +- Fixed a heap-use-after-free in move assignment when the source aliased an + ancestor or descendant of the destination; overlapping `swap()` is now a safe + no-op. +- Rejected duplicate object keys are now reported immediately at the duplicate + key's own offset, before its value subtree is parsed or allocated. + +### Removed + +- Removed the public `pjson::unique_ptr` typedef and `pjson::ValueDeleter`; + parsing returns a `pjson` value. A `new pjson()` root is still freed by an + ordinary `std::unique_ptr` or by normal scope. + +### Security + +- Made configurable nesting limits memory-safe: excessive depth returns a + structured resource-limit error rather than exhausting the stack, across the + string, byte-span, DOM-stream, buffered-SAX, and incremental-SAX front ends. + + ## [1.0.0] - 2026-08-31 ### Added @@ -119,7 +332,8 @@ on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases follow - Initial pjson source release. -[Unreleased]: https://github.com/Pico-Developer/pjson/compare/1.0.0...HEAD +[Unreleased]: https://github.com/Pico-Developer/pjson/compare/2.0.0...HEAD +[2.0.0]: https://github.com/Pico-Developer/pjson/compare/1.0.0...2.0.0 [1.0.0]: https://github.com/Pico-Developer/pjson/compare/release-0.0.3...1.0.0 [0.0.3]: https://github.com/Pico-Developer/pjson/compare/release-0.0.2...release-0.0.3 [0.0.2]: https://github.com/Pico-Developer/pjson/compare/release-0.0.1...release-0.0.2 diff --git a/CMakeLists.txt b/CMakeLists.txt index f5d7618..6669232 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required (VERSION 3.21) -project(pjson VERSION 1.0.0 DESCRIPTION "Praveen's JSON library for C++" LANGUAGES CXX) +project(pjson VERSION 2.0.0 DESCRIPTION "Praveen's JSON library for C++" LANGUAGES CXX) # Keep package/runtime version authorities synchronized at configure time. The # release process updates them together; a mismatch is a hard configuration @@ -13,6 +13,15 @@ file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/pjsonlib/include/pjson.h" string(REGEX REPLACE "^#define PJSON_VERSION \"([^\"]+)\"$" "\\1" PJSON_HEADER_VERSION "${PJSON_HEADER_VERSION_LINE}") set(PJSON_VERSION_SOURCES PJSON_HEADER_VERSION) +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/pjsonlib/include/pjson.h" + PJSON_ABI_VERSION_LINE REGEX "^#define PJSON_ABI_VERSION [0-9]+$") +string(REGEX REPLACE "^#define PJSON_ABI_VERSION ([0-9]+)$" "\\1" + PJSON_ABI_VERSION "${PJSON_ABI_VERSION_LINE}") +if(NOT "${PJSON_ABI_VERSION}" STREQUAL "${PROJECT_VERSION_MAJOR}") + message(FATAL_ERROR + "ABI version mismatch: PJSON_ABI_VERSION=${PJSON_ABI_VERSION}, " + "project major=${PROJECT_VERSION_MAJOR}") +endif() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/conanfile.py") file(READ "${CMAKE_CURRENT_SOURCE_DIR}/conanfile.py" PJSON_CONAN_RECIPE) string(REGEX MATCH "version = \"([^\"]+)\"" PJSON_CONAN_VERSION_MATCH diff --git a/LICENSES/AFL-3.0.txt b/LICENSES/AFL-3.0.txt new file mode 100644 index 0000000..e1b7792 --- /dev/null +++ b/LICENSES/AFL-3.0.txt @@ -0,0 +1,43 @@ +Academic Free License (“AFL”) v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + + Licensed under the Academic Free License version 3.0 + +1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + a) to reproduce the Original Work in copies, either alone or as part of a collective work; + b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + c) to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor’s reserved rights and remedies, in this Academic Free License; + d) to perform the Original Work publicly; and + e) to display the Original Work publicly. + +2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + +3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor’s trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + +5) External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + +6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + +7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + +8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + +9) Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including “fair use” or “fair dealing”). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + +10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + +11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + +12) Attorneys’ Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + +13) Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + +14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For 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. + +15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + +16) Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt new file mode 100644 index 0000000..5f662b3 --- /dev/null +++ b/LICENSES/BSD-2-Clause.txt @@ -0,0 +1,9 @@ +Copyright (c) + +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. + +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. diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt new file mode 100644 index 0000000..ea890af --- /dev/null +++ b/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,11 @@ +Copyright (c) . + +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. diff --git a/README.md b/README.md index 35bb6aa..0b2462e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ number, string, array, or object) and provides an ergonomic `obj["key"][i] = value` style API. - Licensed under Apache-2.0; -- Current source version: **1.0.0** (`pjson::getVersion()` / the +- Current source version: **2.0.0** (`pjson::getVersion()` / the `PJSON_VERSION` macro). --- @@ -63,7 +63,9 @@ Resulting layout: ```text out/ - include/pjson.h public header + include/pjson.h DOM public header + include/pjson_parser.h parser public header + include/pjson_schema.h schema-validator public header release/lib/libpjson.a Release library release/bin/pjsontest Release test runner release/bin/pjsonbench Release benchmark runner @@ -78,11 +80,13 @@ including both pinned test corpora and benchmark dependencies under ### Option 1 : Direct source integration -Add the canonical library sources directly to your project and put -`pjsonlib/include` on its include path. There are no third-party dependencies: +Direct source integration must include every translation unit used by the +selected features and the private Ryu include path for serialization. The +single CMake target below is preferred for the full library. Public headers are: -- Public header: [`pjsonlib/include/pjson.h`](pjsonlib/include/pjson.h) -- Implementation: [`pjsonlib/src/pjson.cpp`](pjsonlib/src/pjson.cpp) +- [`pjson.h`](pjsonlib/include/pjson.h) for the DOM +- [`pjson_parser.h`](pjsonlib/include/pjson_parser.h) for parsing +- [`pjson_schema.h`](pjsonlib/include/pjson_schema.h) for schema validation ### Option 2 : Build with CMake directly @@ -112,7 +116,7 @@ cmake --install build --config Release Consumers use the same target after pointing CMake at that prefix: ```cmake -find_package(pjson 1.0 CONFIG REQUIRED) +find_package(pjson 2.0 CONFIG REQUIRED) target_link_libraries(myapp PRIVATE pjson::pjson) ``` @@ -136,6 +140,7 @@ For a standalone first program and its exact compile command, see the ```cpp #include "pjson.h" +#include "pjson_parser.h" #include #include using namespace ByteDance; @@ -155,12 +160,13 @@ int main() { pjson::SerializeOptions::prettyPrinted(); std::cout << person.toString(pretty) << "\n"; - // Every DOM parse returns an owning pjson::unique_ptr (empty on error). - pjson::unique_ptr parsed = pjson::parse(person.toString()); - if (parsed) { + // Every DOM parse returns a pjson value; pass a pJsonParser::Error to detect failure. + pJsonParser::Error error; + pjson parsed = pJsonParser().parse(person.toString(), error); + if (error.ok) { std::string name; int64_t age = 0; - if (parsed->tryGet("name", name) && parsed->tryGet("age", age)) { + if (parsed.tryGet("name", name) && parsed.tryGet("age", age)) { std::cout << "name = " << name << "\n"; std::cout << "age = " << age << "\n"; } @@ -217,10 +223,11 @@ list += int64_t(3); // [1,"two",3] list += int64_t(4); // [1,"two",3,4] ``` -The `=` and `+=` operators accept strings, `bool`, `int64_t`, and `double`, as -well as vectors of `std::string`, `bool`, `int64_t`, or `double`. Convenience -overloads for `int`, `float`, and vectors of those types or C strings are not -part of the API. +The `=` and `+=` operators accept strings, `bool`, all standard non-character integer types, +and `float`, `double`, or `long double`, as well as vectors of the supported +non-character numeric types, `bool`, or `std::string`. Concrete overloads keep +ordinary expressions unambiguous without putting templates in the public +header. Values are stored as signed or unsigned 64-bit integers, or as `double`. The document built above serializes to: ```json @@ -254,7 +261,7 @@ The document built above serializes to: } ``` The skipped array position `[2]` is auto-filled with `null` by -`doubles[3] = 4.4`. Object keys come out in sorted order. +`doubles[3] = 4.4`. Object member order is unspecified. --- @@ -267,14 +274,13 @@ pjson::SerializeOptions prettyOptions = std::string pretty = person.toString(prettyOptions); ``` -Use `SerializeOptions` when formatting must be explicit or reproducible: +Use `SerializeOptions` when formatting choices must be explicit: ```cpp pjson::SerializeOptions output = pjson::SerializeOptions::prettyPrinted(); output.indentWidth = 4; output.indentCharacter = ' '; // only space or tab; other values fall back to space output.escapeNonAscii = true; -output.keyOrder = pjson::SerializeOptions::DescendingKeys; output.maxOutputBytes = size_t(64) * 1024 * 1024; std::string text = person.toString(output); @@ -282,15 +288,15 @@ person.write(std::cout, output); ``` Defaults are compact output, two-space indentation when `pretty` is enabled, -raw UTF-8, ascending bytewise key order, and a 64 MiB output limit. Set +raw UTF-8, and a 64 MiB output limit. Set `maxOutputBytes = 0` only when explicitly requesting unlimited output. Use -`SerializeOptions::prettyPrinted()` for two-space pretty output. Because objects -use `std::map`, insertion order is not retained; serialization can select -ascending or descending order. +`SerializeOptions::prettyPrinted()` for two-space pretty output. Objects use +private process-seeded hash storage, so insertion, traversal, `keys()`, and +serialization order are unspecified. For the document built in [Quick start](#quick-start): -**Compact** — note that object keys are emitted in sorted order: +**Compact** — one possible member order is: ```json {"active":true,"address":{"city":"London"},"age":36,"name":"Ada","scores":[90,82,77]} ``` @@ -326,15 +332,17 @@ stream/I/O failure can leave a partial prefix. ## Parsing ### `parse()` — the recommended API -Every DOM-parsing overload returns an owning `pjson::unique_ptr` (empty on JSON -or DOM-allocation failure), so there is no manual `delete`. +Every DOM-parsing overload returns a `pjson` **by value** — no smart pointer, no +manual `delete`. The value owns its subtree and frees it on destruction. The +terse overloads return a JSON `null` value on failure; pass a `pJsonParser::Error` when +you need to tell failure apart from a successfully parsed literal `null`. ```cpp -pjson::unique_ptr p = - pjson::parse(R"({ "a": 1, "b": [true, null, "x"] })"); -if (p) { +pJsonParser::Error err; +pjson p = pJsonParser().parse(R"({ "a": 1, "b": [true, null, "x"] })", err); +if (err.ok) { int64_t a = 0; - if (p->tryGet("a", a)) + if (p.tryGet("a", a)) std::cout << a << "\n"; // 1 } // freed automatically ``` @@ -342,32 +350,37 @@ if (p) { A `(const char*, size_t)` overload handles buffers that are not NUL-terminated or that contain embedded NUL bytes: ```cpp -auto p = pjson::parse(buffer, length); +pjson p = pJsonParser().parse(buffer, length, err); ``` -**Parse options** — `parse()` accepts an optional `ParseOptions`: +**Parse options** — `parse()` accepts an optional `pJsonParser::Options`: ```cpp -pjson::ParseOptions opt; -opt.maxDepth = 64; // reject nesting deeper than this (default 512) +pJsonParser::Options opt; +opt.maxDepth = 64; // reject nesting deeper than this (default 512, hard cap 1024) opt.maxNodes = 100000; // cap materialized values (default 1,000,000) opt.maxInputBytes = 8 * 1024 * 1024; // cap input (default 64 MiB) -opt.duplicateKeys = pjson::ParseOptions::RejectDuplicateKeys; // default -auto p = pjson::parse(text, opt); +opt.duplicateKeys = pJsonParser::Options::RejectDuplicateKeys; // default +opt.numberPolicy = pJsonParser::Options::RejectUnrepresentableNumbers; // default +pjson p = pJsonParser(opt).parse(text); ``` -**Error reporting** — pass a `ParseError` to learn *why*/*where* parsing failed +**Error reporting** — pass a `pJsonParser::Error` to learn *why*/*where* parsing failed (no exceptions): ```cpp -pjson::ParseError err; -auto p = pjson::parse("[1, 2, ]", err); -if (!p) { +pJsonParser::Error err; +pjson p = pJsonParser().parse("[1, 2, ]", err); +if (!err.ok) { std::cerr << "parse failed at " << err.line << ':' << err.column - << " (byte " << err.offset << "): " << err.message << "\n"; + << " (byte " << err.offset << "): " << err.message + << " [code " << err.code << "]\n"; } ``` -The parser resets the supplied `ParseError` at the start of every call. Success -leaves `ok == true`, offset `0`, line `1`, column `1`, and an empty message; -failure sets `ok == false` and records the first failure. +The parser resets the supplied `pJsonParser::Error` at the start of every call. Success +leaves `ok == true`, `code == None`, offset `0`, line `1`, column `1`, and an +empty message; failure sets `ok == false`, a stable `code`, and records the +first failure. Because a failed parse returns a `null` value, prefer the +`pJsonParser::Error` overload whenever the input might legitimately be the literal +`null`. ### Strict parsing and duplicate keys The parser always enforces RFC 8259. It rejects: @@ -380,31 +393,33 @@ The parser always enforces RFC 8259. It rejects: Duplicate object keys are rejected by default. Set `duplicateKeys` to `KeepFirstDuplicate` or `KeepLastDuplicate` only when interoperability requires -it. Resource budgets and duplicate-key handling are the only parse options; -neither relaxes the JSON grammar or UTF-8 validation. +it. Resource budgets, duplicate-key handling, and the number policy are the only +parse options; none relaxes the JSON grammar or UTF-8 validation. ### Reading from a stream ```cpp std::ifstream file("data.json"); -pjson::unique_ptr doc = pjson::parseStream(file); -if (doc) { /* ... */ } +pJsonParser::Error err; +pjson doc = pJsonParser().parseStream(file, err); +if (err.ok) { /* ... */ } ``` `parseStream()` builds a normal DOM. For very large documents, derive from -`pjson::SaxHandler` and use `parseSaxStream()` to receive values incrementally +`pJsonParser::SaxHandler` and use `parseSaxStream()` to receive values incrementally without buffering the full file or allocating a DOM: ```cpp -struct Counter : pjson::SaxHandler { +struct Counter : pJsonParser::SaxHandler { size_t numbers = 0; bool onInt(int64_t) override { ++numbers; return true; } + bool onUInt(uint64_t) override { ++numbers; return true; } bool onDouble(double) override { ++numbers; return true; } }; Counter counter; -pjson::ParseError err; +pJsonParser::Error err; std::ifstream input("huge.json", std::ios::binary); -if (!pjson::parseSaxStream(input, counter, err)) { +if (!pJsonParser().parseSaxStream(input, counter, err)) { std::cerr << err.line << ':' << err.column << ": " << err.message << '\n'; } ``` @@ -439,9 +454,8 @@ Given this document: ``` ```cpp -auto p = pjson::parse( +pjson j = pJsonParser().parse( R"({ "name": "Ada", "age": 36, "ratio": 0.5, "active": true })"); -const pjson& j = *p; std::string name; int64_t age = 0; @@ -457,7 +471,7 @@ pjson::jsonType t = nameNode ? nameNode->getType() : pjson::jsonNull; ``` The type tags are: `jsonNull`, `jsonString`, `jsonNumberInt`, -`jsonNumberDouble`, `jsonBoolean`, `jsonArray`, `jsonObject`. +`jsonNumberUInt`, `jsonNumberDouble`, `jsonBoolean`, `jsonArray`, `jsonObject`. --- @@ -488,13 +502,13 @@ Given this document (shown formatted so you can see exactly what is being read): ``` ```cpp -auto p = pjson::parse( +pjson j = pJsonParser().parse( R"({ "scores": [90, 82, 77], "tags": ["a", "b", "c"], "friends": [ {"name":"Bob"}, {"name":"Cid"} ] })"); -pjson& j = *p; ``` -Read arrays through `size()` and `find(index)`. These operations do not resize or +Read arrays through `size()` and `findIndex(size_t)`. Use `find(int)` only when +you need negative indexes from the end. These operations do not resize or otherwise modify the array: ```cpp @@ -502,7 +516,7 @@ if (const pjson* scores = j.find("scores")) { std::cout << "count = " << scores->size() << "\n"; // 3 for (size_t i = 0; i < scores->size(); ++i) { int64_t value = 0; - const pjson* element = scores->find(static_cast(i)); + const pjson* element = scores->findIndex(i); if (element && element->tryGet(value)) std::cout << value << " "; // 90 82 77 } @@ -513,7 +527,7 @@ if (const pjson* scores = j.find("scores")) { ```cpp if (const pjson* friends = j.find("friends")) { for (size_t i = 0; i < friends->size(); ++i) { - const pjson* entry = friends->find(static_cast(i)); + const pjson* entry = friends->findIndex(i); std::string name; if (entry && entry->tryGet("name", name)) std::cout << name << " "; // Bob Cid @@ -536,11 +550,11 @@ when you only want some elements. Given: ``` ```cpp // Sum only the integer elements -> 1 + 3 + 4 = 8 -auto mixed = pjson::parse(R"({ "mixed": [1, "two", 3, true, 4] })"); -if (const pjson* node = mixed->find("mixed")) { +pjson mixed = pJsonParser().parse(R"({ "mixed": [1, "two", 3, true, 4] })"); +if (const pjson* node = mixed.find("mixed")) { for (size_t i = 0; i < node->size(); ++i) { int64_t value = 0; - const pjson* element = node->find(static_cast(i)); + const pjson* element = node->findIndex(i); if (element && element->tryGet(value)) std::cout << value << " "; } @@ -551,8 +565,8 @@ if (const pjson* node = mixed->find("mixed")) { ## Reading objects / maps -Use `keys()` to obtain object keys in sorted order, then `find(key)` to read each -member without creating it. +Use `keys()` to obtain object keys, then `find(key)` to read each member without +creating it. The key order is unspecified. Given this document: ```json @@ -565,11 +579,10 @@ Given this document: } ``` ```cpp -auto p = pjson::parse( +pjson j = pJsonParser().parse( R"({ "name": "Ada", "address": { "city": "London", "zip": "N1" } })"); -pjson& j = *p; -// Iterate top-level keys in sorted order -> "address", then "name" +// Iterate top-level keys in unspecified order. for (const std::string& key : j.keys()) { const pjson* value = j.find(key); if (value) { @@ -602,6 +615,9 @@ This is the one sharp edge worth understanding. **creates** it, and access can change a node's type. That makes building concise, but `operator[]` is not a safe read. A single index access that would create more than 1,000,000 children throws `std::length_error` before mutation: +Valid negative `int` indexes address existing elements from the end; a negative +index before the beginning throws `std::out_of_range` without mutation. A +`size_t` overload is available for ordinary non-negative builder indexes. ```cpp pjson building; @@ -627,9 +643,8 @@ For **non-mutating reads**, use these instead. Given: ``` ```cpp -auto doc = pjson::parse( +pjson j = pJsonParser().parse( R"({ "age": 36, "name": "Ada", "scores": [90, 82, 77] })"); -const pjson& j = *doc; // hasKey / find never create anything if (j.hasKey("scores")) { /* ... */ } @@ -686,18 +701,18 @@ same as building one. Starting from: } ``` ```cpp -auto p = pjson::parse( +pjson p = pJsonParser().parse( R"({ "user": { "scores": [10, 20, 30] }, "status": "active" })"); // Change values in place -(*p)["user"]["scores"][0] = int64_t(99); // change a value -(*p)["user"]["scores"][1] = "twenty"; // change an element's type +p["user"]["scores"][0] = int64_t(99); // change a value +p["user"]["scores"][1] = "twenty"; // change an element's type // Replace a whole node (the "status" string becomes an array here) -(*p)["status"][0] = int64_t(1); -(*p)["status"][1] = int64_t(2); +p["status"][0] = int64_t(1); +p["status"][1] = int64_t(2); -std::cout << p->toString(pjson::SerializeOptions::prettyPrinted()); +std::cout << p.toString(pjson::SerializeOptions::prettyPrinted()); ``` produces: @@ -724,8 +739,7 @@ produces: **Type predicates and container queries** answer common questions directly: ```cpp -auto p = pjson::parse(R"({ "scores": [90, 82, 77] })"); -pjson& j = *p; +pjson j = pJsonParser().parse(R"({ "scores": [90, 82, 77] })"); j.isObject(); // true const pjson* scores = j.find("scores"); @@ -734,9 +748,9 @@ scores ? scores->size() : 0; // 3 scores && !scores->empty(); // true j.getType(); // pjson::jsonObject ``` -Predicates: `isNull`, `isString`, `isNumber`, `isInt`, `isDouble`, `isBool`, -`isArray`, `isObject`. `size()` returns the element count for arrays/objects -(0 for scalars); `empty()` is `size() == 0`. +Predicates: `isNull`, `isString`, `isNumber`, `isInt`, `isUInt`, `isInteger`, +`isDouble`, `isBool`, `isArray`, `isObject`. `size()` returns the element count +for arrays/objects (0 for scalars); `empty()` is `size() == 0`. **Read with an application default** — initialize the destination, then replace it only if `tryGet` succeeds: @@ -748,7 +762,7 @@ std::string name = "anon"; j.tryGet("name", name); // name remains "anon" on failure ``` -**Iterate object keys** (sorted): +**Iterate object keys** (unspecified order): ```cpp for (const std::string& key : j.keys()) { const pjson* value = j.find(key); @@ -771,9 +785,9 @@ j.clear(); // empty the object (stays an object) **Compare** — deep, structural equality. Numbers compare across integer/double (`1 == 1.0`), objects compare regardless of key order, arrays compare in order: ```cpp -auto a = pjson::parse(R"({"x":1,"y":[2,3]})"); -auto b = pjson::parse(R"({"y":[2,3],"x":1.0})"); -bool same = (*a == *b); // true +auto a = pJsonParser().parse(R"({"x":1,"y":[2,3]})"); +auto b = pJsonParser().parse(R"({"y":[2,3],"x":1.0})"); +bool same = (a == b); // true ``` --- @@ -803,19 +817,19 @@ successful RFC 6902 `remove` at the empty root path leaves the target as JSON `null`: ```cpp -auto patch = pjson::parse(R"([ +pjson patch = pJsonParser().parse(R"([ {"op":"replace","path":"/status","value":"ready"}, {"op":"add","path":"/tags/-","value":"new"} ])"); pjson::PatchError patchError; pjson::PatchOptions patchLimits; -if (!document.applyPatch(*patch, patchError, patchLimits)) { +if (!document.applyPatch(patch, patchError, patchLimits)) { std::cerr << patchError.opIndex << ": " << patchError.message << '\n'; } -auto merge = pjson::parse(R"({"obsolete":null,"enabled":true})"); -document.applyMergePatch(*merge, patchError, patchLimits); +pjson merge = pJsonParser().parse(R"({"obsolete":null,"enabled":true})"); +document.applyMergePatch(merge, patchError, patchLimits); ``` `PatchOptions` defaults to 10,000 operations, 1,000,000 cloned nodes, @@ -833,33 +847,94 @@ JSON null. ## Numbers -- Integers are stored and assigned as **64-bit** (`int64_t`); read them with - `tryGet(int64_t&)`. +- Signed integers are stored and assigned as **64-bit** (`int64_t`); read them + with `tryGet(int64_t&)`. +- Unsigned integers above `INT64_MAX` are stored as **64-bit** (`uint64_t`) in a + distinct `jsonNumberUInt` kind, so the full `uint64_t` range round-trips + exactly. Read them with `tryGet(uint64_t&)`, test with `isUInt()`, and test + either integer kind with `isInteger()`. An explicit `uint64_t` assignment keeps + unsigned identity even for small values. A signed read of an unsigned value + succeeds only when it fits in `int64_t`; an unsigned read of a signed value + succeeds only when it is non-negative. - Non-integers are stored and assigned as **`double`**; read them with - `tryGet(double&)`. A stored integer may widen to `double`; other conversions - are rejected. -- There is no unsigned storage kind; range-check other numeric C++ types before - explicitly converting them to `int64_t` or `double`. -- Double serialization is locale-independent and uses 15–17 significant digits - as needed for stable parse/serialize round-tripping. Integral-looking doubles + `tryGet(double&)`. Any stored integer may widen to `double`; other conversions + are rejected. Cross-kind comparison is exact (`1 == 1u == 1.0`), including + above 2^53. +- Integer tokens outside `[INT64_MIN, UINT64_MAX]`, and floating tokens outside + finite `double` range, are **rejected by default** + (`pJsonParser::Options::RejectUnrepresentableNumbers`). Set + `pJsonParser::Options::AllowLossyNumbers` to store the nearest finite `double` instead. +- Nonzero floating tokens that round all the way to zero are also rejected by + default and require `AllowLossyNumbers`. Other finite decimal tokens are + converted by the platform's classic-locale C++ iostream implementation; on + the supported libc++, libstdc++, and MSVC standard libraries this is the + nearest representable `double` under the active floating-point rounding mode. + Applications that change that mode must restore round-to-nearest before + parsing when reproducibility across environments is required. +- A stored non-finite `double` (NaN/±infinity) **fails serialization by default** + (`SerializeOptions::RejectNonFinite`): `toString()` throws and `write()` sets + `failbit`. Use `NonFiniteToNull` to emit `null` (the pre-2.0 behavior) or + `NonFiniteToString` to emit `"NaN"`/`"Infinity"`/`"-Infinity"`. +- Double serialization is locale-independent and uses the Ryu + shortest-round-trip algorithm, providing bit-exact serialize/parse recovery + for every finite binary64 `double`. Integral-looking doubles retain a decimal marker (for example, `1.0`) so reparsing preserves the double storage kind; the spelling is not promised to be the shortest possible. +The type tags are: `jsonNull`, `jsonString`, `jsonNumberInt`, `jsonNumberUInt`, +`jsonNumberDouble`, `jsonBoolean`, `jsonArray`, `jsonObject`. + +### Thread safety + +pjson makes no positive shared-object guarantee beyond the C++ default: distinct +values may be used concurrently, but a single value must not be mutated +concurrently with any other access to it (or its subtree). A custom `Allocator` +must provide its own synchronization if shared across threads. The default +allocator and `getVersion()` are initialization-safe. + +`pJsonSchemaValidator` owns immutable schema/resource copies and may be read +concurrently when callers use separate error vectors. Resolver callbacks run +only during construction and are not retained. + +### Floating-point implementation note + +Fractional and exponent-form JSON numbers are converted through a +classic-locale `std::istream` extraction. On the supported libc++, libstdc++, +and MSVC standard libraries this follows the implementation's correctly rounded +decimal-to-binary conversion under the active floating-point rounding mode. +pjson does not change that process/thread rounding mode. The CI matrix verifies +halfway cases and binary64 extremes on GCC/libstdc++, Clang/libstdc++, +AppleClang/libc++, and MSVC. Serialization uses pinned Ryu +shortest-round-trip conversion, then applies pjson's documented +fixed/scientific spelling policy. The bit-pattern property suite is enabled +when the platform reports IEEE binary64. + --- ## Schema validation A document can be checked against a **schema that is itself a `pjson` object**, so schemas load and round-trip through `parse()`/`toString()` like any other -JSON. The documented vocabulary is a deliberately limited subset of -[JSON Schema](https://json-schema.org), not a complete draft implementation. -`validate()` is `noexcept` and normally collects every -applicable failure (a resource-budget failure stops traversal), each -reported as a `SchemaError { std::string path; std::string message; }` where -`path` is a JSON Pointer to the offending node. +JSON. Validation is performed by a standalone helper class, +`ByteDance::pJsonSchemaValidator` (declared in ``), that is a +pure consumer of pjson's public API — the core `pjson` class carries no schema +or regex machinery, while the schema implementation remains isolated in focused +private translation units within the current library target. Compile a schema +into a validator once, then reuse it for many instances. Default options retain +pjson's deliberately limited subset dialect; `Options::draft2020()` selects +the implemented required vocabularies of +[JSON Schema Draft 2020-12](https://json-schema.org/draft/2020-12/). +`validate()` is `noexcept` and normally collects every applicable failure (a +resource-budget failure stops traversal), each reported as a +`pJsonSchemaValidator::Error` with a stable `code`, separate +`instanceLocation` and `schemaLocation`, the triggering `keyword`, a +human-readable `message`, and optional nested `causes`. `category` distinguishes +`InstanceValidation` from `SchemaCompilation`. ```cpp -auto schema = pjson::parse(R"({ +#include "pjson_schema.h" + +pjson schema = pJsonParser().parse(R"({ "type": "object", "required": ["name", "age"], "properties": { @@ -870,23 +945,44 @@ auto schema = pjson::parse(R"({ "additionalProperties": false })"); -auto data = pjson::parse(R"({ "name": "Ada", "age": 36, "tags": ["x","y"] })"); +pjson data = pJsonParser().parse(R"({ "name": "Ada", "age": 36, "tags": ["x","y"] })"); + +// Compile the schema once, then reuse the validator. +pJsonSchemaValidator validator(schema); +if (!validator.isSchemaValid()) { + for (const auto& e : validator.schemaErrors()) + std::cerr << "invalid schema at " << e.schemaLocation << ": " << e.message << "\n"; +} // Simple pass/fail: -if (data->validate(*schema)) { +if (validator.validate(data)) { /* conforms */ } // Or collect all the reasons it failed: -std::vector errors; -if (!data->validate(*schema, errors)) { +std::vector errors; +if (!validator.validate(data, errors)) { for (const auto& e : errors) { - std::cerr << (e.path.empty() ? "(root)" : e.path) + std::cerr << (e.instanceLocation.empty() ? "(root)" : e.instanceLocation) << ": " << e.message << "\n"; } } ``` +Default construction implements pjson's explicitly named subset dialect. Use +`pJsonSchemaValidator::Options::draft2020()` to opt into the official Draft +2020-12 URI, bundled standard meta-schema validation, modern `$ref` behavior, +and per-resource `$vocabulary` activation. Custom meta-schema URIs are loaded +only through the supplied resolver during construction; pjson never performs +network I/O. Unknown optional vocabularies are annotations, while unknown +required vocabularies fail compilation. Optional big-number and historic +cross-draft behavior remain outside pjson's numeric and dialect contracts. + +References are compiled during construction; resolver callbacks and their +context are not retained, and validation performs no resolver I/O or cache +mutation. A compiled validator may therefore be read concurrently when callers +use separate error vectors. + Example failure output for `{ "age": "old" }` against the schema above: ```text (root): missing required property "name" @@ -903,48 +999,58 @@ schema["required"][1] = "age"; schema["properties"]["name"]["type"] = "string"; schema["properties"]["age"]["type"] = "integer"; schema["properties"]["age"]["minimum"] = int64_t(0); -bool ok = data->validate(schema); +bool ok = pJsonSchemaValidator(schema).validate(data); ``` -> **Warning:** Unknown or unsupported schema keywords are ignored and therefore -> impose no constraint. A typo can silently weaken validation. Treat the table -> below as an allowlist, audit schemas before use, and test both accepted and -> rejected instances for every intended rule. +> **Warning:** By default, unknown or unsupported schema keywords are ignored +> and therefore impose no constraint. A typo can silently weaken validation. +> Treat the table below as an allowlist, audit schemas before use, and test both +> accepted and rejected instances for every intended rule. For a fail-closed +> boundary, use `pJsonSchemaValidator::Options::strict()`, which rejects +> unsupported *standard* keywords instead of ignoring them. **Supported keywords:** | Applies to | Keywords | |------------|----------| -| any / references | `type` (name or array of names), `enum`, `const`, local-fragment `$ref` | -| objects | `properties`, `patternProperties`, `propertyNames`, `required`, `dependentRequired`, `dependencies`, `additionalProperties` (boolean or schema), `minProperties`, `maxProperties` | -| arrays | single-schema or tuple-array `items`, `minItems`, `maxItems`, `uniqueItems` | +| any / references | `type` (name or array of names), `enum`, `const`, `$id`, `$anchor`, `$dynamicAnchor`, `$ref`, `$dynamicRef` | +| objects | `properties`, `patternProperties`, `propertyNames`, `required`, `dependentRequired`, `dependencies`, `additionalProperties`, `unevaluatedProperties`, `minProperties`, `maxProperties` | +| arrays | single-schema or tuple-array `items`, `prefixItems`, `contains`, `minContains`, `maxContains`, `unevaluatedItems`, `minItems`, `maxItems`, `uniqueItems` | | numbers | `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` | | strings | `minLength`, `maxLength`, `pattern` (ECMAScript regex), `format` | | combinators | `allOf`, `anyOf`, `oneOf`, `not` | Notes: - `type: "integer"` matches whole numbers (including `2.0`); `type: "number"` - matches either numeric storage kind (`int64_t` or `double`). + matches every numeric storage kind (`int64_t`, `uint64_t`, or `double`). - `enum` / `const` use pjson's deep equality, so they work for arrays and objects too. - A boolean schema is allowed: `true` accepts every value, `false` rejects all. -- `$ref` resolves local URI fragments only; remote references are rejected. -- Known formats are `date`, `time`, `date-time`, `ipv4`, `ipv6`, and `uuid`; - unknown format names are ignored. +- `$ref` resolves URI resources, JSON Pointer fragments, and anchors using `$id` + bases. `$dynamicRef` and `$dynamicAnchor` follow dynamic scope. External + documents require an explicit function-pointer resolver; pjson never performs + network I/O. `Options::modernSubset()` enables modern `$ref` sibling semantics + and makes `format` annotation-only unless explicitly re-enabled. +- Known formats are `date`, `time`, `date-time`, `ipv4`, `ipv6`, `uuid`, and `regex`; + normal options assert them, while `modernSubset()` follows the Draft 2020-12 + annotation-only default. Unknown format names are ignored. - `minLength` and `maxLength` count Unicode code points, not UTF-8 bytes. - `pattern` uses ECMAScript syntax and search semantics. The default policy limits pattern and subject sizes and rejects unsafe expressions. -- `SchemaOptions` defaults `maxRegexPatternBytes` to 256, +- `pJsonSchemaValidator::Options` defaults `maxRegexPatternBytes` to 256, `maxRegexSubjectBytes` to 4096, `allowUnsafeRegex` to `false`, - `maxValidationDepth` to 64, `maxRefResolutions` to 1024, - `maxValidationWork` to 1,000,000, `maxErrors` to 100, and + `maxValidationDepth` to 32, `maxRefResolutions` to 1024, + `maxValidationWork` to 1,000,000, `maxErrors` to 100, + `maxResolvedDocuments` to 32, `maxResolvedBytes` to 16 MiB, and `validateFormats` to `true`. Zero removes only a regex byte limit; zero for a validation, reference, work, or error budget retains its documented hard - ceiling. Validation depth has an absolute hard ceiling of 64, so larger - configured values are clamped to 64. `trustedRegex()` removes only the regex + ceiling. Validation depth has an absolute hard ceiling of 32, so larger + configured values are clamped to 32. `trustedRegex()` removes only the regex limits/safety screen; reserve it for trusted schemas and data. -This is the documented pjson subset, not a complete JSON Schema draft. See +The default is pjson's documented subset dialect. Draft 2020-12 mode covers its +required vocabularies, but not the complete optional format-assertion vocabulary, +arbitrary-precision numbers, or other drafts. See [the schema tutorial](docs/06-schema-validation.md) and the [generated API reference](https://pico-developer.github.io/pjson/) for details. @@ -952,18 +1058,20 @@ This is the documented pjson subset, not a complete JSON Schema draft. See ## Error handling & allocator ownership -- Every `parse()` / `parseStream()` overload returns a `pjson::unique_ptr` (empty on - JSON or DOM-allocation failure), so ownership is automatic and there is no - manual `delete`. An exception-enabled input stream can still throw while - `parseStream()` buffers input. -- A supplied `ParseError` is reset for each attempt. Success leaves its success - state (`ok`, offset 0, line 1, column 1, empty message); failure records the - first error with a byte `offset`, one-based `line` and byte `column`, and a - human-readable `message`. +- Every `parse()` / `parseStream()` overload returns a `pjson` **by value** that + owns its subtree and frees it on destruction — no smart pointer, no manual + `delete`. The terse overloads return a JSON `null` value on failure; pass a + `pJsonParser::Error` to distinguish failure from a successfully parsed literal `null`. + An exception-enabled input stream can still throw while `parseStream()` buffers + input. +- A supplied `pJsonParser::Error` is reset for each attempt. Success leaves its success + state (`ok`, `code == None`, offset 0, line 1, column 1, empty message); + failure records the first error with a stable `code`, a byte `offset`, + one-based `line` and byte `column`, and a human-readable `message`. - The parser rejects trailing garbage, trailing/leading/doubled commas, unterminated strings/containers, malformed numbers (`1.`, `.5`, `1e`, `+1`), out-of-range numbers (`1e400`), and input nested deeper than - `ParseOptions::maxDepth`. + `pJsonParser::Options::maxDepth` (itself clamped to a stack-safe hard ceiling). - Strings are correctly escaped on output and unescaped on input, including `\uXXXX` (decoded to UTF-8) and surrogate pairs. - Invalid UTF-8 in a programmatically stored string makes `toString()` throw @@ -971,16 +1079,20 @@ This is the documented pjson subset, not a complete JSON Schema draft. See `failbit` before emitting bytes. The default `SerializeOptions::maxOutputBytes` is 64 MiB; exceeding it produces `std::length_error` from `toString()` or preflight `failbit` from `write()`. +- Non-throwing overloads accept `SerializeError` and report stable categories + for invalid UTF-8, rejected non-finite numbers, output limits, allocation + failure, stream failure, and internal errors. String output remains unchanged + on failure; only a physical stream failure may leave a partial prefix. - `tryGet` returns `false` without changing its output on a missing value or type mismatch. Operations that allocate, such as copying a string or moving across allocators, can still report allocation failure through the normal C++ mechanism unless their signature is explicitly `noexcept`. -The default constructors and parse overloads use pjson's default allocator; a -successful DOM parse returns `pjson::unique_ptr`. Applications -that need to route persistent DOM storage can derive from `pjson::Allocator`, -bind a root during construction, or pass it to an allocator-aware parse: +The default `pjson` and `pJsonParser` constructors use pjson's default allocator. +Applications that need to route persistent DOM storage can derive from +`pjson::Allocator`, bind a root during construction, or pass it to an +allocator-configured parser: ```cpp class Arena : public pjson::Allocator { @@ -992,32 +1104,31 @@ public: Arena arena; pjson value(arena); -pjson::ParseError error; -pjson::unique_ptr parsed = pjson::parse(text, error, arena); +pJsonParser::Error error; +pjson parsed = pJsonParser(arena).parse(text, error); // bound to arena ``` `allocate` must return non-null storage satisfying `bytes` and `alignment` or throw; `deallocate` receives matching metadata and must not throw. A directly constructed root such as `value` is caller-owned, while its wrapper objects and -dynamic descendants use its bound allocator. A parsed root is a `NodeAllocation` -released through `pjson::unique_ptr`. +dynamic descendants use its bound allocator. A parsed value is likewise bound to +the allocator passed to `parse()` and releases its storage through that +allocator on destruction. `Allocator` is borrowed and must outlive every bound root and descendant. It covers persistent nodes and string/array/object wrapper objects; backing allocations inside the standard containers and transient algorithm/parser -scratch space still use the standard allocator. The stateless `ValueDeleter` in -`pjson::unique_ptr` reads allocator provenance from the root; do not release a -parsed root and call `delete` on it. -`allocate` must return non-null storage satisfying the requested size and -alignment or throw; `deallocate` receives matching metadata and must not throw. +scratch space still use the standard allocator. Ordinary copy construction inherits the source allocator; `pjson(source, allocator)` explicitly deep-copies into another one. Copy and -move assignment preserve the destination allocator. Same-allocator moves are -constant-time, while cross-allocator moves deep-transfer and may allocate. -`swap()` is constant-time only when `canSwap()` is true; a cross-allocator swap -is a safe no-op. SAX parsing has no allocator overload because it creates no -persistent DOM. +move assignment preserve the destination allocator. Same-allocator moves +transfer storage after an ancestry-safety check, while cross-allocator moves +deep-transfer and may allocate. `swap()` performs the same safety check before +its constant-time exchange; cross-allocator and ancestor/descendant swaps are +safe no-ops. Rvalue insertion snapshots an ancestor when the destination lies +inside it. SAX parsing has no allocator overload because it creates no persistent +DOM. --- @@ -1025,20 +1136,24 @@ persistent DOM. | Category | Members | |----------|---------| -| Parse | `parse(str \| ptr,size, ...)`, `parseStream(std::istream&, ...)` → `pjson::unique_ptr` | -| Streaming parse | `parseSax(str \| ptr,size, handler, ...)`, `parseSaxStream(std::istream&, handler, ...)`, `SaxHandler` callbacks | -| Parse options | `ParseOptions{ maxDepth, maxNodes, maxInputBytes, duplicateKeys }`, `ParseError{ ok, offset, line, column, message }` | -| Serialize | `toString([SerializeOptions])`, `write(std::ostream&[, SerializeOptions])`; options include `maxOutputBytes` | -| Type | `getType()`, `isNull/isString/isNumber/isInt/isDouble/isBool/isArray/isObject()` | -| Typed read | node/key/index `tryGet(out&)` for scalars or `StringView`; result is untouched on failure | -| Inspect containers | `size()`, `empty()`, `keys()`, `hasKey(key)`, `hasIndex(index)`, `find(key\|index)` | +| Parse | `pJsonParser([allocator,] options).parse(str \| ptr,size [, Error&])`, `parseStream(std::istream&[, Error&])` → `pjson` by value | +| Streaming parse | `pJsonParser::parseSax(...)`, `parseSaxStream(...)`, `pJsonParser::SaxHandler` callbacks | +| Parse options | `pJsonParser::Options{ maxDepth, maxNodes, maxInputBytes, duplicateKeys, numberPolicy }`, `pJsonParser::Error{ ok, code, offset, line, column, message }` | +| Serialize | `toString([SerializeOptions])`, `write(std::ostream&[, SerializeOptions])`; options include `maxOutputBytes`, `nonFinite` | +| Type | `getType()`, `isNull/isString/isNumber/isInt/isUInt/isInteger/isDouble/isBool/isArray/isObject()` | +| Typed read | node/key/index `tryGet(out&)` for `int64_t`, `uint64_t`, `double`, `bool`, `std::string`, or `StringView`; untouched on failure | +| Inspect containers | `size()`, `empty()`, `keys()`, `hasKey(key)`, `contains(key)`, `hasIndex(index)`, `find(key\|index)` | +| Traverse | `forEachMember(fn, ctx)` in unspecified object-storage order; `forEachElement(fn, ctx)` in array order — non-allocating callback visitors | +| Checked read | `at(key)`, `at(index)` — throw `std::out_of_range`, never vivify | | JSON Pointer | `findPointer(pointer[, PointerError])`, `escapePointerToken(token)` | | JSON Patch | `applyPatch(patch[, PatchError][, PatchOptions])`, `applyMergePatch(patch[, PatchError][, PatchOptions])` | | Container ops | `size()`, `empty()`, `clear()`, `erase(key)`, `erase(index)` | | Compare | `operator==`, `operator!=` (deep, structural) | -| Validate | `validate(schema[, errors][, SchemaOptions])` — documented JSON Schema subset | +| Validate | `pJsonSchemaValidator v(schema[, Options]); v.validate(value[, errors])` — standalone validator (``); subset by default, required Draft 2020-12 vocabularies via `Options::draft2020()` | | Build | `operator[](key\|index)` — **vivifying** | -| Assign | `operator=` for strings, `bool`, `int64_t`, `double`, `std::vector`, `std::vector`, `std::vector`, and `std::vector` | +| Factories | `null()`, `object()`, `array()`; `operator=(nullptr)` | +| Insert | `pushBack(pjson[&&])`, `insertOrAssign(key, pjson[&&])`, `reserve(n)` | +| Assign | `operator=` for strings, `bool`, native and 64-bit integers, `float`, `double`, and matching `std::vector` types | | Append | `operator+=` for those same scalar and vector types; promotes the node to an array | | Lifetime / allocator | allocator-aware constructors, `getAllocator()`, `canSwap()`, `copyFrom()`, `swap()` | | Reset | `reset()` (→ null), `resetTo(jsonType)`, `resetIfNeeded(jsonType)` | @@ -1092,9 +1207,10 @@ Or run the collected binary directly to see every case: ./build/pjsontest/pjsontest # if built via plain cmake ``` -Deterministic generated cases live in `tests_fuzz.cpp`; four standalone +Deterministic generated cases live in `tests_fuzz.cpp`; seven standalone coverage-guided targets exercise DOM parsing/round trips, stream and SAX -agreement, schema validation, and the atomicity of JSON Patch and Merge Patch. +agreement, serialization, schema validation, JSON Pointer, and the atomicity of +JSON Patch and Merge Patch. With a full Clang/libFuzzer toolchain on Linux or macOS, replay the checked-in seeds with the CI-sized budget: @@ -1125,7 +1241,8 @@ upstream service enrollment. ## Benchmarking The Release benchmark suite measures parsing, compact serialization, read-only -traversal, and deep copying on generated small, medium, and large JSON +traversal, and deep copying on generated small, medium, large, wide-object, +large-array, string-heavy, escape-heavy, integer-heavy, and floating-heavy JSON documents. Run pjson by itself or compare the same cases with pinned versions of nlohmann/json, RapidJSON, and simdjson: @@ -1133,6 +1250,7 @@ of nlohmann/json, RapidJSON, and simdjson: ./build.sh --bench --release-only ./build.sh --bench-compare --release-only ./build.sh --bench-compare --release-only --auto # download pinned dependencies without prompting +./build.sh --bench --release-only --bench-json out/benchmark.json ``` Add real documents with repeatable `--bench-input` arguments: @@ -1149,9 +1267,13 @@ rows. Parse, serialize, and traverse cover every library. Copy covers pjson, nlohmann/json, and RapidJSON; simdjson has no equivalent owned mutable-DOM deep-copy operation. -### Reference comparison +### Historical reference comparison -The following snapshot was produced on this development machine with: +The following pre-matrix-expansion snapshot covers only the original three mixed +workloads. It is retained as a directional example, not a current release result. +Generate a current, provenance-bearing report with `--bench-json` rather than +copying these values into a performance claim. The snapshot was produced on this +development machine with: ```sh ./build.sh --bench-compare --release-only --auto @@ -1206,7 +1328,9 @@ machine before making performance-sensitive decisions. `MiB/s` is an input-size-normalized comparison, not actual serialized, visited, or copied bytes. The final `sink=` value is only an anti-optimization checksum. Benchmark results have no pass/fail threshold; compare Release runs made on the -same machine under similar load. See the [benchmark guide](bench/README.md) for +same machine under similar load. CI retains machine-readable reports as advisory +artifacts but does not gate on noisy shared-runner timings. See the +[benchmark guide](bench/README.md) for the exact timed work, dependency versions, methodology, and sample output. --- @@ -1214,6 +1338,7 @@ the exact timed work, dependency versions, methodology, and sample output. ## Documentation & project resources - [Tutorials](docs/README.md) and [streaming guide](docs/11-streaming.md) +- [pjson 2.0 behavioral and ABI contract](docs/behavioral-contract-2.0.md) - [Browsable API reference](https://pico-developer.github.io/pjson/) and its [source landing page](docs/reference/mainpage.md) - Migration guides for [nlohmann/json](docs/migration-from-nlohmann-json.md) and @@ -1245,26 +1370,40 @@ public API families fail validation. - pjson requires C++11 and owns a mutable DOM; it is not a zero-copy parser. `parseSaxStream()` avoids buffering the whole document, although its handler, current tokens, nesting state, and duplicate-key tracking still use memory. -- Object insertion order is not preserved; keys are stored in `std::map` and - serialize in selectable ascending or descending bytewise order. -- Duplicate object keys are rejected by default; `ParseOptions` can explicitly +- Object insertion and direct traversal order are not preserved. Private + process-seeded hash storage accelerates lookup; `keys()` and serialization + also use unspecified native storage order. +- Duplicate object keys are rejected by default; `pJsonParser::Options` can explicitly keep the first or last value. -- Numbers outside `int64_t` range fall back to `double` (may lose precision); - there is no separate unsigned-integer representation, and numbers outside - finite `double` range are rejected. Programmatically stored non-finite - floating values serialize as `null`. +- Signed integers use `int64_t`; unsigned integers above `INT64_MAX` use a + distinct `uint64_t` kind, so the full 64-bit range round-trips exactly. + Integer tokens outside `[INT64_MIN, UINT64_MAX]`, floating overflow, and + nonzero floating tokens that underflow to zero are rejected by default; opt + in with `pJsonParser::Options::AllowLossyNumbers` to store the nearest finite + `double`. A stored + non-finite `double` fails serialization by default; choose `NonFiniteToNull` + or `NonFiniteToString` to emit it. - Parsing always enforces RFC 8259, including valid UTF-8 and the standard lowercase literals and escape syntax. - Hostile-input limits default to 512 nesting levels, 1,000,000 materialized values, and 64 MiB of input; tune `maxDepth`, `maxNodes`, and `maxInputBytes`. -- Schema validation implements a documented subset, not a complete draft. It - ignores unknown keywords, so unsupported rules and misspellings are not - enforced. It does not compile/cache - schemas, resolve remote `$ref` values, validate during SAX parsing, support - `additionalItems`, or implement newer conditional/unevaluated vocabularies. - Tuple-form `items` validates corresponding positions but leaves elements past - the tuple unconstrained. String lengths count Unicode code points. Regex - matching uses the policy-limited default unless trusted mode is requested. + A configured `maxDepth` is clamped to a stack-safe hard ceiling (1024) that + cannot be raised. +- Schema validation uses a documented subset by default and implements the + required Draft 2020-12 vocabularies through `Options::draft2020()`. It is + provided by the standalone `pJsonSchemaValidator` (in ``), + which consumes only pjson's public API. Permissive subset mode ignores unknown + keywords (`Options::strict()` fails closed on unsupported standard keywords), + while Draft 2020-12 applies vocabulary policy per schema resource and compiles + schemas against bundled or explicitly resolved meta-schemas. It supports URI + resources, anchors, dynamic references, + `unevaluated*`, conditionals, `prefixItems`, `contains` bounds, and + `dependentSchemas`. External references and custom meta-schemas require an + application resolver and never perform implicit I/O. It does not validate + during SAX parsing. Tuple-form `items`/`prefixItems` + validates corresponding positions. String lengths count Unicode code points. + Regex matching uses the policy-limited default unless trusted mode is + requested. - A custom `Allocator` routes persistent DOM nodes and wrapper objects, not transient scratch storage or the backing allocations inside standard-library containers. diff --git a/REUSE.toml b/REUSE.toml index b1b55e0..27fbf7b 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -26,3 +26,32 @@ path = ["CODE_OF_CONDUCT.md"] precedence = "override" SPDX-FileCopyrightText = "2014 Coraline Ada Ehmke" SPDX-License-Identifier = "CC-BY-4.0" + +[[annotations]] +path = [ + "pjsonlib/src/third_party/srell/LICENSE.txt", + "pjsonlib/src/third_party/srell/srell.hpp", + "pjsonlib/src/third_party/srell/srell_ucfdata2.h", + "pjsonlib/src/third_party/srell/srell_updata3.h", +] +precedence = "override" +SPDX-FileCopyrightText = "2012-2026 Nozomu Katoo" +SPDX-License-Identifier = "BSD-2-Clause" + +[[annotations]] +path = [ + "pjsonlib/src/third_party/json-schema-2020-12/LICENSE.txt", + "pjsonlib/src/third_party/json-schema-2020-12/**/*.json", +] +precedence = "override" +SPDX-FileCopyrightText = "2022 JSON Schema Specification Authors" +SPDX-License-Identifier = "BSD-3-Clause OR AFL-3.0" + +[[annotations]] +path = [ + "pjsonlib/src/third_party/ryu/LICENSE.txt", + "pjsonlib/src/third_party/ryu/ryu/**", +] +precedence = "override" +SPDX-FileCopyrightText = "2018 Ulf Adams" +SPDX-License-Identifier = "Apache-2.0" diff --git a/SECURITY.md b/SECURITY.md index 9eba85f..fa215ea 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -10,7 +10,8 @@ against `main` are also welcome and fixes are released as soon as practical. | Version | Supported | | --- | --- | -| 1.0.x | Yes | +| 2.0.x | Yes | +| 1.0.x | No | | 0.0.x | No | Users of an unsupported version should upgrade before reporting a problem that diff --git a/Todo.md b/Todo.md index 2f2bac0..434d5bd 100644 --- a/Todo.md +++ b/Todo.md @@ -1,70 +1,36 @@ -# pjson — Production-Readiness Backlog + + -This file tracks only open work. Completed items are intentionally removed; use -the git history for their implementation details. FEAT-3 is intentionally -deferred: pjson will keep its current `std::map` object representation for now. +# pjson backlog -Current baseline: strict RFC 8259 parsing, bounded parser and schema resources, -JSON Pointer/Patch/Merge Patch, an expanded documented JSON Schema subset, -configurable serialization, allocator-aware DOM storage, non-vivifying typed -access, SAX streaming, individually registered tests, pinned conformance -corpora, libFuzzer/OSS-Fuzz targets, benchmarks, packaging, API reference, and -cross-platform CI. +There are no known release-blocking correctness issues. The core library +supports strict RFC 8259 JSON, JSON Pointer, JSON Patch, JSON Merge Patch, and +the documented JSON Schema subset. Object members use private, process-seeded +hash storage; insertion, traversal, `keys()`, and serialization order are +intentionally unspecified. ---- +## Optional JSON Schema formats -## Medium Priority +Implement additional asserted formats only when users need full format-assertion +coverage: duration, email/IDN email, hostname/IDN hostname, IRI/IRI-reference, +JSON Pointer/relative JSON Pointer, URI/URI-reference, and URI template. These +formats are optional in JSON Schema and are not required for the current +documented subset or required Draft 2020-12 vocabularies. -### [ ] MAINT-1 — Unify DOM and SAX parser grammar code +Arbitrary-precision numbers and cross-draft compatibility remain outside the +library's explicit numeric and dialect contracts. -**Where:** DOM parsing and SAX parsing currently use separate recursive-descent -implementations in `pjson.cpp`, with differential conformance tests guarding -their behavior. +## Controlled performance baseline -**Why:** duplicated token, number, Unicode, and container grammar logic raises -the chance that a future parser fix reaches only one API. The current paths are -well tested, so this is architectural debt rather than a release blocker. +Before enforcing benchmark thresholds, establish a dedicated runner and stable +release baseline, then agree per-workload limits. Hosted-runner measurements +should remain advisory. Allocation counts, peak RSS, binary size, and build time +need separate measurement protocols from operation latency. -**How:** extract a shared lexer/parser core parameterized by a DOM builder or SAX -event sink. Preserve the current error offsets, duplicate-key policies, resource -budgets, streaming cursor behavior, and DOM/SAX differential regression suite. +## Parser maintenance -### [ ] MAINT-2 — Split schema validation into keyword-family helpers - -**Where:** `_validateCtx` coordinates references, scalar keywords, containers, -regular expressions, and combinators in one large dispatcher. - -**Why:** the shared depth, work, reference, and reported-error budgets make this -logic security-sensitive; smaller helpers would make future keyword changes -easier to review without changing the public validation contract. - -**How:** extract focused reference, numeric, string, array, object, and -combinator helpers that all receive the same validation context and error sink. -Keep the official schema manifest and resource-budget tests green throughout. - -### [ ] MAINT-3 — Discover CTest cases from the compiled test registry - -**Where:** `pjsontest/CMakeLists.txt` currently extracts `TEST(name)` tokens -from source text, while the executable separately exposes `--list-tests`. - -**Why:** comments, conditional compilation, or future macro wrappers could make -source-text discovery drift from the cases compiled into the runner. CI compares -both counts today, so this is guarded architectural debt rather than a release -blocker. - -**How:** add a post-build discovery helper that invokes -`pjsontest --list-tests` and generates the CTest entries from that output. Keep -the CI nonzero/count check as a defense-in-depth assertion. - -### [ ] FEAT-3 — Preserve object key insertion order - -**Where:** pjson currently stores objects in `std::map`, so serialization sorts -keys alphabetically. - -**Why:** round-tripping that reorders keys creates noisy configuration and golden -file diffs. Most modern JSON DOMs preserve insertion order even though JSON -object semantics do not require it. - -**How:** use an insertion-ordered representation, such as a vector of key/value -pairs plus a lookup index. Preserve structural equality semantics and retain -protection from hash-collision denial of service if a hash index is introduced. +DOM and SAX parsing still have separate token, Unicode, and container control +flows. Continue sharing small, independently testable pieces only when a defect +or maintenance cost justifies the change. Preserve error offsets, duplicate-key +policies, resource limits, streaming behavior, and the differential tests. A +wholesale parser rewrite is not currently justified. diff --git a/VERSIONING.md b/VERSIONING.md index a31c961..02e1952 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -4,7 +4,7 @@ # Versioning Policy pjson uses [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). The -current stable version is **1.0.0**. Historical `0.0.x` releases used a +current stable version is **2.0.0**. Historical `0.0.x` releases used a `release-` tag prefix and predate this stability policy. ## Version meaning @@ -32,10 +32,16 @@ Private implementation details, tests, benchmarks, examples, diagnostics not documented as stable, and repository layout outside installed artifacts are not public API. -Semantic versioning describes source and behavioral compatibility. pjson does -not currently promise a stable C++ ABI across releases, compilers, standard -libraries, compiler flags, or build configurations. Rebuild pjson and dependent -C++ binaries together when upgrading. +Beginning with 2.0.0, pjson also maintains ABI compatibility within a major +release for the same compiler ABI, standard-library ABI, architecture, and +compatible build settings. `PJSON_ABI_VERSION` identifies that ABI generation. +The `pjson`, `pJsonParser`, and `pJsonSchemaValidator` object layouts are fixed +opaque handles; their private implementations may change without changing those +layouts. Public option/error structures, virtual interfaces, enum values, function +signatures, and exported symbols remain ABI-bearing and must not be reordered, +removed, or changed within the major release. A new compiler runtime, incompatible +standard library mode, architecture, or compile-time ABI switch remains a rebuild +boundary. ## Deprecation @@ -51,6 +57,7 @@ For each release, the following values must agree: - the top-level CMake project version; - `PJSON_VERSION`, `PJSON_VERSION_MAJOR`, `PJSON_VERSION_MINOR`, and `PJSON_VERSION_PATCH` in `pjson.h`; +- `PJSON_ABI_VERSION` in `pjson.h` and the shared-library `SOVERSION`; - package-manager or distribution metadata; and - the release heading in `CHANGELOG.md`. diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt index 4b8568c..01d307f 100644 --- a/bench/CMakeLists.txt +++ b/bench/CMakeLists.txt @@ -10,18 +10,98 @@ set(BENCH_SRC_FILES ${SRC_DIR}/benchmark_main.cpp ) -option(PJSON_BENCH_COMPARE "Enable optional third-party JSON benchmark comparisons" OFF) -set(PJSON_BENCH_DEPS_DIR "${CMAKE_SOURCE_DIR}/.benchmark-deps" - CACHE PATH "Directory containing pinned benchmark comparison dependencies") - if(MSVC) set(PJSON_BENCH_WARN_FLAGS /W4) + set(PJSON_BENCH_TARGET_FLAGS "/W4") else() set(PJSON_BENCH_WARN_FLAGS -Wall -Wextra) + set(PJSON_BENCH_TARGET_FLAGS "-Wall -Wextra") +endif() + +# Capture configure-time provenance in the machine-readable benchmark report. +# A dirty marker is kept separate from the revision so consumers can reject +# locally modified builds without losing the useful commit identity. +find_package(Git QUIET) +get_filename_component(PJSON_BENCH_SOURCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) +set(PJSON_BENCH_GIT_COMMIT "unknown") +set(PJSON_BENCH_GIT_DIRTY "unknown") +set(PJSON_BENCH_SOURCE_FINGERPRINT "unknown") +if(GIT_FOUND) + execute_process( + COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD + WORKING_DIRECTORY "${PJSON_BENCH_SOURCE_ROOT}" + RESULT_VARIABLE PJSON_BENCH_GIT_RESULT + OUTPUT_VARIABLE PJSON_BENCH_GIT_COMMIT + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + if(NOT PJSON_BENCH_GIT_RESULT EQUAL 0) + set(PJSON_BENCH_GIT_COMMIT "unknown") + endif() + execute_process( + COMMAND "${GIT_EXECUTABLE}" status --porcelain + WORKING_DIRECTORY "${PJSON_BENCH_SOURCE_ROOT}" + RESULT_VARIABLE PJSON_BENCH_DIRTY_RESULT + OUTPUT_VARIABLE PJSON_BENCH_DIRTY_OUTPUT + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + if(PJSON_BENCH_DIRTY_RESULT EQUAL 0) + if(PJSON_BENCH_DIRTY_OUTPUT STREQUAL "") + set(PJSON_BENCH_GIT_DIRTY "false") + else() + set(PJSON_BENCH_GIT_DIRTY "true") + endif() + endif() + execute_process( + COMMAND "${GIT_EXECUTABLE}" diff --binary HEAD + WORKING_DIRECTORY "${PJSON_BENCH_SOURCE_ROOT}" + RESULT_VARIABLE PJSON_BENCH_DIFF_RESULT + OUTPUT_VARIABLE PJSON_BENCH_SOURCE_DIFF + ERROR_QUIET) + if(PJSON_BENCH_DIFF_RESULT EQUAL 0) + string(SHA256 PJSON_BENCH_DIFF_HASH "${PJSON_BENCH_SOURCE_DIFF}") + set(PJSON_BENCH_SOURCE_FINGERPRINT + "${PJSON_BENCH_GIT_COMMIT}:${PJSON_BENCH_DIFF_HASH}") + endif() endif() +if(CMAKE_BUILD_TYPE) + set(PJSON_BENCH_BUILD_TYPE "${CMAKE_BUILD_TYPE}") +else() + set(PJSON_BENCH_BUILD_TYPE "multi-config/unspecified") +endif() +string(TOUPPER "${CMAKE_BUILD_TYPE}" PJSON_BENCH_BUILD_TYPE_UPPER) +set(PJSON_BENCH_BUILD_FLAGS "${CMAKE_CXX_FLAGS}") +if(PJSON_BENCH_BUILD_TYPE_UPPER AND + DEFINED CMAKE_CXX_FLAGS_${PJSON_BENCH_BUILD_TYPE_UPPER}) + string(APPEND PJSON_BENCH_BUILD_FLAGS + " ${CMAKE_CXX_FLAGS_${PJSON_BENCH_BUILD_TYPE_UPPER}}") +endif() +string(STRIP "${PJSON_BENCH_BUILD_FLAGS}" PJSON_BENCH_BUILD_FLAGS) + +# Escape values before placing them in ordinary C++ string literals. +foreach(PJSON_BENCH_CONFIG_VALUE + PJSON_BENCH_GIT_COMMIT PJSON_BENCH_GIT_DIRTY PJSON_BENCH_SOURCE_FINGERPRINT + PJSON_BENCH_BUILD_TYPE + PJSON_BENCH_BUILD_FLAGS PJSON_BENCH_TARGET_FLAGS + CMAKE_CXX_COMPILER CMAKE_CXX_COMPILER_ID CMAKE_CXX_COMPILER_VERSION + CMAKE_SYSTEM_NAME CMAKE_SYSTEM_VERSION CMAKE_SYSTEM_PROCESSOR) + string(REPLACE "\\" "\\\\" ${PJSON_BENCH_CONFIG_VALUE} + "${${PJSON_BENCH_CONFIG_VALUE}}") + string(REPLACE "\"" "\\\"" ${PJSON_BENCH_CONFIG_VALUE} + "${${PJSON_BENCH_CONFIG_VALUE}}") +endforeach() +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/benchmark_build_config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/generated/benchmark_build_config.h + @ONLY) + +option(PJSON_BENCH_COMPARE "Enable optional third-party JSON benchmark comparisons" OFF) +set(PJSON_BENCH_DEPS_DIR "${CMAKE_SOURCE_DIR}/.benchmark-deps" + CACHE PATH "Directory containing pinned benchmark comparison dependencies") + add_executable(${TARGET_NAME} ${BENCH_SRC_FILES}) target_link_libraries(${TARGET_NAME} PRIVATE pjson::pjson) +target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated) target_compile_features(${TARGET_NAME} PRIVATE cxx_std_11) target_compile_options(${TARGET_NAME} PRIVATE ${PJSON_BENCH_WARN_FLAGS}) diff --git a/bench/README.md b/bench/README.md index 248c1fb..580218d 100644 --- a/bench/README.md +++ b/bench/README.md @@ -34,6 +34,12 @@ Each operation runs against representative generated workloads: - `small`: compact session-style object with nested user/event data - `medium`: user/session dataset with arrays, nested objects, booleans, and numbers - `large`: inventory-style dataset with hundreds of nested records and repeated arrays +- `wide-object`: 2,048 sibling members, isolating object lookup/iteration overhead +- `large-array`: 8,192 integer elements in one flat array +- `string-heavy`: 1,024 long, mostly unescaped UTF-8 strings +- `escape-heavy`: 1,024 strings containing quotes, backslashes, controls, and UTF-8 +- `integer-heavy`: 8,192 varied signed integers +- `floating-heavy`: 8,192 fractional values across small and large magnitudes The harness adapts iteration counts so each measurement runs long enough to produce stable timings, then records six timed samples. Comparison output is @@ -104,6 +110,55 @@ The benchmark binary also accepts direct inputs: Unreadable or invalid JSON inputs are skipped with a warning so the suite still runs on the remaining workloads. +### Machine-readable results + +Write the same run as a versioned JSON artifact with either interface: + +```bash +./build.sh --bench --release-only --bench-json out/benchmark.json +./out/release/bin/pjsonbench --json out/benchmark.json +``` + +`--json -` emits only JSON on stdout; progress goes to stderr. The report records: + +- the pjson version, configure-time Git commit, and dirty status; +- compiler identity/version, build type, effective CMake and target flags, and + C++ language level; +- operating system, version, architecture, allocator disclosure, and an optional + environment label; +- the timing methodology, workload origins/sizes, implementation versions, and + raw nanosecond/throughput results. + +For controlled runners, supply labels that CMake cannot discover portably: + +```bash +PJSON_BENCH_ENVIRONMENT=linux-perf-runner-01 \ +PJSON_BENCH_CPU='AMD EPYC 7B13' \ +PJSON_BENCH_ALLOCATOR='glibc malloc 2.39' \ +./build.sh --bench --release-only --bench-json out/benchmark.json +``` + +GitHub Actions retains baseline and comparison JSON reports for 30 days. Those +jobs use shared hosted runners, so their values are diagnostic artifacts rather +than pass/fail gates. A downstream controlled-runner job can compare +`median_ns` against a release artifact and report an agreed per-case threshold. +The repository comparator enforces environment compatibility by default: + +```bash +python3 scripts/compare-benchmarks.py baseline.json candidate.json +python3 scripts/compare-benchmarks.py baseline.json candidate.json \ + --threshold-percent 10 --fail-on-regression +``` + +The comparator rejects environment mismatches, duplicate result identities, +invalid medians, and missing/extra cases. This prevents a partial candidate +report from silently hiding a workload regression. + +The first form is advisory. Use `--fail-on-regression` only on a controlled +runner after the environment label, OS, architecture, CPU, allocator, compiler, +build type, and flags are stable. `--allow-environment-mismatch` is intended for +exploratory reports and should not gate a release. + ### Output The report is plain text and intended for direct, case-by-case comparison. For @@ -156,3 +211,24 @@ The suite does not impose pass/fail thresholds because benchmark numbers are sensitive to machine load, CPU scaling, allocator behavior, and backend-specific parser strategies. Record results from comparable Release builds on the same machine when tracking regressions. + +The requirements also mention move latency, allocation counts, peak resident +memory, object/binary size, and build time. They are intentionally not folded +into this timing table: moving a `pjson` mostly transfers its small implementation +handle and is timer-overhead-sensitive, while the other metrics require allocator, +OS, or build-system instrumentation. Report them separately when a controlled +runner and measurement protocol are available. + +Portable artifact sizes can be captured separately while preserving the same +source/build/environment metadata: + +```bash +python3 scripts/benchmark-aux-metrics.py out/benchmark.json \ + --artifact out/release/lib/libpjson.a \ + --artifact out/release/bin/pjsonbench \ + --output out/benchmark-aux.json +``` + +Peak RSS, allocation counts, clean build time, and incremental build time remain +platform-specific measurements. A controlled runner should collect and publish +them beside this auxiliary report with its exact command and tool versions. diff --git a/bench/src/benchmark_build_config.h.in b/bench/src/benchmark_build_config.h.in new file mode 100644 index 0000000..20a778d --- /dev/null +++ b/bench/src/benchmark_build_config.h.in @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// Generated by CMake; do not edit. +#ifndef PJSON_BENCHMARK_BUILD_CONFIG_H +#define PJSON_BENCHMARK_BUILD_CONFIG_H + +#define PJSON_BENCH_GIT_COMMIT "@PJSON_BENCH_GIT_COMMIT@" +#define PJSON_BENCH_GIT_DIRTY "@PJSON_BENCH_GIT_DIRTY@" +#define PJSON_BENCH_SOURCE_FINGERPRINT "@PJSON_BENCH_SOURCE_FINGERPRINT@" +#define PJSON_BENCH_BUILD_TYPE "@PJSON_BENCH_BUILD_TYPE@" +#define PJSON_BENCH_BUILD_FLAGS "@PJSON_BENCH_BUILD_FLAGS@" +#define PJSON_BENCH_TARGET_FLAGS "@PJSON_BENCH_TARGET_FLAGS@" +#define PJSON_BENCH_COMPILER_PATH "@CMAKE_CXX_COMPILER@" +#define PJSON_BENCH_COMPILER_ID "@CMAKE_CXX_COMPILER_ID@" +#define PJSON_BENCH_COMPILER_VERSION "@CMAKE_CXX_COMPILER_VERSION@" +#define PJSON_BENCH_SYSTEM_NAME "@CMAKE_SYSTEM_NAME@" +#define PJSON_BENCH_SYSTEM_VERSION "@CMAKE_SYSTEM_VERSION@" +#define PJSON_BENCH_SYSTEM_PROCESSOR "@CMAKE_SYSTEM_PROCESSOR@" + +#endif diff --git a/bench/src/benchmark_main.cpp b/bench/src/benchmark_main.cpp index 69701f4..e97c1f2 100644 --- a/bench/src/benchmark_main.cpp +++ b/bench/src/benchmark_main.cpp @@ -1,13 +1,17 @@ +#include "benchmark_build_config.h" #include "pjson.h" +#include "pjson_parser.h" #include #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -24,6 +28,7 @@ namespace { using ByteDance::pjson; + using ByteDance::pJsonParser; // ------------------------------------------------------------------------- // Benchmark data and anti-optimization state @@ -40,7 +45,7 @@ namespace { std::string name; std::string origin; std::string jsonText; - pjson::unique_ptr parsed; + pjson parsed; }; // Per-operation timing summary. Times remain in nanoseconds internally and @@ -62,6 +67,10 @@ namespace { RunStats stats; }; + static const double kTargetSeconds = 0.075; + static const std::size_t kMaxIterations = 1U << 22U; + static const int kSamples = 6; + // ------------------------------------------------------------------------- // Stable result hashing and DOM traversal // ------------------------------------------------------------------------- @@ -116,6 +125,10 @@ namespace { return value.tryGet(number) ? mixHash(hash, static_cast(number)) : hash; } + case pjson::jsonNumberUInt: { + std::uint64_t number = 0; + return value.tryGet(number) ? mixHash(hash, number) : hash; + } case pjson::jsonNumberDouble: { double number = 0.0; if (!value.tryGet(number)) { @@ -133,7 +146,7 @@ namespace { const std::size_t elementCount = value.size(); hash = mixHash(hash, static_cast(elementCount)); for (std::size_t i = 0; i < elementCount; ++i) { - const pjson* child = value.find(static_cast(i)); + const pjson* child = value.findIndex(i); if (child != NULL) { hash = mixHash(hash, traversePjson(*child)); } @@ -288,6 +301,67 @@ namespace { return root; } + // Isolates lookup and iteration costs for objects with many siblings. + pjson buildWideObjectDocument() { + pjson root; + for (int i = 0; i < 2048; ++i) { + root[std::string("field-") + makePaddedNumber(i, 5)] = static_cast(i * 17); + } + return root; + } + + // Isolates contiguous array parsing, traversal, serialization, and copying. + pjson buildLargeArrayDocument() { + pjson root; + for (int i = 0; i < 8192; ++i) { + root += static_cast((i * 7919) % 1000003); + } + return root; + } + + // Uses long, unescaped UTF-8 values so string storage dominates node overhead. + pjson buildStringHeavyDocument() { + pjson root; + const std::string base = + "The quick brown fox jumps over the lazy dog; pjson benchmark payload "; + for (int i = 0; i < 1024; ++i) { + root += base + makePaddedNumber(i, 5) + " \xE2\x98\x83"; + } + return root; + } + + // Forces the wire representation through quote, slash, control-character, + // and Unicode escaping paths instead of measuring only raw string copying. + pjson buildEscapeHeavyDocument() { + pjson root; + const std::string escaped = + "quote=\" backslash=\\ newline=\n tab=\t control=\x01 snowman=\xE2\x98\x83"; + for (int i = 0; i < 1024; ++i) { + root += escaped + makePaddedNumber(i, 5); + } + return root; + } + + // Keeps the payload numeric while spanning signed and unsigned-looking values. + pjson buildIntegerHeavyDocument() { + pjson root; + for (int i = 0; i < 8192; ++i) { + const int64_t magnitude = static_cast(i) * 1000003LL + 17LL; + root += (i % 3 == 0) ? -magnitude : magnitude; + } + return root; + } + + // Exercises decimal conversion with fractional values and varied magnitudes. + pjson buildFloatingHeavyDocument() { + pjson root; + for (int i = 0; i < 8192; ++i) { + const double scale = (i % 2 == 0) ? 0.000001 : 1000000.0; + root += (static_cast((i * 104729) % 10000019) + 0.125) * scale; + } + return root; + } + // ------------------------------------------------------------------------- // Workload preparation // ------------------------------------------------------------------------- @@ -300,12 +374,13 @@ namespace { workload.name = name; workload.origin = origin; workload.jsonText = jsonText; - workload.parsed = pjson::parse(workload.jsonText); - if (!workload.parsed) { + pJsonParser::Error parseError; + workload.parsed = pJsonParser().parse(workload.jsonText, parseError); + if (!parseError.ok) { std::cerr << "failed to parse benchmark workload: " << name << "\n"; std::exit(1); } - consumeHash(traversePjson(*workload.parsed)); + consumeHash(traversePjson(workload.parsed)); consumeSize(workload.jsonText.size()); return workload; } @@ -331,13 +406,25 @@ namespace { return path.substr(slash + 1); } - // Creates the three built-in workloads and appends each valid user-supplied + // Creates the built-in mixed and shape-specific workloads and appends valid // corpus document. Invalid inputs are warned about rather than aborting a run. std::vector buildWorkloads(const std::vector& inputFiles) { std::vector workloads; workloads.push_back(makeWorkload("small", "generated", buildSmallDocument().toString())); workloads.push_back(makeWorkload("medium", "generated", buildMediumDocument().toString())); workloads.push_back(makeWorkload("large", "generated", buildLargeDocument().toString())); + workloads.push_back( + makeWorkload("wide-object", "generated", buildWideObjectDocument().toString())); + workloads.push_back( + makeWorkload("large-array", "generated", buildLargeArrayDocument().toString())); + workloads.push_back( + makeWorkload("string-heavy", "generated", buildStringHeavyDocument().toString())); + workloads.push_back( + makeWorkload("escape-heavy", "generated", buildEscapeHeavyDocument().toString())); + workloads.push_back( + makeWorkload("integer-heavy", "generated", buildIntegerHeavyDocument().toString())); + workloads.push_back( + makeWorkload("floating-heavy", "generated", buildFloatingHeavyDocument().toString())); for (std::size_t i = 0; i < inputFiles.size(); ++i) { const std::string jsonText = readFile(inputFiles[i]); @@ -346,8 +433,9 @@ namespace { continue; } - pjson::unique_ptr parsed = pjson::parse(jsonText); - if (!parsed) { + pJsonParser::Error parseError; + pjson parsed = pJsonParser().parse(jsonText, parseError); + if (!parseError.ok) { std::cerr << "warning: benchmark input is not valid JSON and was skipped: " << inputFiles[i] << "\n"; continue; @@ -384,10 +472,6 @@ namespace { // noise for fast cases while the fixed cap bounds unexpectedly expensive runs. template RunStats measure(const std::string& payload, Operation operation) { - static const double kTargetSeconds = 0.075; - static const std::size_t kMaxIterations = 1U << 22U; - static const int kSamples = 6; - // Perform one untimed call to trigger lazy initialization before calibration. operation(); @@ -449,10 +533,13 @@ namespace { // Prints accepted arguments and the high-level benchmark scope. void printUsage(const char* argv0) { - std::cout << "Usage: " << argv0 << " [--input ]... [--compare]\n" + std::cout << "Usage: " << argv0 << " [--input ]... [--compare] [--json ]\n" << "Benchmarks parse, compact serialize, traversal, and deep copy\n" - << "across generated small/medium/large documents, plus any extra\n" - << "JSON files supplied with --input. When built with optional\n" + << "across mixed and shape-specific generated documents, plus any\n" + << "JSON files supplied with --input. --json writes a versioned,\n" + << "machine-readable result document; use '-' for JSON-only stdout.\n" + << "PJSON_BENCH_ENVIRONMENT may name a controlled runner. When built\n" + << "with optional\n" << "third-party dependencies, --compare groups every benchmark case\n" << "with adjacent cross-library rows. Timing is lower-is-better;\n" << "throughput is higher-is-better.\n"; @@ -533,6 +620,130 @@ namespace { } } + std::string currentUtcTime() { + const std::time_t now = std::time(NULL); + const std::tm* utc = std::gmtime(&now); + if (utc == NULL) { + return "unknown"; + } + char text[32] = {}; + if (std::strftime(text, sizeof(text), "%Y-%m-%dT%H:%M:%SZ", utc) == 0U) { + return "unknown"; + } + return text; + } + + // Builds a stable JSON artifact for storage and comparison by external tools. + // It deliberately records raw measurements without applying a regression + // threshold: only callers with a controlled machine can set a meaningful one. + pjson buildMachineReport(const std::vector& workloads, + const std::vector& results, bool compared) { + pjson report; + report["format"] = "pjson-benchmark"; + report["format_version"] = static_cast(1); + report["captured_at_utc"] = currentUtcTime(); + report["library_version"] = PJSON_VERSION; + + report["source"]["commit"] = PJSON_BENCH_GIT_COMMIT; + report["source"]["fingerprint"] = PJSON_BENCH_SOURCE_FINGERPRINT; + report["source"]["dirty_known"] = std::string(PJSON_BENCH_GIT_DIRTY) != "unknown"; + report["source"]["dirty"] = std::string(PJSON_BENCH_GIT_DIRTY) == "true"; + + const char* environment = std::getenv("PJSON_BENCH_ENVIRONMENT"); + const char* cpu = std::getenv("PJSON_BENCH_CPU"); + const char* allocator = std::getenv("PJSON_BENCH_ALLOCATOR"); + report["environment"]["label"] = + environment != NULL && environment[0] != '\0' ? environment : "unspecified"; + report["environment"]["operating_system"] = PJSON_BENCH_SYSTEM_NAME; + report["environment"]["operating_system_version"] = PJSON_BENCH_SYSTEM_VERSION; + report["environment"]["architecture"] = PJSON_BENCH_SYSTEM_PROCESSOR; + report["environment"]["cpu"] = cpu != NULL && cpu[0] != '\0' ? cpu : "unspecified"; + report["environment"]["allocator"] = + allocator != NULL && allocator[0] != '\0' + ? allocator + : "default C++ runtime allocator (implementation unspecified)"; + + report["build"]["type"] = PJSON_BENCH_BUILD_TYPE; + report["build"]["compiler_path"] = PJSON_BENCH_COMPILER_PATH; + report["build"]["compiler_id"] = PJSON_BENCH_COMPILER_ID; + report["build"]["compiler_version"] = PJSON_BENCH_COMPILER_VERSION; + report["build"]["cxx_standard"] = compared ? "C++17" : "C++11"; + report["build"]["flags"] = std::string(PJSON_BENCH_BUILD_FLAGS) + + (PJSON_BENCH_BUILD_FLAGS[0] == '\0' ? "" : " ") + + PJSON_BENCH_TARGET_FLAGS; + + report["methodology"]["clock"] = "std::chrono::steady_clock"; + report["methodology"]["warmup_calls"] = static_cast(1); + report["methodology"]["timed_samples"] = static_cast(kSamples); + report["methodology"]["target_sample_seconds"] = kTargetSeconds; + report["methodology"]["maximum_iterations_per_sample"] = + static_cast(kMaxIterations); + report["methodology"]["primary_statistic"] = "median_ns"; + report["methodology"]["throughput_basis"] = + "original input bytes divided by median latency"; + report["methodology"]["threshold_policy"] = "none; compare controlled runs externally"; + + report["implementations"][0]["name"] = "pjson"; + report["implementations"][0]["version"] = PJSON_VERSION; +#ifdef PJSON_BENCH_COMPARE + if (compared) { + report["implementations"][1]["name"] = "nlohmann/json"; + report["implementations"][1]["version"] = "3.11.3"; + report["implementations"][2]["name"] = "RapidJSON"; + report["implementations"][2]["version"] = "1.1.0"; + report["implementations"][3]["name"] = "simdjson"; + report["implementations"][3]["version"] = "3.12.2"; + } +#else + (void)compared; +#endif + + for (std::size_t i = 0; i < workloads.size(); ++i) { + report["workloads"][i]["name"] = workloads[i].name; + report["workloads"][i]["origin"] = workloads[i].origin; + report["workloads"][i]["input_bytes"] = + static_cast(workloads[i].jsonText.size()); + } + for (std::size_t i = 0; i < results.size(); ++i) { + const BenchmarkResult& result = results[i]; + const Workload& workload = workloads[result.workloadIndex]; + pjson& row = report["results"][i]; + row["library"] = result.library; + row["workload"] = workload.name; + row["operation"] = result.operation; + row["input_bytes"] = static_cast(workload.jsonText.size()); + row["iterations_per_sample"] = static_cast(result.stats.iterations); + row["best_ns"] = result.stats.bestNs; + row["median_ns"] = result.stats.medianNs; + row["average_ns"] = result.stats.averageNs; + row["mib_per_second"] = result.stats.mibPerSecond; + } + return report; + } + + bool writeMachineReport(const std::string& path, const pjson& report) { + pjson::SerializeOptions options; + options.pretty = true; + options.indentWidth = 2; + if (path == "-") { + report.write(std::cout, options); + std::cout << "\n"; + return static_cast(std::cout); + } + std::ofstream output(path.c_str(), std::ios::out | std::ios::binary | std::ios::trunc); + if (!output) { + std::cerr << "unable to open benchmark JSON output '" << path << "'\n"; + return false; + } + report.write(output, options); + output << "\n"; + if (!output) { + std::cerr << "failed to write benchmark JSON output '" << path << "'\n"; + return false; + } + return true; + } + // ------------------------------------------------------------------------- // pjson benchmark cases // ------------------------------------------------------------------------- @@ -545,28 +756,29 @@ namespace { const Workload& workload = workloads[i]; const RunStats parseStats = measure(workload.jsonText, [&workload]() { - pjson::unique_ptr parsed = pjson::parse(workload.jsonText); - if (!parsed) { + pJsonParser::Error parseError; + pjson parsed = pJsonParser().parse(workload.jsonText, parseError); + if (!parseError.ok) { std::cerr << "benchmark parse failed for " << workload.name << "\n"; std::exit(1); } - consumeHash(traversePjson(*parsed)); + consumeHash(traversePjson(parsed)); }); recordResult(results, i, "pjson", "parse", parseStats); const RunStats serializeStats = measure(workload.jsonText, [&workload]() { - const std::string jsonText = workload.parsed->toString(); + const std::string jsonText = workload.parsed.toString(); consumeSize(jsonText.size()); consumeHash(hashString(jsonText)); }); recordResult(results, i, "pjson", "serialize", serializeStats); const RunStats traverseStats = measure( - workload.jsonText, [&workload]() { consumeHash(traversePjson(*workload.parsed)); }); + workload.jsonText, [&workload]() { consumeHash(traversePjson(workload.parsed)); }); recordResult(results, i, "pjson", "traverse", traverseStats); const RunStats copyStats = measure(workload.jsonText, [&workload]() { - pjson copy(*workload.parsed); + pjson copy(workload.parsed); consumeHash(traversePjson(copy)); consumeSize(copy.size()); }); @@ -872,6 +1084,7 @@ int main(int argc, char** argv) { // --- Parse command-line inputs -------------------------------------------- std::vector inputFiles; bool requestCompare = false; + std::string jsonOutput; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if (arg == "--help" || arg == "-h") { @@ -882,6 +1095,22 @@ int main(int argc, char** argv) { requestCompare = true; continue; } + if (arg == "--json") { + if (i + 1 >= argc) { + std::cerr << "--json requires a file path or '-'\n"; + return 2; + } + jsonOutput = argv[++i]; + continue; + } + if (arg.compare(0, 7, "--json=") == 0) { + jsonOutput = arg.substr(7); + if (jsonOutput.empty()) { + std::cerr << "--json requires a file path or '-'\n"; + return 2; + } + continue; + } if (arg == "--input") { if (i + 1 >= argc) { std::cerr << "--input requires a file path\n"; @@ -905,15 +1134,18 @@ int main(int argc, char** argv) { return 1; } - std::cout << "pjson benchmark suite\n"; - std::cout << "generated workloads: small, medium, large"; + const bool jsonOnly = jsonOutput == "-"; + std::ostream& progress = jsonOnly ? std::cerr : std::cout; + progress << "pjson benchmark suite\n"; + progress << "generated workloads: small, medium, large, wide-object, large-array, " + "string-heavy, escape-heavy, integer-heavy, floating-heavy"; if (!inputFiles.empty()) { - std::cout << " | requested extra inputs: " << inputFiles.size(); + progress << " | requested extra inputs: " << inputFiles.size(); } if (requestCompare) { - std::cout << " | compare requested"; + progress << " | compare requested"; } - std::cout << "\n"; + progress << "\n"; std::vector results; runPjsonBenchmarks(workloads, results); @@ -931,9 +1163,15 @@ int main(int argc, char** argv) { #endif // --- Render grouped results and the anti-optimization checksum ------------- - printResultsByCase(workloads, results); - std::cout << std::string(126, '-') << "\n"; - std::cout << "sink=" << g_sink_size << "/" << g_sink_hash - << " (anti-optimization checksum; not a performance measurement)\n"; + if (!jsonOnly) { + printResultsByCase(workloads, results); + std::cout << std::string(126, '-') << "\n"; + std::cout << "sink=" << g_sink_size << "/" << g_sink_hash + << " (anti-optimization checksum; not a performance measurement)\n"; + } + if (!jsonOutput.empty() && + !writeMachineReport(jsonOutput, buildMachineReport(workloads, results, requestCompare))) { + return 1; + } return 0; } diff --git a/build.sh b/build.sh index 0d6f5da..f2680f9 100755 --- a/build.sh +++ b/build.sh @@ -10,7 +10,7 @@ # out/ # release/{lib,bin,bin/examples} Release library, tests, examples # debug/{lib,bin,bin/examples} Debug library, tests, examples -# include/pjson.h public header +# include/{pjson,pjson_parser,pjson_schema}.h public headers # build-release/ build-debug/ CMake build trees # # Usage: @@ -39,6 +39,8 @@ # ./build.sh --tidy Run clang-tidy static analysis (fails on findings) # ./build.sh --bench-input PATH # Add an extra JSON file for benchmark coverage +# ./build.sh --bench-json PATH +# Write machine-readable benchmark results # ./build.sh --auto Never prompt; auto-install/download dependencies # # Flags combine freely. Missing tools and optional JSON/JSON-Schema conformance @@ -77,6 +79,7 @@ Usage: ./build.sh [flags] --bench Build, then run the Release benchmark suite --bench-compare Build, then run the Release benchmark comparison suite --bench-input Add an extra JSON file to the benchmark corpus (repeatable) + --bench-json Write a versioned JSON benchmark report to PATH --fuzz Build libFuzzer targets and run bounded corpus smoke tests --docs Build and validate the generated API reference --package Run static/shared install and pkg-config consumer smoke tests @@ -125,6 +128,7 @@ AUTO=0 RELEASE_ONLY=0 DEBUG_ONLY=0 BENCH_INPUTS=() +BENCH_JSON="" # No flags at all is a friendly shortcut for --all (do everything). if [ "$#" -eq 0 ]; then @@ -160,6 +164,23 @@ while [ "$#" -gt 0 ]; do --bench-input=*) BENCH_INPUTS+=("${1#--bench-input=}") ;; + --bench-json) + shift + if [ "$#" -eq 0 ]; then + echo "Missing value for --bench-json" >&2 + usage >&2 + exit 2 + fi + BENCH_JSON="$1" + ;; + --bench-json=*) + BENCH_JSON="${1#--bench-json=}" + if [ -z "${BENCH_JSON}" ]; then + echo "Missing value for --bench-json" >&2 + usage >&2 + exit 2 + fi + ;; --auto|--yes|-y) AUTO=1 ;; -h|--help) usage; exit 0 ;; *) @@ -470,7 +491,10 @@ source_files() { find "${SCRIPT_DIR}/pjsonlib" "${SCRIPT_DIR}/pjsontest" "${SCRIPT_DIR}/examples" \ "${SCRIPT_DIR}/bench" "${SCRIPT_DIR}/fuzz" "${SCRIPT_DIR}/test_package" \ "${SCRIPT_DIR}/tests" \ - \( -name '*.cpp' -o -name '*.h' \) -type f | sort + \( -name '*.cpp' -o -name '*.h' \) -type f \ + ! -path '*/third_party/*' \ + ! -path '*/build/*' \ + ! -path '*/out/*' | sort } # --------------------------------------------------------------------------- @@ -567,7 +591,10 @@ build_one() { find "${bdir}/examples" -maxdepth 2 -type f \ \( -perm -u+x -o -name '*.exe' \) ! -name '*.o' ! -name '*.obj' \ -exec cp {} "${dest}/bin/examples/" \; 2>/dev/null || true - cp "${SCRIPT_DIR}/pjsonlib/include/pjson.h" "${OUT_DIR}/include/" + cp "${SCRIPT_DIR}/pjsonlib/include/pjson.h" \ + "${SCRIPT_DIR}/pjsonlib/include/pjson_parser.h" \ + "${SCRIPT_DIR}/pjsonlib/include/pjson_schema.h" \ + "${OUT_DIR}/include/" LAST_BUILD_DIR="${bdir}" } @@ -759,6 +786,9 @@ if [ "${DO_BENCH}" -eq 1 ]; then if [ "${DO_BENCH_COMPARE}" -eq 1 ]; then BENCH_ARGS+=(--compare) fi + if [ -n "${BENCH_JSON}" ]; then + BENCH_ARGS+=(--json "${BENCH_JSON}") + fi echo ">> Running benchmarks (Release)" if [ "${#BENCH_ARGS[@]}" -gt 0 ]; then @@ -772,7 +802,7 @@ fi # Optional fuzz, documentation, and packaging validation. # --------------------------------------------------------------------------- -# Probes for a usable Clang/libFuzzer pair, builds all four harnesses, and +# Probes for a usable Clang/libFuzzer pair, builds every harness, and # replays each checked-in seed corpus with deterministic bounds. run_fuzz_smoke() { case "$(uname -s)" in @@ -859,15 +889,27 @@ run_fuzz_smoke() { -DPJSON_BUILD_FUZZERS=ON \ ${GEN_ARG} "${CMAKE}" --build "${fuzz_build_dir}" --parallel --target \ - pjson_fuzz_parse pjson_fuzz_stream pjson_fuzz_schema pjson_fuzz_patch + pjson_fuzz_parse pjson_fuzz_stream pjson_fuzz_serialize pjson_fuzz_schema \ + pjson_fuzz_pointer pjson_fuzz_patch pjson_fuzz_merge_patch local target corpus_dir - for target in parse stream schema patch; do + for target in parse stream serialize schema pointer patch merge_patch; do corpus_dir="${OUT_DIR}/fuzz-corpus/${target}" mkdir -p "${corpus_dir}" "${OUT_DIR}/fuzz-artifacts/${target}" echo ">> Fuzz corpus smoke: pjson_fuzz_${target}" - "${fuzz_build_dir}/fuzz/pjson_fuzz_${target}" \ - -runs=1000 -seed=1337 -max_len=4096 -timeout=5 -verbosity=0 \ + # Homebrew LLVM's libFuzzer runtime can be built against libc++ with + # container annotations that differ from the active macOS SDK headers. + # That mismatch produces a false container-overflow while libFuzzer + # scans its corpus, before LLVMFuzzerTestOneInput is called. Disable + # only container annotation checking on macOS; ordinary ASan and UBSan + # instrumentation remain enabled for pjson and the fuzz harnesses. + local fuzz_asan_options="${ASAN_OPTIONS:-}" + if [ "$(uname -s)" = "Darwin" ]; then + fuzz_asan_options="${fuzz_asan_options:+${fuzz_asan_options}:}detect_container_overflow=0" + fi + ASAN_OPTIONS="${fuzz_asan_options}" \ + "${fuzz_build_dir}/fuzz/pjson_fuzz_${target}" \ + -runs=1000 -seed=1337 -max_len=65536 -timeout=5 -verbosity=0 \ -dict="${SCRIPT_DIR}/fuzz/json.dict" \ -artifact_prefix="${OUT_DIR}/fuzz-artifacts/${target}/" \ "${corpus_dir}" "${SCRIPT_DIR}/fuzz/corpus/${target}" diff --git a/cmake/DiscoverTests.cmake b/cmake/DiscoverTests.cmake new file mode 100644 index 0000000..7ee9af7 --- /dev/null +++ b/cmake/DiscoverTests.cmake @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +cmake_policy(SET CMP0057 NEW) + +if(NOT DEFINED PJSON_TEST_EXECUTABLE OR NOT DEFINED PJSON_TEST_OUTPUT) + message(FATAL_ERROR "PJSON_TEST_EXECUTABLE and PJSON_TEST_OUTPUT are required") +endif() + +execute_process( + COMMAND "${PJSON_TEST_EXECUTABLE}" --list-tests + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ERROR_VARIABLE error + OUTPUT_STRIP_TRAILING_WHITESPACE) +if(NOT result EQUAL 0) + message(FATAL_ERROR "pjsontest discovery failed (${result}): ${error}") +endif() + +string(REPLACE "\r\n" "\n" output "${output}") +string(REPLACE "\n" ";" tests "${output}") +set(content "# Generated from the compiled pjsontest registry.\n") +set(seen) +foreach(test_name IN LISTS tests) + if(test_name STREQUAL "") + continue() + endif() + if(NOT test_name MATCHES "^[A-Za-z0-9_]+$") + message(FATAL_ERROR "Invalid registered pjson test name: ${test_name}") + endif() + if(test_name IN_LIST seen) + message(FATAL_ERROR "Duplicate registered pjson test name: ${test_name}") + endif() + list(APPEND seen "${test_name}") + string(APPEND content + "add_test([=[pjson.${test_name}]=] [=[${PJSON_TEST_EXECUTABLE}]=] --run-test [=[${test_name}]=])\n") +endforeach() + +list(LENGTH seen count) +if(count EQUAL 0) + message(FATAL_ERROR "The compiled pjsontest registry is empty") +endif() + +file(WRITE "${PJSON_TEST_OUTPUT}.tmp" "${content}") +file(RENAME "${PJSON_TEST_OUTPUT}.tmp" "${PJSON_TEST_OUTPUT}") +message(STATUS "Discovered ${count} pjson test cases from the compiled registry") diff --git a/cmake/RunInstallConsumer.cmake b/cmake/RunInstallConsumer.cmake index 1c8c916..6663d48 100644 --- a/cmake/RunInstallConsumer.cmake +++ b/cmake/RunInstallConsumer.cmake @@ -144,8 +144,23 @@ endforeach() if(NOT EXISTS "${relocated_prefix}/include/pjson.h") message(FATAL_ERROR "Installed package is missing include/pjson.h") endif() +if(NOT EXISTS "${relocated_prefix}/include/pjson_parser.h") + message(FATAL_ERROR "Installed package is missing include/pjson_parser.h") +endif() +if(NOT EXISTS "${relocated_prefix}/include/pjson_schema.h") + message(FATAL_ERROR "Installed package is missing include/pjson_schema.h") +endif() list(GET pc_files 0 pc_file) get_filename_component(pc_dir "${pc_file}" DIRECTORY) +file(READ "${pc_file}" pc_contents) +string(FIND "${pc_contents}" "-DPJSON_SHARED" pc_shared_definition) +if(DEFINED PJSON_BUILD_SHARED_LIBS AND PJSON_BUILD_SHARED_LIBS) + if(pc_shared_definition EQUAL -1) + message(FATAL_ERROR "Shared pkg-config metadata does not define PJSON_SHARED") + endif() +elseif(NOT pc_shared_definition EQUAL -1) + message(FATAL_ERROR "Static pkg-config metadata unexpectedly defines PJSON_SHARED") +endif() file(RELATIVE_PATH pc_dir_from_prefix "${relocated_prefix}" "${pc_dir}") set(expected_pc_prefix ".") cmake_path(RELATIVE_PATH expected_pc_prefix @@ -205,7 +220,7 @@ if(PJSON_PKG_CONFIG_EXECUTABLE) "${CMAKE_COMMAND}" -E env "PKG_CONFIG_PATH=${pc_dir}" "PKG_CONFIG_LIBDIR=${pc_dir}" - "${PJSON_PKG_CONFIG_EXECUTABLE}" --exact-version=1.0.0 pjson) + "${PJSON_PKG_CONFIG_EXECUTABLE}" --exact-version=2.0.0 pjson) set(pkgconfig_consumer_configure "${CMAKE_COMMAND}" -E env diff --git a/cmake/pjson.pc.in b/cmake/pjson.pc.in index 59c7df3..cf36507 100644 --- a/cmake/pjson.pc.in +++ b/cmake/pjson.pc.in @@ -11,4 +11,4 @@ Name: pjson Description: @PROJECT_DESCRIPTION@ Version: @PROJECT_VERSION@ Libs: -L${libdir} -lpjson -Cflags: -I${includedir} +Cflags: -I${includedir} @PJSON_PC_COMPILE_FLAGS@ diff --git a/conanfile.py b/conanfile.py index ddcc51c..fa5b291 100644 --- a/conanfile.py +++ b/conanfile.py @@ -14,7 +14,7 @@ # pkg-config metadata installed by pjsonlib/CMakeLists.txt. class PjsonConan(ConanFile): name = "pjson" - version = "1.0.0" + version = "2.0.0" package_type = "library" license = "Apache-2.0" @@ -100,3 +100,5 @@ def package_info(self): self.cpp_info.set_property("cmake_target_name", "pjson::pjson") self.cpp_info.set_property("pkg_config_name", "pjson") self.cpp_info.libs = ["pjson"] + if self.options.shared: + self.cpp_info.defines.append("PJSON_SHARED") diff --git a/docs/00-what-is-json.md b/docs/00-what-is-json.md index 56e40e6..b08d9d7 100644 --- a/docs/00-what-is-json.md +++ b/docs/00-what-is-json.md @@ -159,16 +159,16 @@ pjson mirrors this model with a single C++ class, `ByteDance::pjson`. One | number | `jsonNumberInt` or `jsonNumberDouble` | `int64_t` (whole numbers) or `double` | | boolean | `jsonBoolean` | `bool` | | array | `jsonArray` | list of `pjson` | -| object | `jsonObject` | map of `string -> pjson` | +| object | `jsonObject` | unique `string -> pjson` members | Two pjson-specific details worth knowing early: - **Numbers split into two types.** A whole number like `42` is kept as a 64-bit integer; anything with a fraction or exponent like `3.14` is kept as a `double`. Read either representation with the matching `tryGet()` overload. -- **Objects keep keys sorted.** pjson stores object keys in alphabetical order - (it uses a `std::map`), so when you print a document the keys come out sorted, - not in the order you added them. This keeps output predictable. +- **Object member order is unspecified.** pjson stores members in a private hash + table for fast lookup. `keys()`, callback traversal, and serialization expose + its unspecified native order; JSON objects are unordered collections. ## What you learned @@ -176,7 +176,7 @@ Two pjson-specific details worth knowing early: - A JSON value is one of: string, number, boolean, null, array, or object. - Values nest freely, which lets JSON describe complex data. - pjson represents any JSON value with one class, `pjson`, storing whole numbers - as `int64` and other numbers as `double`, and keeping object keys sorted. + as `int64` and other numbers as `double`, with unspecified object-member order. Next: [Chapter 01 — Getting started](01-getting-started.md), where you compile and run your first pjson program. diff --git a/docs/01-getting-started.md b/docs/01-getting-started.md index d493998..536869d 100644 --- a/docs/01-getting-started.md +++ b/docs/01-getting-started.md @@ -7,7 +7,8 @@ end you will have printed a JSON value to the screen. - A C++ compiler that supports **C++11** or newer (g++, clang, or MSVC). - pjson's canonical public header, `pjsonlib/include/pjson.h`. -- pjson's canonical implementation, `pjsonlib/src/pjson.cpp`. +- pjson's DOM and serialization sources, `pjsonlib/src/pjson.cpp` and + `pjsonlib/src/pjson_serialize.cpp`, plus the vendored Ryu conversion source. That's it. pjson has **no third-party dependencies**. @@ -56,14 +57,19 @@ The fastest way, compiling the library source directly alongside your program: ```sh c++ -std=c++11 -I pjsonlib/include \ - pjsonlib/src/pjson.cpp examples/src/01_hello_world.cpp \ + -I pjsonlib/src/third_party/ryu \ + pjsonlib/src/pjson.cpp pjsonlib/src/pjson_serialize.cpp \ + pjsonlib/src/third_party/ryu/ryu/d2s.c \ + examples/src/01_hello_world.cpp \ -o hello ./hello ``` - `-std=c++11` selects the C++ standard. - `-I pjsonlib/include` tells the compiler where to find `pjson.h`. -- We list **both** `.cpp` files so the library code is compiled in. +- We list the DOM, serializer, and Ryu sources used by this example. The CMake + target shown in Chapter 08 is preferable once parsing or other components are + needed because it maintains the complete source list. Expected output: @@ -77,11 +83,11 @@ Expected output: Notice the compact form is one line, and the pretty form is indented. Also note the keys came out in the order `message`, then `year` — which happens to be -alphabetical. (Recall from Chapter 00 that pjson keeps object keys sorted.) +alphabetical. (Recall from Chapter 00 that pjson sorts object keys for output.) ```mermaid flowchart LR - src["your .cpp + pjson.cpp"] -->|"c++ -std=c++11 -I include"| exe["executable"] + src["your .cpp + DOM + serializer + Ryu"] -->|"c++ -std=c++11 -I include"| exe["executable"] exe -->|run| out["JSON printed"] ``` @@ -99,19 +105,19 @@ with the bundled script — covered fully in - **`fatal error: 'pjson.h' file not found`** — you forgot `-I pjsonlib/include` (or the path to wherever you put the header). -- **`undefined reference to ByteDance::pjson::...`** — you compiled only your - `.cpp` and forgot to include `pjsonlib/src/pjson.cpp` in the command. +- **`undefined reference to ByteDance::pjson::...`** — a required library + translation unit was omitted; prefer linking the `pjson::pjson` CMake target. - **Lots of syntax errors** — your compiler may be defaulting to an old standard; add `-std=c++11` (or newer). ## What you learned -- pjson's canonical public header and implementation source have no third-party - dependencies. +- pjson has no dependencies that applications must install separately; its Ryu + number-conversion dependency is vendored and built by the CMake target. - A new `pjson` is `null`; assigning to a key makes it an object. - `SerializeOptions` selects compact or pretty JSON serialization. -- Compile by passing both your file and `pjson.cpp`, with `-I` pointing at the - header. +- Direct compilation must include the component sources used by the program; + the CMake target supplies the complete list automatically. Next: [Chapter 02 — Creating JSON](02-creating-json.md), where you build richer objects and arrays. diff --git a/docs/02-creating-json.md b/docs/02-creating-json.md index 20454fc..d10a3eb 100644 --- a/docs/02-creating-json.md +++ b/docs/02-creating-json.md @@ -47,7 +47,7 @@ person["address"]["zip"] = "N1"; The first `person["address"]` creates an empty object, and `["city"]` adds a key inside it. -## Numbers: `int64_t` and `double` +## Numbers pjson keeps whole numbers as 64-bit integers and everything else as `double`: @@ -57,8 +57,9 @@ person["score"] = double(4.5); // jsonNumberDouble person["big"] = int64_t(9000000000); // jsonNumberInt ``` -Use these exact-width APIs deliberately: `int64_t` represents whole JSON -numbers and `double` represents fractional or exponent-form values. +All standard non-character integer types and `float`, `double`, and `long double` are accepted +for convenient builder syntax. Values are stored as signed or unsigned 64-bit +integers, or as `double`; a `long double` is therefore narrowed explicitly. ## Strings @@ -83,8 +84,8 @@ The most direct way to make an array: person["scores"] = std::vector({90, 82, 77}); ``` -Vectors of `int64_t`, `double`, `bool`, and `std::string` are supported. Build -arrays of other value types element by element. +Vectors of the supported non-character numeric types, `bool`, and `std::string` +are supported. Build arrays of other value types element by element. ### 2. By index @@ -145,25 +146,23 @@ pjson::SerializeOptions options = pjson::SerializeOptions::prettyPrinted(); options.indentWidth = 2; options.indentCharacter = ' '; // a space or tab options.escapeNonAscii = false; // keep UTF-8 instead of \u escapes -options.keyOrder = pjson::SerializeOptions::AscendingKeys; options.maxOutputBytes = size_t(64) * 1024 * 1024; std::string text = person.toString(options); ``` A default-constructed `SerializeOptions` produces compact output. Its other -defaults are a two-space indent, raw non-ASCII UTF-8, ascending keys, and a +defaults are a two-space indent, raw non-ASCII UTF-8, and a 64 MiB output limit. Set `maxOutputBytes = 0` only when explicitly requesting -unlimited output. Set -`keyOrder` to `DescendingKeys` to reverse object-key output. Only space and tab -are valid indentation characters; another value falls back to space. Stored +unlimited output. Only space and tab are valid indentation characters; another +value falls back to space. Stored strings must contain valid UTF-8: `toString()` throws `std::invalid_argument` for invalid bytes, while `write()` sets the destination stream's failure state. Crossing the output limit or overflowing indentation arithmetic instead throws `std::length_error` from `toString()` or sets `failbit` from `write()`. These logical failures are detected before `write()` emits bytes. Double formatting -is locale-independent and uses 15–17 -significant digits for stable round-tripping; integral-looking doubles keep a +is locale-independent and uses pinned Ryu shortest-round-trip conversion; +integral-looking doubles keep a decimal marker so reparsing preserves their storage kind. Running the example produces (abridged): @@ -194,9 +193,8 @@ Running the example produces (abridged): } ``` -Remember: object insertion order is not retained. Keys print in ascending -bytewise string order by default (or descending order when requested), and the -skipped array index shows up as `null`. +Remember: object insertion and output order are unspecified, and the skipped +array index shows up as `null`. ## What you learned @@ -207,7 +205,7 @@ skipped array index shows up as `null`. - Whole numbers are stored as int64, other numbers as double; strings are auto-escaped on output. - `SerializeOptions` controls pretty layout, indentation, non-ASCII escaping, - and ascending or descending key order. + non-finite values, and output limits. Next: [Chapter 03 — Parsing & reading](03-parsing-and-reading.md), where you go the other direction: text into data, and reading it back safely. diff --git a/docs/03-parsing-and-reading.md b/docs/03-parsing-and-reading.md index fb0dcfe..75e2fe8 100644 --- a/docs/03-parsing-and-reading.md +++ b/docs/03-parsing-and-reading.md @@ -6,25 +6,33 @@ it into a `pjson` you can read. Follow along with ## Parsing with `parse()` -`pjson::parse()` takes JSON text and returns a `pjson::unique_ptr`: +Include the parser separately from the DOM, then construct a parser and call +`parse()`. The dependency is one-way: `pJsonParser` uses `pjson`; `pjson` does +not depend on the parser. ```cpp -auto doc = pjson::parse(R"({ "name": "Ada", "age": 36 })"); -if (!doc) { +#include "pjson.h" +#include "pjson_parser.h" + +pJsonParser parser; +pJsonParser::Error err; +pjson doc = parser.parse(R"({ "name": "Ada", "age": 36 })", err); +if (!err.ok) { // parsing failed — the text was not valid JSON } ``` Two things to understand: -- **`pjson::unique_ptr`** is a smart pointer that automatically frees the value - when it goes out of scope. You never call `delete`. Use `*doc` to get the - `pjson`, or `doc->method()` to call methods. Its deleter preserves allocator - provenance, so every DOM parse overload uses the same ownership type. -- On a JSON or DOM-allocation **failure** the pointer is empty (`!doc` is true); - malformed input does not escape as an exception. Stream objects configured to - throw can still propagate I/O exceptions from `parseStream()`. (Chapter 05 - shows how to find out why JSON parsing failed.) +- **`parse()` returns a `pjson` by value** that owns its subtree and frees it + when it goes out of scope. You never call `delete`, and there is no smart + pointer in the API. Use `doc.method()` directly. To move the result into + another document, `dest["k"] = std::move(doc);`. +- On a JSON failure the terse `parse(text)` returns a JSON `null` value; pass a + `pJsonParser::Error` (as above) to tell failure apart from a successfully parsed + literal `null`. Malformed input does not escape as an exception. Stream + objects configured to throw can still propagate I/O exceptions from + `parseStream()`. (Chapter 05 shows how to find out why JSON parsing failed.) > `R"(...)"` is a C++ *raw string literal*. Inside it, quotes and backslashes > are literal, so you can paste JSON without escaping every `"`. Very handy. @@ -36,7 +44,7 @@ when the value has the requested type and leaves the output unchanged on failure: ```cpp -const pjson& j = *doc; +const pjson& j = doc; int64_t age = 0; if (!j.tryGet("age", age)) { @@ -157,7 +165,7 @@ or mutating internal storage: ```cpp if (const pjson* node = j.find("scores")) { for (size_t i = 0; node->isArray() && i < node->size(); ++i) { - const pjson* score = node->find(static_cast(i)); + const pjson* score = node->findIndex(i); int64_t value = 0; if (score && score->tryGet(value)) std::cout << value << " "; @@ -192,7 +200,7 @@ Combine iteration with per-element lookup: if (const pjson* friends = j.find("friends")) { if (friends->isArray()) { for (size_t i = 0; i < friends->size(); ++i) { - const pjson* friend_ = friends->find(static_cast(i)); + const pjson* friend_ = friends->findIndex(i); pjson::StringView name; if (friend_ && friend_->tryGet("name", name)) std::cout.write(name.data(), static_cast(name.size())); @@ -226,7 +234,7 @@ for any failure. ## Iterating an object -To iterate an object's keys, use `keys()` (returned sorted): +To iterate an object's keys, use `keys()`. The returned order is unspecified: ```cpp for (const std::string& key : j.keys()) { @@ -241,8 +249,8 @@ for (const std::string& key : j.keys()) { ## What you learned -- `parse()` returns a `pjson::unique_ptr` and reports JSON/DOM-allocation failures with - an empty result. +- `parse()` returns a `pjson` value and reports failures through a `pJsonParser::Error` + out-param (the terse overload yields a JSON `null` on failure). - `tryGet()` provides exact-type node/key/index reads and leaves outputs unchanged on failure; `StringView` offers a mutation-sensitive, copy-free string view. - `find`, `findPointer`, `hasKey`, and `hasIndex` inspect without creating; use diff --git a/docs/04-editing.md b/docs/04-editing.md index 1d96d4a..1fda914 100644 --- a/docs/04-editing.md +++ b/docs/04-editing.md @@ -9,8 +9,8 @@ Follow along with [`examples/src/04_editing.cpp`](../examples/src/04_editing.cpp Index to the value and assign a new one: ```cpp -auto doc = pjson::parse(R"({ "user": { "name": "Ada" }, "count": 2 })"); -pjson& j = *doc; +pJsonParser::Error err; +pjson j = pJsonParser().parse(R"({ "user": { "name": "Ada" }, "count": 2 })", err); j["user"]["name"] = "Ada Lovelace"; // change a string j["count"] = int64_t(3); // change a number @@ -84,8 +84,9 @@ if (a.canSwap(b)) a.swap(b); // exchange the two sub-trees in place ``` -Sibling nodes in the same document are compatible. Check `canSwap()` when the -values come from different sources. +Sibling nodes in the same document are compatible. Ancestor/descendant pairs +are rejected to prevent ownership cycles. Check `canSwap()` whenever the +relationship is not obvious. ## Editing a path atomically @@ -94,14 +95,15 @@ For a sequence of path-based edits, `applyPatch()` implements JSON Patch (RFC `test` operations: ```cpp -auto patch = pjson::parse(R"([ +pjson patch = pJsonParser().parse(R"([ { "op": "replace", "path": "/user/name", "value": "Ada Byron" }, { "op": "add", "path": "/user/roles/-", "value": "reviewer" } -])"); +])", + err); pjson::PatchError error; pjson::PatchOptions limits; -if (!patch || !j.applyPatch(*patch, error, limits)) { +if (!j.applyPatch(patch, error, limits)) { std::cerr << "patch operation " << error.opIndex << ": " << error.message << "\n"; } @@ -111,8 +113,8 @@ Patch paths use JSON Pointer syntax. An empty path addresses the whole document; in particular, removing the root succeeds and leaves the target as JSON null: ```cpp -auto removeRoot = pjson::parse(R"([{"op":"remove","path":""}])"); -if (removeRoot && j.applyPatch(*removeRoot, error, limits)) { +pjson removeRoot = pJsonParser().parse(R"([{"op":"remove","path":""}])", err); +if (err.ok && j.applyPatch(removeRoot, error, limits)) { // j.isNull() is now true } ``` @@ -125,11 +127,12 @@ For object-shaped updates, `applyMergePatch()` implements JSON Merge Patch (RFC 7396): ```cpp -auto merge = pjson::parse(R"({ +pjson merge = pJsonParser().parse(R"({ "user": { "email": "ada@example.com", "nickname": null } -})"); +})", + err); -if (merge && !j.applyMergePatch(*merge, error, limits)) { +if (!j.applyMergePatch(merge, error, limits)) { std::cerr << error.message << "\n"; } ``` @@ -208,6 +211,9 @@ if (const pjson* user = j.find("user")) j = *user; // replacing a root from its own child is safe ``` +Rvalue `pushBack` and `insertOrAssign` likewise snapshot an aliased ancestor or +sibling rather than consuming storage that the destination still owns. + ## What you learned - `operator[]` returns a mutable reference, so editing is just assignment. diff --git a/docs/05-parsing-and-errors.md b/docs/05-parsing-and-errors.md index d8b8983..bc29ea9 100644 --- a/docs/05-parsing-and-errors.md +++ b/docs/05-parsing-and-errors.md @@ -1,16 +1,16 @@ # Chapter 05 — Parsing, resource limits & errors -pjson parses **RFC 8259 JSON**. This chapter shows the resource limits -that keep hostile documents from exhausting memory or the call stack, the -independent duplicate-key policy, and structured diagnostics. Follow along with +`pJsonParser` parses **RFC 8259 JSON** into `pjson` values. This chapter shows +the resource limits that keep hostile documents from exhausting memory or the +call stack, the independent duplicate-key policy, and structured diagnostics. Follow along with [`examples/src/05_parsing_and_errors.cpp`](../examples/src/05_parsing_and_errors.cpp). ## Parse options -Every `parse()` call can take a `pjson::ParseOptions`: +Configure a reusable parser with `pJsonParser::Options`: ```cpp -struct ParseOptions { +struct pJsonParser::Options { int maxDepth; // default 512 size_t maxNodes; // default 1,000,000; 0 means unlimited size_t maxInputBytes; // default 64 MiB; 0 means unlimited @@ -19,9 +19,11 @@ struct ParseOptions { ``` ```cpp -pjson::ParseOptions opt; +pJsonParser::Options opt; opt.maxNodes = 100000; -auto doc = pjson::parse(text, opt); +pJsonParser parser(opt); +pJsonParser::Error err; +pjson doc = parser.parse(text, err); ``` ## JSON syntax and duplicate keys @@ -46,33 +48,36 @@ raw tab: FAILED at byte 2 (unescaped control character in string) The default also rejects duplicate object keys. Independently choose `RejectDuplicateKeys`, `KeepFirstDuplicate`, or `KeepLastDuplicate` through -`ParseOptions::duplicateKeys`. This changes only duplicate handling; it never +`pJsonParser::Options::duplicateKeys`. This changes only duplicate handling; it never relaxes RFC 8259 syntax. ## Resource limits `maxDepth` caps how deeply values may nest. This is a safety valve: without it, a maliciously deep document (thousands of nested `[`s) could exhaust the call -stack and crash your program. The default of 512 is generous for real data. -`maxNodes` separately caps the number of materialized JSON values, blocking -wide flat inputs from amplifying into millions of heap allocations. +stack and crash your program. The default of 512 is generous for real data, and +any configured value is clamped to a stack-safe hard ceiling (1024) that cannot +be exceeded. `maxNodes` separately caps the number of materialized JSON values, +blocking wide flat inputs from amplifying into millions of heap allocations. `maxInputBytes` rejects oversized buffers before parsing begins. ```cpp -pjson::ParseOptions shallow; +pJsonParser::Options shallow; shallow.maxDepth = 3; -auto d = pjson::parse("[[[[1]]]]", shallow); // fails: too deep +pJsonParser::Error err; +pjson d = pJsonParser(shallow).parse("[[[[1]]]]", err); // fails: too deep ``` ## Getting the error details -Pass a `pjson::ParseError` to learn what went wrong. Reporting APIs reset every -field on entry: success leaves `ok == true`, offset `0`, line `1`, column `1`, -and an empty message; failure describes the first problem. +Pass a `pJsonParser::Error` to learn what went wrong. Reporting APIs reset every +field on entry: success leaves `ok == true`, `code == None`, offset `0`, line +`1`, column `1`, and an empty message; failure describes the first problem. ```cpp -struct ParseError { +struct pJsonParser::Error { bool ok; // true if parsing succeeded + Code code; // stable machine-facing category (None on success) size_t offset; // byte index where the problem was found size_t line; // one-based source line size_t column; // one-based byte column @@ -80,27 +85,36 @@ struct ParseError { }; ``` +`code` is a stable enum (`Syntax`, `InvalidEncoding`, `DuplicateKey`, +`NumberRange`, `DepthLimit`, `InputLimit`, `NodeLimit`, `AllocationFailure`, +`StreamError`, `CallbackError`, `InvalidArgument`) suitable for programmatic +branching; the `message` text may change between releases. + ```cpp -pjson::ParseError err; -auto doc = pjson::parse("[1, 2, ]", err); -if (!doc) { +pJsonParser::Error err; +pjson doc = pJsonParser().parse("[1, 2, ]", err); +if (!err.ok) { std::cerr << "parse failed at " << err.line << ':' << err.column << " (byte " << err.offset << "): " << err.message << "\n"; } ``` -You can combine both: `parse(text, err, opt)`. +The parser retains its options, so reuse `parser.parse(text, err)` for multiple +inputs. Because a failed parse returns a +JSON `null` value, always test `err.ok` (not the value) when the input might +legitimately be `null`. The same options and error coordinates apply to `parseSax()` and the incremental -`parseSaxStream()` API. SAX callback cancellation and callback exceptions are -converted into an ordinary parse failure rather than escaping. Streaming avoids +`parseSaxStream()` API. SAX callback cancellation and ordinary callback exceptions +become `CallbackError`; `std::bad_alloc` becomes `AllocationFailure`. Input-stream +failures become `StreamError`. None escapes the SAX boundary. Streaming avoids buffering the complete document, but current tokens, nesting state, duplicate-key tracking, and handler-owned state still consume memory. ## Why not exceptions? pjson does not throw JSON-specific parse exceptions. In-memory JSON and -DOM-allocation failures produce an empty pointer plus optional `ParseError`; SAX +DOM-allocation failures produce a null `pjson` value plus optional `pJsonParser::Error`; SAX handler failures similarly become `false`. An exception-enabled input stream can still throw while `parseStream()` buffers bytes, and mutating APIs that allocate may report `std::bad_alloc` unless declared `noexcept`. @@ -110,7 +124,7 @@ may report `std::bad_alloc` unless declared `noexcept`. - All parsing follows RFC 8259 syntax; duplicate handling is a separate policy. - `maxDepth`, `maxNodes`, and `maxInputBytes` bound stack and memory use. -- `ParseError{ ok, offset, line, column, message }` tells you where and why +- `pJsonParser::Error{ ok, offset, line, column, message }` tells you where and why parsing failed, without exceptions. Next: [Chapter 06 — Schema validation](06-schema-validation.md), where you check diff --git a/docs/06-schema-validation.md b/docs/06-schema-validation.md index 91c783d..ea52129 100644 --- a/docs/06-schema-validation.md +++ b/docs/06-schema-validation.md @@ -9,31 +9,44 @@ ranges. That is what **schema validation** is for. Follow along with A **schema** is a description of what valid data looks like: "must be an object, must have a `name` string and a non-negative `age` integer", and so on. In -pjson, a schema is *itself a JSON value* (a `pjson`), written with pjson's -documented subset of the widely-used -[JSON Schema](https://json-schema.org) vocabulary. It is not a claim of complete -conformance to a JSON Schema draft. Schemas load, build, and round-trip exactly -like any other pjson value. +pjson, a schema is *itself a JSON value* (a `pjson`). Default options use +pjson's documented subset; `Options::draft2020()` opts into the required +[JSON Schema Draft 2020-12](https://json-schema.org/draft/2020-12/) +vocabularies. The latter does not claim the complete optional format-assertion +vocabulary, arbitrary-precision numbers, or support for other drafts. Schemas +load, build, and round-trip exactly like any other pjson value. + +Validation itself lives in a separate helper class, +`ByteDance::pJsonSchemaValidator`, declared in ``. It is a pure +consumer of pjson's public API: the core `pjson` class carries no schema or +regex state, and the implementation is isolated in focused private translation units. You +compile a schema into a validator once and reuse it to check many instances. ```mermaid flowchart LR - data["data (pjson)"] --> V{validate} - schema["schema (pjson)"] --> V + schema["schema (pjson)"] --> C["pJsonSchemaValidator(schema)"] + data["data (pjson)"] --> V{validator.validate} + C --> V V -->|conforms| OK["true, no errors"] - V -->|violates| ERR["false + list of SchemaError"] + V -->|violates| ERR["false + list of Error"] ``` ## A first schema ```cpp -auto schema = pjson::parse(R"({ +#include "pjson_parser.h" +#include "pjson_schema.h" + +pJsonParser::Error err; +pjson schema = pJsonParser().parse(R"({ "type": "object", "required": ["name", "age"], "properties": { "name": { "type": "string", "minLength": 1 }, "age": { "type": "integer", "minimum": 0, "maximum": 150 } } -})"); +})", + err); ``` Read it in English: *the value must be an object; it must have `name` and `age`; @@ -41,22 +54,62 @@ Read it in English: *the value must be an object; it must have `name` and `age`; ## Validating +Build a validator from the schema, then validate instances against it: + ```cpp -auto data = pjson::parse(R"({ "name": "Ada", "age": 36 })"); +pJsonSchemaValidator validator(schema); +if (!validator.isSchemaValid()) { + for (const pJsonSchemaValidator::Error& e : validator.schemaErrors()) + std::cerr << "invalid schema at " << e.schemaLocation << ": " << e.message << "\n"; +} + +pjson data = pJsonParser().parse(R"({ "name": "Ada", "age": 36 })", err); // Simple yes/no: -bool ok = data->validate(*schema); +bool ok = validator.validate(data); +``` + +The validator deep-copies the schema on construction, so the original `schema` +value may change or be destroyed afterward. A single validator can check any +number of instances and is cheap to reuse. + +## Dialect and vocabulary contract + +pjson retains one named dialect for backward-compatible subset behavior and an +explicit opt-in Draft 2020-12 mode: + +```cpp +const char* dialect = pJsonSchemaValidator::documentedSubsetDialectUri(); +const char* vocabulary = pJsonSchemaValidator::documentedSubsetVocabularyUri(); +pJsonSchemaValidator::Options draft2020 = + pJsonSchemaValidator::Options::draft2020(); ``` -To learn *what* failed, pass a vector — pjson normally collects every applicable -failure instead of stopping at the first (a resource-budget failure stops the -traversal): +Default options require the subset dialect. `Options::draft2020()` accepts the +official Draft 2020-12 URI, validates schemas against bundled standard +meta-schemas, enables modern `$ref` sibling behavior, and applies vocabularies +per schema resource. Custom meta-schema URIs require an application resolver and +are accepted only when `resolveCustomDialects` is enabled (as in the preset). + +Under this subset dialect, `$vocabulary` is an object mapping vocabulary URIs +to booleans. The pjson subset vocabulary may be required (`true`); unknown +optional vocabularies (`false`) are accepted as annotations; unknown required +vocabularies fail schema compilation. Malformed `$schema`/`$vocabulary` shapes +also fail compilation. `schemaErrors()` reports these failures with +`Error::SchemaCompilation`; instance failures use `Error::InstanceValidation`. +All local and external references are indexed and resolved while the validator +is constructed. The resolver context is not retained, and repeated or concurrent +`validate()` calls perform no resolver I/O or cache mutation. + +To learn *what* failed, pass a vector — the validator normally collects every +applicable failure instead of stopping at the first (a resource-budget failure +stops the traversal): ```cpp -std::vector errors; -if (!data->validate(*schema, errors)) { - for (const pjson::SchemaError& e : errors) { - std::cout << (e.path.empty() ? "(root)" : e.path) +std::vector errors; +if (!validator.validate(data, errors)) { + for (const pJsonSchemaValidator::Error& e : errors) { + std::cout << (e.instanceLocation.empty() ? "(root)" : e.instanceLocation) << ": " << e.message << "\n"; } } @@ -67,9 +120,14 @@ when old results are not wanted. Normally all applicable failures are collected; reaching a validation-depth or reference-resolution budget stops that traversal safely. -Each `SchemaError` has a `path` (a **JSON Pointer** like `/age` or -`/friends/2/name`, empty for the document root) and a `message`. From the -example, an all-bad document reports: +Each `pJsonSchemaValidator::Error` has a stable `code`, an `instanceLocation` +(a **JSON Pointer** like `/age` or `/friends/2/name`, empty for the document +root), a `schemaLocation`, a triggering `keyword`, a `message`, and optional +nested `causes`. `category` distinguishes instance failures from +schema-compilation failures. Set `Options::stopAfterFirstError` to stop after +one public failure or `Options::collectNestedCauses` to retain bounded branch +failures under `anyOf` and zero-match `oneOf` errors. From the example, an +all-bad document reports: ``` /age: value 200.0 is above maximum 150.0 @@ -84,17 +142,20 @@ into arrays (`/roles/0`). ## Supported keywords -pjson implements the documented keyword subset below. Unknown and unsupported -keywords are **ignored, not enforced**. This permits annotations and future -vocabulary to pass through, but it also means a misspelled or unsupported +pjson implements the keywords below. In the default subset, unknown and +unsupported keywords are **ignored, not enforced**. This permits annotations and +future vocabulary to pass through, but it also means a misspelled or unsupported constraint can silently weaken validation. Treat this table as an allowlist and -test both accepted and rejected instances for every application schema. +test both accepted and rejected instances for every application schema, or use +`pJsonSchemaValidator::Options::strict()` to reject unsupported standard keywords +outright. | Applies to | Keywords and forms | |------------|--------------------| -| any value | `type`, `enum`, `const`, local-fragment `$ref` | -| objects | `properties`, `patternProperties`, `propertyNames`, `required`, `dependentRequired`, `dependencies`, `additionalProperties` (boolean or schema), `minProperties`, `maxProperties` | -| arrays | single-schema or tuple-array `items`, plus `minItems`, `maxItems`, `uniqueItems` | +| any value | `type`, `enum`, `const`, `$ref`, `$dynamicRef`, `$id`, `$anchor`, `$dynamicAnchor` | +| conditional| `if`, `then`, `else` | +| objects | `properties`, `patternProperties`, `propertyNames`, `required`, `dependentRequired`, `dependencies`, `dependentSchemas`, `additionalProperties`, `unevaluatedProperties`, `minProperties`, `maxProperties` | +| arrays | single-schema `items`, tuple `prefixItems` (and legacy tuple-array `items`), `contains`, `minContains`, `maxContains`, `unevaluatedItems`, `minItems`, `maxItems`, `uniqueItems` | | numbers | `minimum`, `maximum`, numeric `exclusiveMinimum`, numeric `exclusiveMaximum`, `multipleOf` | | strings | `minLength`, `maxLength`, `pattern` (ECMAScript regex), `format` | | combinators| `allOf`, `anyOf`, `oneOf`, `not` | @@ -102,62 +163,118 @@ test both accepted and rejected instances for every application schema. A few notes: - `type: "integer"` matches whole numbers (including `2.0`); `type: "number"` - matches any int or double. `type` may also be an **array** of allowed names, + matches signed integers, unsigned integers, and doubles. `type` may also be an + **array** of allowed names, e.g. `"type": ["string", "null"]`. - `enum` and `const` use deep equality, so they work for arrays and objects too. -- `$ref` resolves only a local URI fragment containing a JSON Pointer, such as - `#/$defs/address`; both `$defs` and `definitions` can hold referenced schemas. - Remote references are rejected, and siblings of `$ref` are ignored. +- `$ref` resolves local JSON Pointers and anchors against `$id` resource bases. + External document URIs are resolved only through an application-supplied + function pointer (`Options::resolver`); pjson never performs network I/O. + Resolution is bounded by reference, document, byte, work, and depth budgets. +- `$dynamicRef` and `$dynamicAnchor` follow dynamic scope across local and + explicitly resolved resources. `Options::modernSubset()` applies `$ref` + siblings as modern drafts require; the default preserves the former Draft 7 + replacement behavior for compatibility. +- `unevaluatedProperties` and `unevaluatedItems` consume successful evaluation + annotations propagated through references, conditionals, combinators, + `contains`, and the regular object/array applicators. + +An application that allows references to other schema documents supplies them +explicitly. The callback receives an absolute document URI (without a fragment), +fills the output value, and returns success; it must not fetch anything the +application's policy does not authorize: + +```cpp +bool resolveSchema(const std::string& uri, pjson& output, void* context) { + const SchemaStore& store = *static_cast(context); + return store.find(uri, output); // copy the matching schema into output +} + +pJsonSchemaValidator::Options options = + pJsonSchemaValidator::Options::modernSubset(); +options.retrievalUri = "https://example.test/schemas/root.json"; +options.resolver = resolveSchema; +options.resolverContext = &store; +pJsonSchemaValidator validator(schema, options); +``` + +The callback runs only during construction. Returned documents are copied into +validator-owned storage, and the callback/context pointers are then cleared. - `patternProperties` applies schemas to matching keys, `propertyNames` checks each key, and `dependentRequired`/`dependencies` express rules triggered by the presence of another property. -- Known string formats are `date`, `time`, `date-time`, `ipv4`, `ipv6`, and - `uuid`. They are checked by default; unknown format names are ignored. +- Known string formats are `date`, `time`, `date-time`, `ipv4`, `ipv6`, `uuid`, + and `regex`. They are checked by the normal/default options; + `Options::modernSubset()` follows Draft 2020-12 and treats them as annotations + unless `validateFormats` is explicitly re-enabled. Unknown names are ignored. - A **boolean schema** is allowed: `true` accepts everything, `false` rejects everything (handy as a sub-schema, e.g. `"additionalProperties": false`). -- `pattern` uses `std::regex` ECMAScript syntax with search semantics. Default - `SchemaOptions` bound pattern and subject byte sizes and reject expressions - disallowed by the regex safety policy. Applications that fully trust both - schemas and instances may opt out with - `pjson::SchemaOptions::trustedRegex()`. - -The supported vocabulary is deliberately a subset. Tuple-form `items` validates +- `pattern` uses a private Unicode-aware ECMAScript engine with search semantics, + including Unicode property escapes and non-BMP code points. Default options + bound pattern and subject byte sizes and reject expressions disallowed by the + regex safety policy. The engine also has a finite internal work ceiling. + Applications that fully trust both schemas and instances may opt out of the + conservative syntax policy with + `pJsonSchemaValidator::Options::trustedRegex()`. + +The default vocabulary is deliberately a subset. Tuple-form `items` validates the corresponding array positions, but elements beyond the tuple remain unconstrained because `additionalItems` is not implemented. `minLength` and `maxLength` count Unicode code points, not UTF-8 bytes. Unknown keywords and -many malformed keyword forms are ignored, and pjson does not validate schemas -against a meta-schema. +malformed keyword forms are ignored in the default permissive mode. Strict mode +rejects malformed values for every supported keyword before instance validation. +Draft 2020 mode additionally validates against the selected standard or resolved +custom meta-schema. ## Validation options and resource budgets -`SchemaOptions` controls regex policy, traversal budgets, and format checking: +`pJsonSchemaValidator::Options` controls regex policy, traversal budgets, and +format checking. Pass it when constructing the validator: ```cpp -pjson::SchemaOptions options; +pJsonSchemaValidator::Options options; options.maxRegexPatternBytes = 256; options.maxRegexSubjectBytes = 4096; options.allowUnsafeRegex = false; -options.maxValidationDepth = 64; +options.maxValidationDepth = 32; options.maxRefResolutions = 1024; options.maxValidationWork = 1000000; options.maxErrors = 100; +options.stopAfterFirstError = false; +options.collectNestedCauses = false; options.validateFormats = true; - -std::vector errors; -bool ok = data->validate(*schema, errors, options); +options.strictSubset = false; // set true to fail closed on unsupported keywords +options.refSiblings = false; // modernSubset() sets true and validateFormats false +options.retrievalUri.clear(); // set when a root schema with relative refs was retrieved by URI +options.defaultDialectUri = pJsonSchemaValidator::documentedSubsetDialectUri(); +options.resolver = nullptr; // no implicit external I/O +options.resolverContext = nullptr; +options.maxResolvedDocuments = 32; +options.maxResolvedBytes = size_t(16) * 1024 * 1024; + +pJsonSchemaValidator validator(schema, options); +std::vector errors; +bool ok = validator.validate(data, errors); ``` These are the defaults. A zero regex byte limit disables that individual regex limit and should be reserved for trusted input. Zero for the validation-depth, reference-resolution, work, or error-count budget retains that budget's documented hard ceiling rather than disabling it. -Validation depth has an absolute hard ceiling of 64; larger configured values -are clamped to 64 to bound native-stack use during recursive keyword evaluation. -`SchemaOptions::trustedRegex()` disables both regex byte limits and permits -unsafe regular expressions while retaining all other defaults. Set +Validation depth has an absolute hard ceiling of 32; larger configured values +are clamped to 32 to bound native-stack use during recursive keyword evaluation +on every supported platform, including Windows' smaller default thread stack. +`pJsonSchemaValidator::Options::trustedRegex()` disables both regex byte limits +and permits unsafe regular expressions while retaining all other defaults. Set `validateFormats = false` when known formats should act only as annotations. +Set `strictSubset = true` (or use `pJsonSchemaValidator::Options::strict()`) to +**fail closed**: a schema that uses a standard validation/applicator keyword +pjson does not implement (for example `contentSchema` or `$recursiveRef`) +or a malformed value for a supported keyword then makes schema compilation +fail instead of silently ignoring the constraint. Unknown non-standard +extension keywords are still allowed as annotations even in strict mode. -## Combinators (composing schemas) +## Combinators and conditionals (composing schemas) The logical keywords let you build up complex rules: @@ -165,6 +282,8 @@ The logical keywords let you build up complex rules: - `anyOf`: must satisfy **at least one**. - `oneOf`: must satisfy **exactly one**. - `not`: must **not** satisfy the sub-schema. +- `if` / `then` / `else`: when the value matches `if`, it must also satisfy + `then`; otherwise it must satisfy `else`. ```json { "anyOf": [ { "type": "string" }, { "type": "integer" } ] } @@ -189,15 +308,22 @@ schema["properties"]["age"]["minimum"] = int64_t(0); ## What you learned -- A schema is a `pjson` describing valid data with pjson's documented JSON - Schema keyword subset, not a complete draft implementation. -- `validate(schema)` returns yes/no; `validate(schema, errors)` collects **all** - failures, each with a JSON-Pointer `path` and a `message`. -- The subset includes local `$ref`, object constraints, known string formats, - and logical combinators. Unknown keywords are ignored and therefore enforce - no constraint. -- `SchemaOptions` bounds regex, validation depth, reference resolution, total - validation work, and collected errors, and can disable known-format checks. +- A schema is a `pjson`; default options use pjson's documented subset, while + `Options::draft2020()` implements the required Draft 2020-12 vocabularies. +- Validation lives in the standalone `pJsonSchemaValidator` (in + ``), a pure consumer of pjson's public API. Compile a schema + once, then reuse the validator for many instances. +- `validator.validate(data)` returns yes/no; `validator.validate(data, errors)` + collects **all** failures, each with JSON-Pointer `instanceLocation` and + `schemaLocation` fields plus a `message`. +- The implemented keyword set includes URI/anchor/dynamic references, + `unevaluated*`, object and + array constraints, known string formats, and logical combinators. External + resources are available only through an explicit resolver callback. Unknown + keywords are ignored and therefore enforce no constraint. +- `pJsonSchemaValidator::Options` bounds regex, validation depth, reference + resolution, total validation work, and collected errors, and can disable + known-format checks. Next: [Chapter 07 — Capstone: address book](07-capstone-address-book.md), where everything comes together in one small application. diff --git a/docs/07-capstone-address-book.md b/docs/07-capstone-address-book.md index e71c3c6..4c193b3 100644 --- a/docs/07-capstone-address-book.md +++ b/docs/07-capstone-address-book.md @@ -11,7 +11,11 @@ serializes the whole thing. ## 1. Define what a valid contact looks like ```cpp -pjson::unique_ptr schema = pjson::parse(R"({ +#include "pjson_parser.h" +#include "pjson_schema.h" + +pJsonParser::Error err; +pjson schema = pJsonParser().parse(R"({ "type": "object", "required": ["id", "name", "emails"], "properties": { @@ -21,9 +25,13 @@ pjson::unique_ptr schema = pjson::parse(R"({ "items": { "type": "string", "pattern": "@" } }, "tags": { "type": "array", "items": { "type": "string" } } } -})"); -if (!schema) +})", + err); +if (!err.ok) return 1; + +// Compile the schema once into a reusable validator. +pJsonSchemaValidator validator(schema); ``` Every contact must have a positive `id`, a non-empty `name`, and at least one @@ -32,26 +40,24 @@ email address containing `@`. ## 2. A gatekeeper that validates before storing ```cpp -bool addContact(pjson& book, const pjson& schema, const pjson& contact) { - std::vector errors; - if (!contact.validate(schema, errors)) { - for (const pjson::SchemaError& e : errors) { - std::cout << " " << (e.path.empty() ? "(root)" : e.path) +bool addContact(pjson& book, const pJsonSchemaValidator& validator, + const pjson& contact) { + std::vector errors; + if (!validator.validate(contact, errors)) { + for (const pJsonSchemaValidator::Error& e : errors) { + std::cout << " " + << (e.instanceLocation.empty() ? "(root)" : e.instanceLocation) << ": " << e.message << "\n"; } return false; // rejected } - pjson& contacts = book["contacts"]; - if (contacts.size() > static_cast(INT_MAX)) - return false; - contacts[static_cast(contacts.size())] = contact; + book["contacts"].pushBack(contact); // promotes to an array and deep-copies return true; } ``` -The checked conversion is necessary because `size()` returns `size_t` while -the builder index is `int`. Assigning the index one past the end auto-extends -the array. +`pushBack` promotes the target to an array if needed and appends a deep copy of +the whole contact value. ```mermaid flowchart TD @@ -78,27 +84,28 @@ ada["id"] = int64_t(1); ada["name"] = "Ada Lovelace"; ada["emails"] += "ada@example.com"; ada["tags"] += "pioneer"; -addContact(book, *schema, ada); +addContact(book, validator, ada); ``` **From a JSON payload** (e.g. arriving over a network): ```cpp -auto incoming = pjson::parse(R"({ +pjson incoming = pJsonParser().parse(R"({ "id": 2, "name": "Bob", "emails": ["bob@example.com", "b@work.com"] -})"); -if (incoming) - addContact(book, *schema, *incoming); +})", + err); +if (err.ok) + addContact(book, validator, incoming); ``` **An invalid one is rejected** with precise messages: ```cpp -auto invalid = pjson::parse(R"({ "id": 0, "name": "", "emails": [] })"); -if (invalid) - addContact(book, *schema, *invalid); +pjson invalid = pJsonParser().parse(R"({ "id": 0, "name": "", "emails": [] })", err); +if (err.ok) + addContact(book, validator, invalid); // /emails: array has 0 items, below minItems 1 -// /id: value 0.0 is below minimum 1.0 +// /id: value 0 is below minimum 1 // /name: string length 0 is below minLength 1 ``` @@ -111,7 +118,7 @@ book["contacts"][0]["emails"] += "ada@lovelace.org"; // Find the contact with id == 2 without creating anything. const pjson* contacts = book.find("contacts"); for (size_t i = 0; contacts && i < contacts->size(); ++i) { - const pjson* contact = contacts->find(static_cast(i)); + const pjson* contact = contacts->findIndex(i); int64_t id = 0; std::string name; if (contact && contact->tryGet("id", id) && id == int64_t(2) && @@ -128,8 +135,8 @@ output.maxOutputBytes = size_t(64) * 1024 * 1024; std::cout << book.toString(output) << "\n"; ``` -produces a tidy, sorted-key document with both accepted contacts and their -edits. +produces a tidy document with both accepted contacts and their edits. Object +member order is unspecified. ## What this demonstrates diff --git a/docs/08-building-and-installing.md b/docs/08-building-and-installing.md index b396c01..0f5bc8f 100644 --- a/docs/08-building-and-installing.md +++ b/docs/08-building-and-installing.md @@ -4,16 +4,23 @@ There are several ways to use pjson in your project, from compiling its canonical sources directly to consuming an installed package. Pick whichever fits. -## Option 1 — Compile the canonical sources directly +## Option 1 — Compile sources directly -pjson has **no dependencies** beyond the C++ standard library. For a vendored -copy, use the canonical header and implementation from the repository: +pjson has **no public dependencies** beyond the C++ standard library. The +implementation is intentionally split by responsibility. A DOM-only build needs: - `pjsonlib/include/pjson.h` - `pjsonlib/src/pjson.cpp` -Compile `pjsonlib/src/pjson.cpp` alongside your own sources and add -`pjsonlib/include` to the include path: +Parsing additionally needs `pjsonlib/include/pjson_parser.h` and +`pjsonlib/src/pjson_parser.cpp`; serialization needs `pjson_serialize.cpp`; JSON +Pointer and Patch need `pjson_pointer.cpp` and `pjson_patch.cpp`. Serialization +also requires the vendored Ryu source and its private include paths. Because +these details are easy to omit, the single `pjson::pjson` CMake target is the +recommended integration whenever more than the DOM-only core is used. + +For a DOM-only program, compile `pjson.cpp` alongside your source and add the +public include directory: ```sh c++ -std=c++11 -I path/to/pjson/pjsonlib/include \ @@ -27,7 +34,9 @@ In code: using namespace ByteDance; ``` -This is the recommended path for small projects and for trying pjson out. +Applications that parse, serialize, apply Pointer/Patch operations, or use +`pJsonSchemaValidator` should consume the CMake target, which already includes +all required translation units and private dependencies. ```mermaid flowchart LR @@ -57,7 +66,9 @@ Resulting layout: ``` out/ - include/pjson.h public header + include/pjson.h DOM public header + include/pjson_parser.h parser public header + include/pjson_schema.h schema-validator public header release/lib/libpjson.a Release static library release/bin/pjsontest Release test runner release/bin/pjsonbench Release benchmark runner @@ -100,14 +111,14 @@ cmake --build build --config Release cmake --install build --config Release --prefix /path/to/pjson-prefix ``` -The install contains `pjson.h`, the library, `pjsonConfig.cmake`, +The install contains `pjson.h`, `pjson_parser.h`, `pjson_schema.h`, the library, `pjsonConfig.cmake`, `pjsonConfigVersion.cmake`, and `pjsonTargets.cmake`. The CMake package files normally live under `//cmake/pjson`; the exact `` follows the platform's GNU install-directory convention. Consume them with a versioned config-package lookup: ```cmake -find_package(pjson 1.0 CONFIG REQUIRED) +find_package(pjson 2.0 CONFIG REQUIRED) target_link_libraries(my_app PRIVATE pjson::pjson) ``` @@ -123,7 +134,7 @@ Installation also writes a relocatable `pjson.pc` under ```sh pkg-config --modversion pjson -c++ -std=c++11 your_app.cpp $(pkg-config --cflags --libs 'pjson >= 1.0') \ +c++ -std=c++11 your_app.cpp $(pkg-config --cflags --libs 'pjson >= 2.0') \ -o your_app ``` @@ -161,7 +172,7 @@ Configure consumers with the usual vcpkg toolchain file, then use the same | `BUILD_SHARED_LIBS` | `OFF` | Build a shared library instead of the default static library | | `PJSON_SANITIZE` | `OFF` | Enable AddressSanitizer and UndefinedBehaviorSanitizer with GCC or Clang | | `PJSON_BUILD_DOCS` | `OFF` | Build the Doxygen reference; requires Doxygen and Python 3 | -| `PJSON_BUILD_FUZZERS` | `OFF` | Build the four coverage-guided fuzz targets | +| `PJSON_BUILD_FUZZERS` | `OFF` | Build the seven coverage-guided fuzz targets | | `PJSON_BENCH_COMPARE` | `OFF` | Add pinned nlohmann/json, RapidJSON, and simdjson comparisons to `pjsonbench` | | `PJSON_BENCH_DEPS_DIR` | `.benchmark-deps` | Locate the pinned comparison sources | | `PJSON_FUZZING_ENGINE` | empty | Supply an external fuzz-engine linker command instead of built-in libFuzzer | @@ -170,6 +181,11 @@ These defaults describe a normal configure with no pre-seeded cache values. Use the three explicit pjson component switches in scripts so the selected target set does not depend on surrounding project configuration. +Shared consumers should use the exported `pjson::pjson` CMake target or the +installed `pjson.pc` file. Both propagate the private `PJSON_SHARED` import-mode +definition required by the public visibility macro; consumers should not define +`PJSON_BUILDING_LIBRARY`. + With an empty `PJSON_FUZZING_ENGINE`, `PJSON_BUILD_FUZZERS=ON` requires Clang with libFuzzer on Linux/macOS. An external engine can instead be supplied through `PJSON_FUZZING_ENGINE`. For a small @@ -196,8 +212,9 @@ developer components default to `OFF` automatically. ## What you learned -- The simplest integration is to compile `pjsonlib/src/pjson.cpp` with your app - and point `-I` at `pjsonlib/include` — no build system required. +- The simplest full-featured integration is the `pjson::pjson` CMake target; + direct source compilation is practical only when its complete feature-specific + source list and private include paths are maintained by the embedding project. - `build.sh` builds everything into `out/`; CMake integration exposes the `pjson::pjson` target. - Installed CMake and pkg-config metadata are relocatable, and Conan 2 and an diff --git a/docs/09-testing.md b/docs/09-testing.md index a5cf4b3..5f9e488 100644 --- a/docs/09-testing.md +++ b/docs/09-testing.md @@ -23,8 +23,9 @@ cmake --build build ctest --test-dir build --output-on-failure ``` -CTest registers every `TEST()` separately, so progress and failures are -reported case by case rather than as one aggregate `1/1` executable. +After linking, CMake asks the compiled test registry for its test names and +registers every `TEST()` separately, so progress and failures are reported case +by case rather than as one aggregate `1/1` executable. You can run one case by name with `ctest --test-dir build -R pjson.test_name`. There is still only one test binary. Run it directly to execute every case: @@ -65,18 +66,24 @@ full sweep. The fetch helper checks out a pinned corpus commit for reproducible results. Plain `./build.sh --test --auto` also fetches either corpus when it is missing. Without `--auto`, both `--test` and `--all` ask before downloading. -The schema suite uses a separately pinned subset manifest drawn from the -JSON-Schema-Test-Suite `draft7` directory. Fetch and run that manifest with: +The schema suite uses a pinned JSON-Schema-Test-Suite checkout. It runs the +legacy subset manifest from `draft7` and a complete 80-file Draft 2020-12 +manifest. The Draft 2020-12 gate currently executes 1,773 applicable cases +across 437 groups with no selected-group skips; 15 whole optional files +are explicitly deferred and checked by the bidirectional manifest. Fetch and +run both gates with: ```sh ./scripts/fetch-json-schema-test-suite.sh PJSON_JSON_SCHEMA_TEST_SUITE_DIR="$PWD/.test-corpora/JSON-Schema-Test-Suite" \ ctest --test-dir out/build-debug --output-on-failure \ - -R '^pjson\.schema_official_draft7_optional$' + -R '^pjson\.schema_official_(draft7|draft2020)_optional$' ``` -Without that checkout, the official-schema case reports a clean skip; the -repository's inline schema tests still run. +Without that checkout, an explicitly optional local test run reports clean +skips; `--auto`, the full contributor gate, and release CI fetch the pinned +checkout and require the manifest-backed cases to run. The repository's inline +schema tests always run. ## How the suite is organized @@ -92,14 +99,14 @@ one executable: | `tests_roundtrip.cpp` | serialize/parse stability, formatting | | `tests_features.cpp` | version, depth guard, RFC 8259 parsing, errors, equality, streams | | `tests_schema.cpp`, `tests_schema_complex.cpp`, `tests_schema_vocabulary.cpp` | schema validation and vocabulary | -| `tests_schema_official.cpp` | optional pinned JSON-Schema-Test-Suite subset manifest | +| `tests_schema_official.cpp` | pinned draft-07 subset plus complete Draft 2020-12 manifest and deferral accounting | | `tests_malformed.cpp` | exhaustive invalid/hostile input (never throws) | | `tests_mutation.cpp` | complex add/edit/delete/rebuild scenarios | | `tests_api_edge.cpp` | normal + edge case for every public method | | `tests_fuzz.cpp` | deterministic (seeded) fuzzing | | `tests_pathological.cpp` | extreme numbers, wide payloads, and exact budget boundaries | | `tests_conformance.cpp` | inline RFC 8259 cases + optional nst/JSONTestSuite corpus | -| `tests_storage.cpp` | inline scalar storage, copy/move/swap, type transitions | +| `tests_storage.cpp` | opaque scalar storage, copy/move/swap, type transitions, ABI shape | | `tests_allocator.cpp` | custom allocator ownership, failure, move, and swap behavior | | `tests_streaming.cpp` | SAX events, chunk boundaries, cancellation, direct stream output | | `tests_serialize_access.cpp` | serialization options and non-vivifying access | @@ -129,7 +136,7 @@ TEST(my_feature_does_x) { - `TEST(name) { ... }` registers a test automatically — no list to maintain. - `CHECK(expr)` fails the test if `expr` is false. - `CHECK_EQ(a, b)` checks equality and prints both values on failure. -- `CHECK_PARSE_FAILS(text)` asserts that parsing `text` returns empty. +- `CHECK_PARSE_FAILS(text)` asserts that parsing `text` reports a failure. There is no `main()` to edit; the runner discovers every `TEST` at startup. @@ -142,12 +149,14 @@ points must not crash or emit unexpected exceptions, and successful parses must round-trip. Expected serialization and allocation failures keep their documented contracts. -For mutation-guided coverage, Clang builds four standalone libFuzzer targets: +For mutation-guided coverage, Clang builds seven standalone libFuzzer targets: `pjson_fuzz_parse` exercises RFC 8259 DOM round trips, `pjson_fuzz_stream` compares buffer, stream, and SAX paths, and -`pjson_fuzz_schema` checks schema validation invariants. `pjson_fuzz_patch` -exercises JSON Patch and Merge Patch, checking that failures leave the target -unchanged and successful transformations remain serializable. Run the bounded +`pjson_fuzz_serialize` checks serialization options and structured failures. +`pjson_fuzz_schema` checks schema validation invariants, +`pjson_fuzz_pointer` covers RFC 6901 lookup, and separate `pjson_fuzz_patch` and +`pjson_fuzz_merge_patch` targets check atomic failure and successful +transformations. Run the bounded seed corpus smoke used by CI with: ```sh @@ -183,7 +192,8 @@ With no `PJSON_FUZZING_ENGINE`, this requires a full LLVM Clang distribution with libFuzzer; Apple Command Line Tools alone may not include that runtime. An external engine may instead be supplied through `PJSON_FUZZING_ENGINE`. OSS-Fuzz packaging is kept in `oss-fuzz/`, and every target uses -`fuzz/json.dict`. +`fuzz/json.dict`. The smoke and OSS-Fuzz configurations permit inputs up to +64 KiB, and checked-in serializer/Merge Patch seeds exceed 4 KiB. `PJSON_BUILD_FUZZERS` controls only whether the targets are built. It does not run them; use `./build.sh --fuzz`, invoke the executables directly, or use the diff --git a/docs/10-contributing.md b/docs/10-contributing.md index b9c3dfa..9a2c5aa 100644 --- a/docs/10-contributing.md +++ b/docs/10-contributing.md @@ -8,8 +8,14 @@ coding style, and the checks your change should pass. ``` pjson/ pjsonlib/ - include/pjson.h the public header (declarations only) - src/pjson.cpp the implementation + include/pjson.h DOM public header + include/pjson_parser.h parser public header + include/pjson_schema.h schema-validator public header + src/pjson.cpp DOM/value/storage implementation + src/pjson_parser.cpp JSON parser implementation + src/pjson_serialize.cpp serializer implementation + src/pjson_pointer.cpp JSON Pointer implementation + src/pjson_patch.cpp JSON Patch/Merge Patch implementation pjsontest/src/ the test suite (tests_*.cpp + harness) examples/src/ runnable examples used by the docs docs/ this tutorial series @@ -22,10 +28,9 @@ pjson/ .clang-tidy static-analysis rules ``` -A design principle: **the header stays small**. All implementation — including -the parser, schema validator, and serialization helpers — lives in `pjson.cpp` -(inside the `pjsonImpl` helper). Please keep new internal helpers out of the -header. +A design principle: **public headers stay declaration-focused**. The DOM must +not depend on `pJsonParser`; dependencies flow from parser to core. Keep new +internal helpers in the relevant private header/translation unit. ## The one command to run before submitting @@ -56,7 +61,7 @@ flowchart LR - `--all` benchmarks pjson alongside pinned nlohmann/json, RapidJSON, and simdjson versions. Use `--bench` for the dependency-free pjson-only run, or `--bench-compare` to request comparison mode directly. -- `--all` also replays bounded corpora through four libFuzzer targets covering +- `--all` also replays bounded corpora through seven libFuzzer targets covering DOM parsing, streaming, schema validation, and Patch/Merge Patch atomicity when the local platform has Clang and libFuzzer; otherwise that optional part is reported as skipped. `--fuzz` is strict and fails if it cannot run. @@ -113,7 +118,8 @@ Naming conventions used in the codebase: - Public methods are `camelCase` (`findPointer`, `parseStream`). - Parameters are prefixed `a` (`aKey`, `aValue`); members with `_` (`_eType`, `_pValueMap`). -- Internal helpers in `pjson.cpp` are `_leadingUnderscore`. +- Internal DOM helpers in `pjsonImpl` retain `_leadingUnderscore`; focused + component-private helpers follow their translation unit's local convention. ## Adding a test diff --git a/docs/11-streaming.md b/docs/11-streaming.md index 65a6d44..1948a18 100644 --- a/docs/11-streaming.md +++ b/docs/11-streaming.md @@ -12,19 +12,21 @@ Follow along with ## Define an event handler -Derive from `pjson::SaxHandler` and override only the events you need. Every +Derive from `pJsonParser::SaxHandler` and override only the events you need. Every callback returns `bool`; return `false` for controlled early termination. ```cpp #include "pjson.h" +#include "pjson_parser.h" #include #include #include using ByteDance::pjson; +using ByteDance::pJsonParser; -struct NumberSummary : pjson::SaxHandler { +struct NumberSummary : pJsonParser::SaxHandler { uint64_t count = 0; double total = 0.0; @@ -34,6 +36,12 @@ struct NumberSummary : pjson::SaxHandler { return true; } + bool onUInt(uint64_t value) override { + ++count; + total += static_cast(value); + return true; + } + bool onDouble(double value) override { ++count; total += value; @@ -42,18 +50,19 @@ struct NumberSummary : pjson::SaxHandler { }; ``` -Available callbacks are `onNull`, `onBool`, `onInt`, `onDouble`, `onString`, -`onStartArray`, `onEndArray`, `onStartObject`, `onKey`, and `onEndObject`. They -arrive in source order. +Available callbacks are `onNull`, `onBool`, `onInt`, `onUInt`, `onDouble`, +`onString`, `onStartArray`, `onEndArray`, `onStartObject`, `onKey`, and +`onEndObject`. They arrive in source order. `onUInt` receives integer tokens +above `INT64_MAX`; handlers that only care about smaller integers may ignore it. ## Parse the stream ```cpp std::ifstream input("huge.json", std::ios::binary); NumberSummary summary; -pjson::ParseError error; +pJsonParser::Error error; -if (!pjson::parseSaxStream(input, summary, error)) { +if (!pJsonParser().parseSaxStream(input, summary, error)) { std::cerr << "JSON error at " << error.line << ':' << error.column << " (byte " << error.offset << "): " << error.message << '\n'; return 1; @@ -68,7 +77,7 @@ constructing the tree. Only `parseSaxStream()` provides true incremental input. ## Limits and duplicate keys -All `ParseOptions` apply to streaming input. Their defaults are `maxDepth = +All `pJsonParser::Options` apply to streaming input. Their defaults are `maxDepth = 512`, `maxNodes = 1,000,000`, and `maxInputBytes = 64 MiB`. `maxNodes` counts JSON values even without a DOM, providing a predictable work limit. Zero makes `maxNodes` or `maxInputBytes` unlimited; a non-positive `maxDepth` is instead @@ -83,7 +92,7 @@ occurrences because a stream cannot retract an event already delivered. Returning `false` from a callback stops parsing and reports `SAX parse aborted`. If a callback throws, pjson catches it and reports a handler exception in -`ParseError`; exceptions do not escape `parseSax*()`. +`pJsonParser::Error`; exceptions do not escape `parseSax*()`. ## Streaming output @@ -96,7 +105,6 @@ pjson::SerializeOptions options = pjson::SerializeOptions::prettyPrinted(); options.indentWidth = 4; options.indentCharacter = ' '; options.escapeNonAscii = true; -options.keyOrder = pjson::SerializeOptions::AscendingKeys; options.maxOutputBytes = size_t(64) * 1024 * 1024; document.write(output, options); if (!output) { @@ -104,11 +112,12 @@ if (!output) { } ``` -This avoids a whole-document output buffer; traversal state scales with nesting -depth, while escaping a key or string can use temporary memory proportional to -that token. `SerializeOptions` controls indentation, non-ASCII escaping, and -ascending or descending key traversal for both output APIs. The default output -limit is 64 MiB; zero explicitly means unlimited. `write()` returns `void`, so +This avoids a whole-document output buffer. Traversal state scales with nesting +depth; escaping a key or string can use temporary memory proportional to that +token. Object members are emitted in unspecified native storage order. +`SerializeOptions` controls indentation, non-ASCII escaping, non-finite values, +and output limits for both output APIs. The default output limit is 64 MiB; zero +explicitly means unlimited. `write()` returns `void`, so check the stream state. Invalid stored UTF-8, crossing the configured byte limit, and indentation/size overflow are logical preflight failures: they set `failbit` before any bytes are emitted. The corresponding `toString()` call diff --git a/docs/12-custom-allocators.md b/docs/12-custom-allocators.md index 69cb24e..059562c 100644 --- a/docs/12-custom-allocators.md +++ b/docs/12-custom-allocators.md @@ -11,12 +11,13 @@ Without an allocator argument, a value uses pjson's built-in allocator: ```cpp pjson value; -pjson::unique_ptr parsed = pjson::parse(R"({"answer":42})"); +pJsonParser::Error error; +pjson parsed = pJsonParser().parse(R"({"answer":42})", error); ``` -The direct value is owned by its C++ scope. The parse result uses the same -provenance-aware `pjson::unique_ptr` returned by every DOM parse overload, so it -is also released automatically. Children returned by `find()` or +The direct value is owned by its C++ scope. The parse result is a plain `pjson` +value returned by every DOM parse overload, so it is also released +automatically when it goes out of scope. Children returned by `find()` or `findPointer()` are borrowed views into the owning tree—never delete them yourself. @@ -57,11 +58,14 @@ The contract is: | `StringAllocation` | The `std::string` wrapper for a string-valued node | | `ArrayAllocation` | The internal wrapper for an array-valued node | | `ObjectAllocation` | The internal wrapper for an object-valued node | +| `ImplementationAllocation` | The private representation of a non-null `pjson` value | -The hook deliberately does not replace every allocation in the process. The -internal buffers/nodes allocated by `std::string`, `std::vector`, and `std::map`, -and transient parsing, serialization, pointer, patch, and validation workspaces, -continue to use the standard allocator. +`ImplementationAllocation` is part of ABI generation 2; custom allocators +must accept every defined kind and should avoid fixed-size tables that assume only +the original four values. The hook deliberately does not replace every allocation +in the process. The internal buffers/nodes allocated by `std::string`, +`std::vector`, and `std::unordered_map`, and transient parsing, serialization, +pointer, patch, and validation workspaces, continue to use the standard allocator. ## Direct roots versus parsed roots @@ -77,30 +81,29 @@ PoolAllocator pool; } // document's destructor returns its bound storage to pool ``` -Parsing must allocate the root dynamically, so every overload returns -`pjson::unique_ptr`: +Parsing must allocate the root's descendants dynamically, but every overload +returns the document **by value**, bound to the supplied allocator: ```cpp -pjson::ParseError error; -pjson::ParseOptions options; -pjson::unique_ptr document = pjson::parse(text, error, pool, options); -if (!document) { +pJsonParser::Error error; +pJsonParser::Options options; +pjson document = pJsonParser(pool, options).parse(text, error); +if (!error.ok) { std::cerr << error.line << ':' << error.column << ": " << error.message << '\n'; } ``` -`pjson::unique_ptr` is `std::unique_ptr`. Its -stateless deleter reads allocator provenance from the root and returns the root -through the correct allocator. Do not replace that deleter or call `delete` on -the root. Moving the smart pointer transfers the root but does not own or extend -the allocator's lifetime. +The returned value is bound to `pool`: its wrapper objects and descendants were +obtained from `pool`, and its destructor returns them through `pool`. There is +no smart pointer and no manual `delete`. Moving the value transfers the tree but +does not own or extend the allocator's lifetime. -Allocator-aware overloads exist for `std::string`, `(const char*, size_t)`, and -`std::istream`, with optional `ParseError` and `ParseOptions`. `parseStream()` -uses standard allocation for its temporary input buffer but uses the supplied -allocator for the persistent DOM. SAX parsing builds no persistent DOM and has -no allocator overload. +An allocator-configured parser accepts `std::string`, `(const char*, size_t)`, +and `std::istream`, with optional `pJsonParser::Error`. Its `Options` are fixed +at construction. `parseStream()` uses standard allocation for its temporary +input buffer but uses the supplied allocator for the persistent DOM. SAX +parsing builds no persistent DOM; the parser's allocator is unused by SAX calls. ## Copy, move, assignment, and swap @@ -134,10 +137,10 @@ source is JSON null but remains bound to its original allocator. ## Failure behavior Allocator-aware in-memory parsing catches failures during DOM construction, -destroys partial trees, returns an empty `pjson::unique_ptr`, and fills -`ParseError` when supplied. `parseStream()` first fills a standard-allocated -input buffer, so an exception-enabled stream or failure in that buffer can still -throw before DOM construction. +destroys partial trees, returns a JSON `null` value, and fills `pJsonParser::Error` when +supplied. `parseStream()` first fills a standard-allocated input buffer, so an +exception-enabled stream or failure in that buffer can still throw before DOM +construction. Other operations that allocate—such as string/container mutation, deep copy, and cross-allocator move—may propagate `std::bad_alloc`. Copy assignment, @@ -162,14 +165,13 @@ CountingAllocator storage; pjson direct(storage); direct["kind"] = "direct root"; - pjson::ParseError error; - pjson::unique_ptr parsed = - pjson::parse(R"({"kind":"parsed root","values":[1,2,3]})", - error, storage); - if (!parsed) + pJsonParser::Error error; + pjson parsed = pJsonParser(storage).parse( + R"({"kind":"parsed root","values":[1,2,3]})", error); + if (!error.ok) return 1; - pjson copy(*parsed, storage); + pjson copy(parsed, storage); if (direct.canSwap(copy)) direct.swap(copy); } @@ -178,12 +180,12 @@ CountingAllocator storage; ## What you learned -- Default values require no allocator setup and every DOM parse returns the - provenance-aware `pjson::unique_ptr`. +- Default values require no allocator setup and every DOM parse returns a plain + `pjson` value. - Every value is allocator-bound; a supplied `Allocator` is borrowed and must outlive the entire bound tree. -- Direct roots remain caller-owned, while allocator-parsed roots use - `pjson::unique_ptr` and `ValueDeleter`. +- Direct roots remain caller-owned, and an allocator-parsed value is bound to, + and freed through, the allocator passed to `parse()`. - Copies are deep, assignments preserve the destination allocator, and moves may allocate across allocator domains. - `canSwap()` distinguishes the O(1) same-allocator path from a cross-allocator diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index f343af1..a4a7048 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -7,6 +7,8 @@ find_package(Python3 REQUIRED COMPONENTS Interpreter) # Read the public version macro so generated pages always match the library. set(PJSON_PUBLIC_HEADER "${PROJECT_SOURCE_DIR}/pjsonlib/include/pjson.h") +set(PJSON_PARSER_PUBLIC_HEADER "${PROJECT_SOURCE_DIR}/pjsonlib/include/pjson_parser.h") +set(PJSON_SCHEMA_PUBLIC_HEADER "${PROJECT_SOURCE_DIR}/pjsonlib/include/pjson_schema.h") file(STRINGS "${PJSON_PUBLIC_HEADER}" PJSON_VERSION_DEFINE REGEX "^#define PJSON_VERSION \"[^\"]+\"$") string(REGEX REPLACE "^#define PJSON_VERSION \"([^\"]+)\"$" "\\1" @@ -37,6 +39,8 @@ set(PJSON_DOCS_COMMANDS --xml "${PJSON_DOCS_OUTPUT_DIR}/xml" --html "${PJSON_DOCS_OUTPUT_DIR}/html") set(PJSON_DOCS_DEPENDS "${PJSON_PUBLIC_HEADER}" + "${PJSON_PARSER_PUBLIC_HEADER}" + "${PJSON_SCHEMA_PUBLIC_HEADER}" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile" "${PJSON_DOXYGEN_FILTER}" "${PJSON_DOCS_VALIDATOR}" diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index dfa86ca..903d46d 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -44,6 +44,8 @@ WARN_NO_PARAMDOC = NO WARN_AS_ERROR = YES INPUT = "@PJSON_PUBLIC_HEADER@" \ + "@PJSON_PARSER_PUBLIC_HEADER@" \ + "@PJSON_SCHEMA_PUBLIC_HEADER@" \ "@PJSON_DOCS_MAINPAGE@" \ "@PJSON_DOCS_API_NOTES@" \ "@PJSON_DOCS_ALLOCATORS@" \ diff --git a/docs/README.md b/docs/README.md index 52c1a8f..3c72193 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,6 +52,9 @@ flowchart LR ## Reference and migration +- [pjson 2.0 behavioral and ABI contract](behavioral-contract-2.0.md) — current + ownership, parsing, numeric, mutation, error, allocator, thread-safety, and + binary-compatibility guarantees - [Browsable API reference](https://pico-developer.github.io/pjson/) — generated per-symbol documentation (its [source page](reference/mainpage.md) is kept in this repository). @@ -87,6 +90,7 @@ warnings and missing public API families fail the build. ```cpp #include "pjson.h" +#include "pjson_parser.h" #include #include using namespace ByteDance; @@ -101,9 +105,10 @@ int main() { pjson::SerializeOptions pretty = pjson::SerializeOptions::prettyPrinted(); std::cout << person.toString(pretty) << "\n"; - pjson::unique_ptr parsed = pjson::parse(person.toString(pretty)); + pJsonParser::Error error; + pjson parsed = pJsonParser().parse(person.toString(pretty), error); std::string name; - if (parsed && parsed->tryGet("name", name)) + if (error.ok && parsed.tryGet("name", name)) std::cout << name << "\n"; // Ada } ``` diff --git a/docs/behavioral-contract-2.0.md b/docs/behavioral-contract-2.0.md new file mode 100644 index 0000000..accc25a --- /dev/null +++ b/docs/behavioral-contract-2.0.md @@ -0,0 +1,276 @@ + + + +# pjson 2.0 behavioral contract + +Status: normative public behavior for pjson 2.0.x +Applies to: `pjson.h`, `pjson_parser.h`, `pjson_schema.h`, and the `pjson::pjson` library target + +This page consolidates the guarantees that applications may rely on. The public +headers remain authoritative for overload signatures and enum members. Examples, +benchmarks, private layout, exact diagnostic prose, and undocumented implementation +details are not compatibility promises. pjson follows Semantic Versioning for +source, documented behavior, and its same-major ABI baseline. + +## 1. Value and ownership model + +`ByteDance::pjson` is an owning, mutable JSON value with deep-copy semantics. A +value owns its complete subtree. Roots are ordinary C++ values: parsing returns a +`pjson` by value, no smart pointer is exposed, and callers never delete child nodes. + +The representations are: + +| JSON value | `jsonType` | Stored C++ value | +|---|---|---| +| null | `jsonNull` | no payload; also the default and moved-from state | +| string | `jsonString` | length-aware bytes in `std::string`; valid UTF-8 is required for JSON output | +| signed integer | `jsonNumberInt` | `int64_t` | +| unsigned integer | `jsonNumberUInt` | `uint64_t` | +| fractional/exponent number | `jsonNumberDouble` | `double` | +| boolean | `jsonBoolean` | `bool` | +| array | `jsonArray` | ordered children | +| object | `jsonObject` | unique `std::string` keys; storage order unspecified | + +Object keys and strings may contain embedded NUL bytes. `std::string` and +`StringView` APIs preserve their full lengths; `const char*` APIs are conventionally +NUL-terminated and reject null pointers where documented. Objects use private, +process-seeded hash storage. Insertion, `keys()`, callback traversal, and +serialization order are unspecified. + +`size()` is the member/element count for containers and zero for scalars, so +`empty()` is true for every scalar. `clear()` keeps an array or object container but +empties it; it resets a scalar to null. `reset()` always produces null. + +## 2. Construction, lookup, and mutation + +`operator[]` is a builder, not a read-only lookup: + +- key access converts a non-object to an object and inserts a missing null child; +- a non-negative index converts a non-array to an array and fills gaps with null; +- one indexed access may create at most 1,000,000 children; larger growth throws + `std::length_error` before mutation; +- a valid negative `int` index addresses an existing element from the end; a + negative index before the beginning throws `std::out_of_range` without mutation. + +Use `find`, `findPointer`, `hasKey`, `hasIndex`, `contains`, `tryGet`, or `at` for +reads. `find` and `tryGet` do not create values and report absence/type mismatch by +null or `false`; `at` is non-vivifying and throws `std::out_of_range` on a missing +key/index or wrong container type. `tryGet` leaves its output unchanged on failure. +No scalar-to-string or boolean coercions occur. Integer reads permit only +range-safe signed/unsigned conversion; a `double` read accepts all stored numbers. + +`pushBack` and `insertOrAssign` copy lvalues. They transfer a same-allocator rvalue +without a deep copy and deep-copy it otherwise. An ancestor rvalue is snapshotted +when the destination lies inside it, preventing an ownership cycle. `reserve` promotes a non-array to +an empty array. Array erasure shifts later elements left. + +### Borrowing and invalidation + +Pointers/references returned by `find`, `findPointer`, `at`, and `operator[]`, plus +`StringView` and traversal callback arguments, borrow storage from the owning tree. +They become invalid when that child or an ancestor is destroyed, replaced, reset, +erased, moved, swapped, cleared, or successfully patched. Array growth/erasure and +object insertion/erasure may invalidate container traversal state. Never mutate a +container's membership or size from its `forEachMember`/`forEachElement` callback; +mutating the current child without resizing the parent is allowed. Callback key/value +views are valid only during the callback. + +`keys()` returns owning copies. Direct traversal uses pre-declared function pointers +plus an opaque context and does not copy keys or perform a second lookup. A null +visitor or wrong container type is a successful no-op; returning `false` stops early. + +## 3. Copy, move, swap, and aliasing + +- Copy construction and assignment are deep. Copy assignment preserves the + destination allocator. +- Move construction transfers storage in O(1) and leaves the source null. Move + assignment transfers same-allocator storage after an ancestry-safety check; a + cross-allocator move deep-copies, may allocate, and clears the source only after + success. Moving an ancestor into its descendant snapshots the ancestor instead. +- `swap` performs an ancestry-safety check followed by an O(1) exchange when + `canSwap` is true. Cross-allocator and overlapping ancestor/descendant swaps are + safe no-ops. +- Self-copy and self-move are safe. Assignment from an ancestor, descendant, or + sibling is snapshot-safe. Swapping an ancestor with its descendant is rejected as + a safe no-op so an ownership cycle cannot be formed. + +Copy assignment, cross-allocator move assignment, container promotion/growth, and +document-level Patch operations build replacement state before publication in their +documented failure paths. Allocation failure therefore does not publish a partially +constructed replacement. + +## 4. Parsing contract + +The separate `ByteDance::pJsonParser` owns parser configuration and consumes the +DOM API; `pjson` does not depend on or expose parser members. All DOM, byte-span, +buffered-stream, SAX-buffer, and incremental SAX entry points +accept exactly one RFC 8259 JSON value followed only by JSON whitespace. They reject +malformed UTF-8, raw string controls, invalid escapes/surrogates, non-lowercase +literals, malformed numbers, trailing data, and (by default) duplicate object names. +A byte-span is length-aware and may contain NUL bytes; a null source pointer is an +`InvalidArgument` failure even when its size is zero. + +Default `pJsonParser::Options` are: + +| Option | Default | Zero/non-positive meaning | +|---|---:|---| +| `maxDepth` | 512 | values <= 0 mean one level; all values clamp to hard maximum 1024 | +| `maxNodes` | 1,000,000 | unlimited | +| `maxInputBytes` | 64 MiB | unlimited | +| `duplicateKeys` | `RejectDuplicateKeys` | choose keep-first/keep-last explicitly | +| `numberPolicy` | `RejectUnrepresentableNumbers` | opt into lossy conversion explicitly | + +Every DOM overload returns a `pjson` value. A failed parse returns null; because valid +JSON `null` produces the same value, use a `pJsonParser::Error` overload whenever success must +be distinguished. Reporting overloads reset the error first and provide a stable +`Code`, zero-based byte offset, one-based line, one-based byte column, and unstable +human-readable message. In-memory syntax, budget, numeric, and DOM-allocation +failures are reported rather than exposed as JSON-specific exceptions. Buffered +stream input may still propagate exceptions from an exception-enabled stream or its +temporary standard-allocated buffer. + +SAX callbacks occur in source order and borrow string/key values only for the call. +Returning `false` or throwing from a callback stops parsing and becomes +`CallbackError`; callback exceptions do not cross the public SAX boundary. SAX work +already delivered is not rolled back. Under keep-first duplicate policy, later value +subtrees are suppressed; under keep-last, both occurrences are observable because a +stream cannot retract earlier callbacks. + +## 5. Numeric contract + +Integer tokens in `[INT64_MIN, INT64_MAX]` use `jsonNumberInt`; non-negative integer +tokens through `UINT64_MAX` that exceed `INT64_MAX` use `jsonNumberUInt`. Explicit +`uint64_t` assignment retains unsigned identity even for a small value. Integer +tokens outside `[INT64_MIN, UINT64_MAX]` are rejected by default. + +Fractional/exponent tokens use finite `double`. Overflow and a nonzero token that +rounds to zero are rejected by default. `AllowLossyNumbers` permits out-of-range +integers and nonzero-to-zero underflow to use the nearest finite representable +`double`. Decimal conversion is classic-locale and follows the active floating-point +rounding mode; applications that change that mode must restore round-to-nearest for +cross-environment reproducibility. A floating negative-zero token (such as `-0.0`) +is retained as a double, compares equal to zero, and round-trips with its sign; the +integer token `-0` is the ordinary signed integer zero. + +Numeric equality and `tryCompareNumber` compare signed integers, unsigned integers, +and doubles without first rounding integers through `double`; `1`, explicit `1u`, and +`1.0` compare equal. NaN is unequal and unordered. Arrays compare in order and +objects by key/value, independent of any construction history. + +Finite double output is locale-independent and uses Ryu shortest-round-trip +conversion. pjson retains fixed notation for ordinary decimal exponents and a +decimal marker for integral-looking doubles so their storage kind survives a round +trip. Exact lexical spelling is not otherwise guaranteed. + +## 6. Serialization contract + +Defaults are compact output, raw valid UTF-8, non-finite rejection, and a 64 MiB +output limit. Pretty output defaults to two spaces. Non-ASCII escaping is an +explicit option; an indentation character other than space/tab is normalized to +space. Object-member output order is unspecified. A zero output limit means unlimited. + +Stored invalid UTF-8 is never emitted. NaN and infinity fail by default; explicit +policies may emit `null` or the strings `"NaN"`, `"Infinity"`, and +`"-Infinity"`. + +| API | Logical/allocation failure | Physical stream failure | Publication | +|---|---|---|---| +| `toString(options)` | throws `std::invalid_argument`, `std::length_error`, or allocation exception | n/a | no result | +| `toString(out, error, options)` | returns `false` with stable `SerializeError::Code` | n/a | `out` remains unchanged | +| `write(stream, options)` | sets `failbit`; enabled stream exceptions may propagate | sets stream failure state | logical failures emit no bytes; I/O may leave a prefix | +| `write(stream, error, options)` | returns `false`, sets error and stream failure state | returns `false` as `StreamFailure` | logical failures emit no bytes; I/O may leave a prefix | + +The structured serialization overloads are `noexcept`; diagnostic message text is not +stable. + +## 7. JSON Pointer, Patch, and Merge Patch + +`findPointer` implements RFC 6901 lookup without mutation. The empty pointer selects +the current value; non-empty pointers start with `/`; `~0` and `~1` decode to `~` and +`/`; and `-` is not a lookup index. Reporting overloads provide stable +`PointerError::Code` values and token details. Returned nodes are borrowed. + +`applyPatch` implements RFC 6902 and `applyMergePatch` implements RFC 7396. Both are +`noexcept`, transactional at document scope, and leave the target unchanged on any +failure. Defaults/hard ceilings are 10,000 operations, 1,000,000 cloned nodes, 64 MiB +of cloned node/string/key bytes, and 1,000,000 work units; zero retains the hard +ceiling rather than disabling it. Patch `test` uses pjson structural/numeric equality. +Moving a root beneath itself and moving into a descendant are rejected. + +## 8. Allocators and destruction + +Every value is permanently bound to either the process-lifetime default allocator or +a caller-supplied `Allocator`. A supplied allocator is borrowed and must outlive all +bound roots and descendants. It receives persistent node and string/array/object +wrapper allocations. Standard-container backing storage and temporary parsing, +serialization, Pointer, Patch, and schema work continue to use standard allocation. +`allocate` returns aligned non-null storage or throws; `deallocate` must not throw. + +Destruction and subtree cleanup are iterative and do not allocate, including deeply +nested values. A custom allocator must remain usable during unwinding and must provide +its own synchronization if shared across threads. + +## 9. Thread safety + +Distinct `pjson` values may be used concurrently. A value or any part of its subtree +must not be mutated concurrently with another read or write of that tree. The default +allocator and `getVersion()` are initialization-safe. + +`pJsonSchemaValidator` owns immutable copies after construction. One compiled +validator may validate concurrently when callers provide separate error vectors. Its +resolver runs only during construction and is not retained. + +## 10. JSON Schema contract + +`pJsonSchemaValidator` is an external helper and consumes only public `pjson` APIs. +Construction deep-copies the root and resolved schemas; construction may throw +`std::bad_alloc`. `isSchemaValid()` and `schemaErrors()` report invalid dialects, +vocabularies, keyword shapes, identifiers, anchors, references, resolver failures, and +resource exhaustion. `validate()` is read-only, `noexcept`, and never mutates either +input; its vector overload appends diagnostics rather than clearing the vector. + +Default validation implements the named dialect returned by +`documentedSubsetDialectUri()`. `Options::draft2020()` opts into official Draft +2020-12, bundled standard meta-schema validation, and per-resource vocabulary +activation. It supports the +keyword allowlist documented in `pjson_schema.h`, including references/anchors, +conditionals, applicators, `unevaluated*`, object/array/string/numeric assertions, and +seven formats. It never performs implicit network I/O. Unknown keywords are ignored in +permissive mode; `Options::strict()` rejects unsupported standard keywords and +malformed supported keywords. `Options::modernSubset()` enables modern `$ref` sibling +semantics and makes `format` annotation-only by default. + +The private regex backend implements Unicode-aware +ECMAScript syntax, including property escapes and non-BMP code points. Safe regex mode +bounds patterns/subjects and rejects risky constructs; `trustedRegex()` removes only +that conservative syntax restriction and must be reserved for trusted schemas and +instances. The backend's finite work ceiling remains active. +Validation/reference/work/error/resource budgets remain active. + +## 11. Standards and compatibility scope + +| Facility | Contract | Scope caveat | +|---|---|---| +| JSON parse/output | RFC 8259 and ECMA-404 data model | duplicate-name policy is explicit; object order is unspecified | +| JSON Pointer | RFC 6901 | lookup API only; `-` is Patch syntax, not lookup | +| JSON Patch | RFC 6902 | bounded and document-atomic | +| JSON Merge Patch | RFC 7396 | bounded and document-atomic | +| JSON Schema | pjson subset by default; required Draft 2020-12 vocabularies through `Options::draft2020()` | optional format-assertion, bignum, and cross-draft behavior is not complete | + +## 12. ABI baseline + +`PJSON_ABI_VERSION` is 2. Within compatible 2.x releases, `pjson` remains a +two-pointer opaque handle and `pJsonParser`/`pJsonSchemaValidator` remain +one-pointer opaque handles. Public option/error layouts, virtual interfaces, +enum values, signatures, calling conventions, and exported symbols remain +compatible. Private representation and source organization may change. + +ABI compatibility assumes the same compiler ABI, standard-library ABI, +architecture, and compatible build settings. A different major version, ABI +version, or shared-library SOVERSION is an explicit rebuild boundary. + +Stable public enum/code values and documented defaults are behavioral API. Exact error +messages, private storage, benchmark numbers, and source-file organization may change +without a major release. Changes to number classification, duplicate defaults, object +ordering, exception behavior, or serialization semantics require deliberate compatible +versioning under `VERSIONING.md`. diff --git a/docs/migration-from-nlohmann-json.md b/docs/migration-from-nlohmann-json.md index 63ab270..22bcffe 100644 --- a/docs/migration-from-nlohmann-json.md +++ b/docs/migration-from-nlohmann-json.md @@ -15,85 +15,88 @@ using json = nlohmann::json; // After #include "pjson.h" +#include "pjson_parser.h" using ByteDance::pjson; +using ByteDance::pJsonParser; ``` -pjson requires C++11 or newer. It is a compiled library: link `pjson::pjson` -or compile `pjsonlib/src/pjson.cpp` with the application in addition to -including `pjson.h`. Those two files are the canonical API and behavior -sources; generated documentation and examples are explanatory. +pjson requires C++11 or newer. It is a compiled library; link `pjson::pjson`, +which contains the core, parser, serialization, Pointer, Patch, and schema +translation units. Include `pjson_parser.h` only where parsing is needed. ## API mapping at a glance | nlohmann/json | pjson | Important difference | |---|---|---| | `json j;` | `pjson j;` | Both start as JSON `null`. | -| `json::object()` / `json::array()` | `j.resetTo(pjson::jsonObject)` / `j.resetTo(pjson::jsonArray)` | pjson has no object/array factory. | -| `json::parse(text)` | `pjson::parse(text)` | Returns `pjson::unique_ptr`; failure is an empty pointer. | -| `json::parse(text, nullptr, false)` | `pjson::parse(text, error)` | Inspect the pointer and optional `ParseError`; there is no discarded value. | -| `input >> j` or `json::parse(input)` | `pjson::parseStream(input)` | Builds a DOM and buffers the complete input. | +| `json::object()` / `json::array()` | `pjson::object()` / `pjson::array()` | Factories return an empty object/array value. | +| `json::parse(text)` | `pJsonParser().parse(text)` | Returns a `pjson` value; failure yields JSON `null`. | +| `json::parse(text, nullptr, false)` | `pJsonParser().parse(text, error)` | Pass a `pJsonParser::Error` to detect failure vs. a real `null`. | +| `input >> j` or `json::parse(input)` | `pJsonParser().parseStream(input, error)` | Builds a DOM and buffers the complete input. | | `j.dump()` | `j.toString()` | Compact output. | | `j.dump(indent, ch, ensure_ascii)` | `j.toString(options)` | Configure a `SerializeOptions` value explicitly. | | `out << j` | `j.write(out[, options])` | Returns `void`; inspect stream state. | -| `j.is_null()`, `is_string()`, ... | `j.isNull()`, `isString()`, ... | pjson distinguishes signed `int64_t` and `double`; there is no unsigned kind. | +| `j.is_null()`, `is_string()`, ... | `j.isNull()`, `isString()`, ... | pjson has signed `int64_t`, unsigned `uint64_t`, and `double` numeric kinds. | | `j.get()` | `j.tryGet(out)` | Exact-type extraction writes an out-parameter and returns `false` on mismatch. | | `j.get_ref()` | `j.tryGet(pjson::StringView&)` | The view is borrowed and mutation-sensitive. | -| `j.contains(key)` | `j.hasKey(key)` | Non-mutating; false on a non-object. | +| `j.contains(key)` | `j.contains(key)` / `j.hasKey(key)` | Non-mutating; false on a non-object. | | `j.find(key)` | `j.find(key)` | pjson returns a borrowed pointer or `nullptr`, not an iterator. | +| `j.at(key)` | `j.at(key)` | Checked, non-vivifying; throws `std::out_of_range` when absent. | | `j.value(key, fallback)` | `tryGet`, then choose the fallback | The fallback remains application logic. | | `j[key] = value` | `j[key] = value` | pjson `operator[]` is a builder and may replace the receiver's type. | -| `j.push_back(value)` | `j[static_cast(j.size())] = value` | Indexed builder access grows an array. | +| `j.push_back(value)` | `j.pushBack(value)` | Promotes to an array and appends a value. | | `j.erase(key/index)` | `j.erase(key/index)` | Returns `bool`; an array index is `size_t`. | -| range iteration | `size()` + `find(index)`, or `keys()` + `find(key)` | No public raw-container access. | -| `json::sax_parse(...)` | `pjson::parseSax(...)` / `parseSaxStream(...)` | `parseSaxStream()` is the incremental stream path. | +| range iteration | `forEachMember`/`forEachElement`, or `size()` + `find(index)` | No public raw-container access. | +| `json::sax_parse(...)` | `pJsonParser().parseSax(...)` / `parseSaxStream(...)` | `parseSaxStream()` is the incremental stream path. | | `j = j.patch(patch)` | `j.applyPatch(patch[, error][, options])` | Mutates atomically; `PatchOptions` bounds amplification. | | `j.merge_patch(patch)` | `j.applyMergePatch(patch[, error][, options])` | Atomic RFC 7396 with the same limits. | -| external JSON Schema library | `value.validate(schema[, errors][, options])` | Implements only the documented subset. | +| external JSON Schema library | `pJsonSchemaValidator v(schema[, options]); v.validate(value[, errors])` | Standalone validator; subset by default, with required Draft 2020-12 vocabularies via `Options::draft2020()`. | ## Parsing and ownership -### Every DOM parse returns `pjson::unique_ptr` +### Every DOM parse returns a `pjson` value -All DOM parse and stream-parse overloads return `pjson::unique_ptr`, including -those using the default allocator. An empty pointer means failure. The custom -deleter destroys the complete tree through the allocator recorded by its root. -Do not call `delete` on the pointer or convert it to a differently-deletered -smart pointer. +All DOM parse and stream-parse overloads return a `pjson` **by value** that owns +its subtree and frees it on destruction — no smart pointer, no `delete`. The +terse overloads return JSON `null` on failure; pass a `pJsonParser::Error` to tell +failure apart from a successfully parsed literal `null`. Move the value to +transfer ownership into another document. ```cpp -pjson::ParseError error; -pjson::unique_ptr document = pjson::parse(text, error); -if (!document) { +pJsonParser::Error error; +pjson document = pJsonParser().parse(text, error); +if (!error.ok) { report(error.message, error.offset, error.line, error.column); return; } -consume(*document); +consume(document); ``` The `(const char*, size_t)` overload parses exactly the supplied byte span, including embedded NUL bytes. `parseStream()` buffers one complete document. Pass `pjson&` or `const pjson&` when code only borrows the parsed document, and -move the `pjson::unique_ptr` to transfer root ownership. +`std::move` the returned value to transfer ownership into another tree. -Allocator-aware overloads take a borrowed `pjson::Allocator&`. That allocator -must outlive the returned root and every descendant. A directly constructed -root remains caller-owned; a parser-created root is owned by -`pjson::unique_ptr`. SAX parsing builds no persistent DOM and has no allocator -overload. +An allocator-aware `pJsonParser` constructor takes a borrowed `pjson::Allocator&`. That allocator +must outlive the returned value and every descendant. A directly constructed +root remains caller-owned; a parser-created value is bound to, and freed +through, the allocator selected by the parser. SAX parsing builds no persistent +DOM and does not use the parser's allocator. -### `ParseError` is reset on every reporting call +### `pJsonParser::Error` is reset on every reporting call -`ParseError::offset` is a zero-based byte offset. `line` and `column` are -one-based, and `column` counts bytes. Every parse overload that accepts a -`ParseError&` resets all fields before doing work. Success leaves: +`pJsonParser::Error::offset` is a zero-based byte offset. `line` and `column` are +one-based, and `column` counts bytes. `code` is a stable machine-facing +category. Every parse overload that accepts a `pJsonParser::Error&` resets all fields +before doing work. Success leaves: ```text -ok == true, offset == 0, line == 1, column == 1, message.empty() +ok == true, code == None, offset == 0, line == 1, column == 1, message.empty() ``` -Failure sets `ok == false` and describes the first error. It is safe to reuse -one error object across calls; never infer failure from an old message. Test -the returned pointer first, or `ok` for SAX parsing. +Failure sets `ok == false`, a stable `code`, and describes the first error. It +is safe to reuse one error object across calls; never infer failure from an old +message. Test `error.ok` after every parse. ### Parsing always enforces RFC 8259 @@ -102,14 +105,14 @@ unpaired UTF-16 surrogates, upper- or mixed-case keywords, raw control characters in strings, malformed UTF-8, invalid number grammar, comments, trailing commas, `NaN`, `Infinity`, and trailing non-whitespace content. -`ParseOptions` contains resource budgets and duplicate-key policy only: +`pJsonParser::Options` contains resource budgets and duplicate-key policy only: ```cpp -pjson::ParseOptions options; +pJsonParser::Options options; options.maxDepth = 512; options.maxNodes = 1000000; options.maxInputBytes = size_t(64) * 1024 * 1024; -options.duplicateKeys = pjson::ParseOptions::RejectDuplicateKeys; +options.duplicateKeys = pJsonParser::Options::RejectDuplicateKeys; ``` `maxNodes == 0` and `maxInputBytes == 0` mean unlimited. A non-positive @@ -126,9 +129,10 @@ To preserve nlohmann/json's usual keep-last behavior without weakening RFC 8259 validation: ```cpp -pjson::ParseOptions options; -options.duplicateKeys = pjson::ParseOptions::KeepLastDuplicate; -auto document = pjson::parse(text, options); +pJsonParser::Error error; +pJsonParser::Options options; +options.duplicateKeys = pJsonParser::Options::KeepLastDuplicate; +pjson document = pJsonParser(options).parse(text, error); ``` ## Reading without mutation @@ -142,13 +146,16 @@ Do not translate checked or observational nlohmann access into pjson subscripting. One indexed access that would create more than 1,000,000 children throws `std::length_error` before mutation. +Valid negative `int` indexes count from the end; an index before the beginning +throws `std::out_of_range`. Prefer the `size_t` overload for non-negative +builder indexes. Use `find(key)` and `find(index)` for borrowed node access. Both return `nullptr` for the wrong container type or a missing child and never mutate the document. Negative indexes count from the end. ```cpp -const pjson& root = *document; +const pjson& root = document; std::string name; if (root.tryGet("name", name)) @@ -156,7 +163,7 @@ if (root.tryGet("name", name)) if (const pjson* items = root.find("items")) { for (size_t i = 0; i < items->size(); ++i) { - if (const pjson* item = items->find(static_cast(i))) + if (const pjson* item = items->findIndex(i)) consume(*item); } } @@ -174,24 +181,29 @@ An integer may widen to `double`; no other coercion occurs. A `StringView` borrows bytes and is invalidated when its node or an ancestor is modified or destroyed. -### Numbers are signed `int64_t` or `double` +### Numbers are signed `int64_t`, unsigned `uint64_t`, or `double` -pjson has no unsigned numeric representation and no convenience `int` or -`float` API. Use `int64_t` and `double` explicitly in assignments, appends, -vectors, SAX callbacks, and `tryGet` calls: +pjson has no convenience `int`/`float` API, but as of 2.0 it does have a +distinct unsigned kind. Use `int64_t`, `uint64_t`, and `double` explicitly in +assignments, appends, vectors, SAX callbacks, and `tryGet` calls: ```cpp root["count"] = int64_t(42); +root["big"] = uint64_t(18446744073709551615ULL); // exact, round-trips root["ratio"] = double(0.5); int64_t count = 0; +uint64_t big = 0; double ratio = 0.0; -if (!root.tryGet("count", count) || !root.tryGet("ratio", ratio)) +if (!root.tryGet("count", count) || !root.tryGet("big", big) || + !root.tryGet("ratio", ratio)) reportTypeError(); ``` -Before narrowing an unsigned source, perform an application-level range check. -An integer read as `double` may lose precision beyond `2^53`. +Integer tokens above `INT64_MAX` (up to `UINT64_MAX`) become the unsigned kind. +Tokens beyond `UINT64_MAX`, and non-finite floats, are rejected by default (see +`pJsonParser::Options::AllowLossyNumbers` and `SerializeOptions::NonFinitePolicy`). An +integer read as `double` may lose precision beyond `2^53`. ### Building and editing @@ -246,20 +258,21 @@ pjson::SerializeOptions options = pjson::SerializeOptions::prettyPrinted(); options.indentWidth = 4; options.indentCharacter = ' '; options.escapeNonAscii = true; -options.keyOrder = pjson::SerializeOptions::AscendingKeys; options.maxOutputBytes = size_t(64) * 1024 * 1024; -std::string text = document->toString(options); -document->write(output, options); +std::string text = document.toString(options); +document.write(output, options); ``` Default construction selects compact output, two-space indentation, a space -indent character, UTF-8 output, ascending keys, and a 64 MiB output limit. Set +indent character, UTF-8 output, and a 64 MiB output limit. Set `maxOutputBytes = 0` only when explicitly requesting unlimited output. Objects -are inherently map-ordered; insertion order is unavailable. Non-finite stored -doubles serialize as JSON `null`. Finite doubles use locale-independent, stable -round-trip formatting with 15–17 significant digits; shortest spelling is not -part of the contract. +use unspecified native storage order for traversal and serialization; insertion +order is not retained. +Non-finite stored doubles fail serialization unless `SerializeOptions::nonFinite` explicitly +selects `NonFiniteToNull` or `NonFiniteToString`. Finite doubles use pinned Ryu +shortest-round-trip conversion followed by pjson's documented +fixed/scientific spelling policy. Every stored string value and object key must contain valid UTF-8. Invalid stored UTF-8 is a serialization failure even when `escapeNonAscii` is false: @@ -276,48 +289,64 @@ with `maxInputBytes` but buffers the document. `parseSaxStream()` reads incrementally and retains no DOM. SAX callbacks receive borrowed string/key references valid only for the duration of the callback. Returning `false` from a callback cancels parsing; the public call then returns `false` and populates -`ParseError` when supplied. +`pJsonParser::Error` when supplied. -## JSON Schema validation is a subset +## JSON Schema validation modes -pjson validates a value directly against another `pjson` value. It does not -compile a schema or validate against a meta-schema. The collecting overload -appends `SchemaError` entries; clear a reused vector first. Error paths are RFC -6901 pointers, with the empty string denoting the root. +pjson compiles a schema (itself a `pjson` value) into a standalone +`ByteDance::pJsonSchemaValidator` (declared in ``) and validates +values against it. The validator is a pure consumer of pjson's public API. The +collecting overload appends +`pJsonSchemaValidator::Error` entries; clear a reused vector first. Error paths +are RFC 6901 pointers, with the empty string denoting the root. -The documented pjson subset is the complete enforced vocabulary; it is not a -complete JSON Schema draft implementation: +Default construction implements pjson's explicitly named subset dialect. +`Options::draft2020()` selects the official Draft 2020-12 URI, bundled standard +meta-schema validation, modern `$ref` behavior, and per-resource vocabulary +activation. Custom meta-schema URIs and external resources are loaded only +through the explicit resolver. An unsupported root `$schema` or required +`$vocabulary` makes `isSchemaValid()` false; inspect `schemaErrors()` before +trusting validation. Unknown optional vocabularies are accepted as annotations. + +The following keywords form the default subset and the required Draft 2020-12 +vocabularies implemented by the opt-in mode: | Area | Supported keywords and forms | |---|---| -| General | `type` (string or array), `enum`, `const`, local-fragment `$ref` | -| Objects | `properties`, `patternProperties`, `propertyNames`, `required`, `dependentRequired`, `dependencies`, `additionalProperties`, `minProperties`, `maxProperties` | -| Arrays | single-schema or tuple-array `items`, `minItems`, `maxItems`, `uniqueItems` | +| General | `type` (string or array), `enum`, `const` | +| References | `$id`, `$anchor`, `$dynamicAnchor`, `$ref`, `$dynamicRef`; explicit resolver for external documents | +| Objects | `properties`, `patternProperties`, `propertyNames`, `required`, `dependentRequired`, `dependencies`, `dependentSchemas`, `additionalProperties`, `unevaluatedProperties`, `minProperties`, `maxProperties` | +| Arrays | `items`, `prefixItems`, `contains`, `minContains`, `maxContains`, `unevaluatedItems`, `minItems`, `maxItems`, `uniqueItems` | | Numbers | `minimum`, `maximum`, numeric `exclusiveMinimum`, numeric `exclusiveMaximum`, `multipleOf` | | Strings | `minLength`, `maxLength`, `pattern`, and supported `format` values | -| Composition | `allOf`, `anyOf`, `oneOf`, `not` | +| Composition | `allOf`, `anyOf`, `oneOf`, `not`, `if`, `then`, `else` | | Schema values | Boolean schemas | -Unknown or unsupported schema keywords are ignored and therefore impose no -constraint. This is a compatibility hazard: a typo or unsupported security -rule can make validation less restrictive without producing an error. Audit -schemas against the table above and retain an external validator when another -vocabulary is required. Remote references are unsupported; `$ref` resolves -only local URI-fragment JSON Pointers. +In permissive subset mode, unknown or unsupported schema keywords are ignored +and therefore impose no constraint. This is a compatibility hazard: a typo or +unsupported security rule can make validation less restrictive without an +error. Use `Options::strict()` for a fail-closed subset boundary or +`Options::draft2020()` for the implemented official dialect. The complete +optional format-assertion vocabulary, arbitrary-precision numbers, and other +drafts remain out of scope. `$ref` resolves URI resources, pointers, and anchors; +external documents are available only through an explicit resolver callback, so +pjson never performs network I/O. `$dynamicRef`/`$dynamicAnchor` and both +`unevaluated*` keywords are supported by the modern subset option. `minLength` and `maxLength` count Unicode code points, not UTF-8 bytes. `pattern` uses ECMAScript regular-expression syntax with search semantics. The default policy caps pattern and subject sizes and rejects expressions outside a -conservative safe subset. `SchemaOptions::trustedRegex()` removes only those -regex restrictions and should be used only when both schema and instance are -trusted. Validation depth, reference, work, and error-count budgets remain in -effect. Known format checks run by default; unknown format names are ignored. +conservative safe subset. `pJsonSchemaValidator::Options::trustedRegex()` removes +only those regex restrictions and should be used only when both schema and +instance are trusted. Validation depth, reference, work, and error-count budgets +remain in effect. Known format checks run by default; unknown format names are +ignored. ## Suggested migration sequence -1. Replace parse results with `pjson::unique_ptr` and check every result before - dereferencing it. -2. Replace exception-based parse handling with `ParseError`, remembering that +1. Replace parse results with a `pjson` value plus a `pJsonParser::Error`, and check + `error.ok` before using the value. +2. Replace exception-based parse handling with `pJsonParser::Error`, remembering that reporting calls reset it on entry. 3. Remove permissive parser flags; pjson always enforces RFC 8259 syntax. 4. Choose a duplicate-key policy and explicit resource budgets. @@ -325,9 +354,9 @@ effect. Known format checks run by default; unknown format names are ignored. `tryGet`; reserve `operator[]` for building. 6. Replace raw-container iteration with `size()` plus `find(index)`, or `keys()` plus `find(key)`. -7. Normalize numeric code to `int64_t` and `double`, with explicit range checks - at unsigned boundaries. +7. Normalize numeric code to `int64_t`, `uint64_t`, and `double`, with explicit + range checks at signed/unsigned boundaries. 8. Replace dump flags and pretty booleans with `SerializeOptions`, and handle invalid-UTF-8 serialization failure. -9. Audit every schema keyword against pjson's documented subset and test both - accepted and rejected instances. +9. Choose the default/strict subset or `Options::draft2020()` deliberately and + test both accepted and rejected instances. diff --git a/docs/migration-from-rapidjson.md b/docs/migration-from-rapidjson.md index b40f1f1..23e6a92 100644 --- a/docs/migration-from-rapidjson.md +++ b/docs/migration-from-rapidjson.md @@ -7,13 +7,14 @@ limited schema vocabulary. ```cpp #include "pjson.h" +#include "pjson_parser.h" using ByteDance::pjson; +using ByteDance::pJsonParser; ``` -pjson requires C++11 or newer and is a compiled library. Link `pjson::pjson` or -compile `pjsonlib/src/pjson.cpp` with the application. -`pjsonlib/include/pjson.h` and `pjsonlib/src/pjson.cpp` are the canonical API -and behavior sources; this guide describes how to adapt RapidJSON code to them. +pjson requires C++11 or newer and is a compiled library. Link `pjson::pjson`, +which contains the decomposed implementation. Include `pjson_parser.h` only in +translation units that parse input. ## Migration map @@ -29,14 +30,14 @@ and behavior sources; this guide describes how to adapt RapidJSON code to them. | `operator[]` for lookup | `find` / `tryGet` | pjson subscripting is builder-only and may mutate. | | member iteration | `keys()` + `find(key)` | No public raw object container. | | array iteration | `size()` + `find(index)` | No public raw array container. | -| `Document::Parse(...)` | `pjson::parse(...)` | Every DOM overload returns `pjson::unique_ptr`. | +| `Document::Parse(...)` | `pJsonParser().parse(...)` | Every DOM overload returns a `pjson` value; pass a `pJsonParser::Error` for status. | | `Reader` + handler | `parseSax(...)` / `parseSaxStream(...)` | SAX callbacks return `bool` to continue. | | `Writer` / `PrettyWriter` | `write(out[, options])` | Configure `SerializeOptions`; inspect stream state. | | `StringBuffer` + Writer | `toString([options])` | Returns the serialized string. | | `Pointer::Get` | `findPointer(...)` | Non-vivifying RFC 6901 lookup. | | Pointer mutation | normal building or `applyPatch(...[, options])` | RFC 6902 patching is atomic and bounded. | | Merge Patch helper code | `applyMergePatch(...[, options])` | Atomic RFC 7396 with the same limits. | -| `SchemaDocument` + `SchemaValidator` | `value.validate(schema, ...)` | No compiled schema; only the documented subset is enforced. | +| `SchemaDocument` + `SchemaValidator` | `pJsonSchemaValidator v(schema[, options]); v.validate(value, ...)` | Compile once into the standalone validator; subset by default, required Draft 2020-12 vocabularies via `Options::draft2020()`. | ## Values, ownership, and allocators @@ -47,6 +48,9 @@ an object and creates a missing null child. Integer access promotes it to an array and grows it with null children. Use explicit final scalar types: One indexed access that would create more than 1,000,000 children throws `std::length_error` before mutation. +Valid negative `int` indexes count from the end; an index before the beginning +throws `std::out_of_range`. Prefer the `size_t` overload for non-negative +builder indexes. ```cpp pjson document; @@ -66,37 +70,40 @@ arrays with indexed assignment: array[static_cast(array.size())] = child; ``` -### Parsed roots always use `pjson::unique_ptr` +### Parsed roots are returned by value -Every DOM `parse` and `parseStream` overload returns `pjson::unique_ptr`, for -both default and custom allocation. Failure produces an empty pointer. The -custom deleter follows allocator provenance stored in the root, so do not call -`delete` or substitute another smart-pointer deleter. +Every DOM `parse` and `parseStream` overload returns a `pjson` **by value**, for +both default and custom allocation. There is no smart pointer and no manual +`delete`; the value owns its subtree and frees it on destruction. The terse +overloads return JSON `null` on failure; pass a `pJsonParser::Error` to distinguish +failure from a successfully parsed literal `null`. ```cpp -pjson::ParseError error; -pjson::unique_ptr document = pjson::parse(jsonBytes, byteCount, error); -if (!document) { +pJsonParser::Error error; +pjson document = pJsonParser().parse(jsonBytes, byteCount, error); +if (!error.ok) { std::cerr << error.line << ':' << error.column << ": " << error.message << '\n'; return; } ``` -An allocator passed to a constructor or parse overload is borrowed and must -outlive the complete tree. Persistent nodes and wrapper objects use it; backing -storage inside standard containers and parser scratch space use their normal -standard allocators. Copying a `pjson` is deep. Assignment preserves the -destination allocator; a cross-allocator move may allocate. `swap()` is O(1) -only when `canSwap()` is true. +An allocator passed to a `pjson` or `pJsonParser` constructor is borrowed and must +outlive the complete tree. The returned value is bound to that allocator. +Persistent nodes and wrapper objects use it; backing storage inside standard +containers and parser scratch space use their normal standard allocators. +Copying a `pjson` is deep. Assignment preserves the destination allocator; a +cross-allocator move may allocate. `swap()` is O(1) only when `canSwap()` is +true. ### Parse diagnostics have a reusable lifecycle -Reporting parse and SAX overloads reset `ParseError` on entry. Success leaves -`ok == true`, offset zero, line one, column one, and an empty message. Failure -sets `ok == false` and reports the first problem. Offset is a zero-based byte -position; line and byte-column are one-based. A reused error never intentionally -retains diagnostics from the previous call. +Reporting parse and SAX overloads reset `pJsonParser::Error` on entry. Success leaves +`ok == true`, `code == None`, offset zero, line one, column one, and an empty +message. Failure sets `ok == false`, a stable `code`, and reports the first +problem. Offset is a zero-based byte position; line and byte-column are +one-based. A reused error never intentionally retains diagnostics from the +previous call. ## Parsing always enforces RFC 8259 @@ -105,16 +112,16 @@ All DOM and SAX entry points reject malformed UTF-8, invalid escapes, lone surrogates, raw string controls, non-lowercase literals, comments, trailing commas, invalid numbers, `NaN`, `Infinity`, and trailing non-whitespace data. -`ParseOptions` controls only work budgets and duplicate names: +`pJsonParser::Options` controls only work budgets and duplicate names: ```cpp -pjson::ParseOptions options; +pJsonParser::Options options; options.maxDepth = 512; options.maxNodes = 1000000; options.maxInputBytes = size_t(64) * 1024 * 1024; -options.duplicateKeys = pjson::ParseOptions::RejectDuplicateKeys; +options.duplicateKeys = pJsonParser::Options::RejectDuplicateKeys; -pjson::unique_ptr document = pjson::parse(json, error, options); +pjson document = pJsonParser(options).parse(json, error); ``` Zero means unlimited for node and input-byte budgets. A non-positive depth @@ -134,7 +141,7 @@ and can replace the receiver's type. Use `find` for a borrowed node and `tryGet` for a typed value: ```cpp -const pjson& root = *document; +const pjson& root = document; int64_t count = 0; if (root.tryGet("count", count)) @@ -147,16 +154,17 @@ if (const pjson* settings = root.find("settings")) { } ``` -`tryGet` supports `int64_t`, `double`, `bool`, `std::string`, and -`StringView`. It performs no coercion except integer-to-double widening, and it -leaves the destination unchanged on failure. `StringView` is valid only while -the owning node remains alive and unchanged. +`tryGet` supports `int64_t`, `uint64_t`, `double`, `bool`, `std::string`, and +`StringView`. It performs no coercion except integer-to-double widening and the +exact signed/unsigned reads described in the numeric section, and it leaves the +destination unchanged on failure. `StringView` is valid only while the owning +node remains alive and unchanged. Iterate without exposing container internals: ```cpp for (size_t i = 0; i < array.size(); ++i) { - if (const pjson* value = array.find(static_cast(i))) + if (const pjson* value = array.findIndex(i)) consume(*value); } @@ -168,21 +176,25 @@ for (const std::string& key : object.keys()) { `find(index)` supports negative end-relative indexes, but normal forward loops should convert their checked `size_t` position to `int`. `keys()` returns a -copy in deterministic map order. Child pointers are borrowed and can be -invalidated by mutation of the child or an ancestor. +copy in private native storage order; `forEachMember()` uses that same +unspecified order. Child +pointers are borrowed and can be invalidated by mutation of the child or an ancestor. ## Numeric migration -pjson stores numbers as signed `int64_t` or `double`; it has no unsigned type. -Use `isInt()`/`isDouble()` to inspect storage and `tryGet` for extraction. -Reading an integer into `double` is allowed but may lose precision beyond -`2^53`; reading a double into `int64_t` is not an implicit `tryGet` conversion. +pjson stores numbers as signed `int64_t`, unsigned `uint64_t` (for values above +`INT64_MAX`), or `double`. Use `isInt()`/`isUInt()`/`isInteger()`/`isDouble()` +to inspect storage and `tryGet` for extraction. Reading an integer into `double` +is allowed but may lose precision beyond `2^53`; reading a double into an integer +is not an implicit `tryGet` conversion. -Before migrating `SetUint64`, `GetUint64`, or `IsUint64` code, define an -application policy. Values above `INT64_MAX` cannot be represented exactly as -the integer kind. Reject them, store them as strings, or accept documented -double precision loss. Use explicit `int64_t` and `double` at all API -boundaries rather than relying on C++ overload selection. +`SetUint64`, `GetUint64`, and `IsUint64` map directly onto +`operator=(uint64_t)`, `tryGet(uint64_t&)`, and `isUInt()`; the full `uint64_t` +range round-trips exactly. Values above `UINT64_MAX`, and non-finite floats, are +rejected by default (`pJsonParser::Options::AllowLossyNumbers` and +`SerializeOptions::NonFinitePolicy` opt out). Use explicit `int64_t`, +`uint64_t`, and `double` at all API boundaries rather than relying on C++ +overload selection. ## JSON Pointer and patching @@ -194,14 +206,15 @@ unsigned decimal indices, and `-` is not a lookup index. Use For general pointer mutation, apply an RFC 6902 patch: ```cpp -auto patch = pjson::parse(R"([ +pjson patch = pJsonParser().parse(R"([ {"op":"replace", "path":"/address/city", "value":"Paris"}, {"op":"add", "path":"/tags/-", "value":"new"} -])"); +])", + error); pjson::PatchError patchError; pjson::PatchOptions patchOptions; -if (!patch || !document->applyPatch(*patch, patchError, patchOptions)) { +if (!error.ok || !document.applyPatch(patch, patchError, patchOptions)) { // The document is unchanged on failure. } ``` @@ -224,7 +237,7 @@ Moving the document root beneath itself reports ## SAX input and serialized output -Derive from `pjson::SaxHandler` and override the callbacks of interest. Integer +Derive from `pJsonParser::SaxHandler` and override the callbacks of interest. Integer events use `int64_t`; floating events use `double`. Returning `false` cancels the parse. Callback string and key references are borrowed only for the callback duration. @@ -237,22 +250,23 @@ pjson::SerializeOptions options = pjson::SerializeOptions::prettyPrinted(); options.indentWidth = 2; options.indentCharacter = ' '; options.escapeNonAscii = true; -options.keyOrder = pjson::SerializeOptions::AscendingKeys; options.maxOutputBytes = size_t(64) * 1024 * 1024; -document->write(output, options); +document.write(output, options); if (!output) reportWriteFailure(); -std::string encoded = document->toString(options); +std::string encoded = document.toString(options); ``` The defaults are compact layout, two-space indentation, space indentation, -UTF-8 output, ascending keys, and a 64 MiB output limit. Zero explicitly makes -`maxOutputBytes` unlimited. Object insertion order is not retained. Non-finite -stored doubles serialize as JSON null. Finite doubles use locale-independent, -stable round-trip formatting with 15–17 significant digits; shortest spelling -is not part of the contract. +UTF-8 output, and a 64 MiB output limit. Zero explicitly makes +`maxOutputBytes` unlimited. Object insertion order is not retained, and +serialization order is unspecified. A stored +non-finite double fails serialization by default (`SerializeOptions::nonFinite` +selects `RejectNonFinite`, `NonFiniteToNull`, or `NonFiniteToString`). Finite +doubles use pinned Ryu shortest-round-trip conversion followed by pjson's +documented fixed/scientific spelling policy. Invalid UTF-8 in any stored string or object key is a serialization failure, regardless of `escapeNonAscii`: `toString()` throws `std::invalid_argument`. @@ -264,45 +278,61 @@ the parser itself accepts only valid UTF-8. ## Schema validation -RapidJSON 1.1 validates compiled draft-04 schemas. pjson instead validates an -already-built value directly against another `pjson`, with no compiled-schema -object and no SAX validation. The error overload appends `SchemaError` values; -clear a reused vector first. Error paths are RFC 6901 pointers, with `""` -denoting the root. - -The documented pjson subset is the complete enforced vocabulary; it is not a -complete JSON Schema draft implementation: +RapidJSON 1.1 validates compiled draft-04 schemas. pjson instead compiles a +schema (itself a `pjson`) into a standalone `ByteDance::pJsonSchemaValidator` +(declared in ``), then validates already-built values against +it, with no SAX validation. The validator is a pure consumer of pjson's public +API. The error overload appends `pJsonSchemaValidator::Error` values; clear a +reused vector first. Error paths are RFC 6901 pointers, with `""` denoting the +root. + +Default construction implements pjson's explicitly named subset dialect, not +RapidJSON's Draft 4 behavior. `Options::draft2020()` selects the official Draft +2020-12 URI, bundled standard meta-schema validation, modern `$ref` behavior, +and per-resource vocabulary activation. Custom meta-schema URIs and external +resources are loaded only through the explicit resolver. An unsupported root +`$schema` or required `$vocabulary` makes `isSchemaValid()` false; inspect +`schemaErrors()` before trusting validation. Unknown optional vocabularies are +accepted as annotations. + +The following keywords form the default subset and the required Draft 2020-12 +vocabularies implemented by the opt-in mode: | Area | Supported keywords/forms | |---|---| | Any value | `type`, `enum`, `const` | -| References | local-fragment `$ref` into the root schema | -| Objects | `properties`, `patternProperties`, `propertyNames`, `required`, `dependentRequired`, `dependencies`, `additionalProperties`, `minProperties`, `maxProperties` | -| Arrays | schema or tuple-array `items`, `minItems`, `maxItems`, `uniqueItems` | +| References | `$id`, `$anchor`, `$dynamicAnchor`, `$ref`, `$dynamicRef`; explicit resolver for external documents | +| Objects | `properties`, `patternProperties`, `propertyNames`, `required`, `dependentRequired`, `dependencies`, `dependentSchemas`, `additionalProperties`, `unevaluatedProperties`, `minProperties`, `maxProperties` | +| Arrays | `items`, `prefixItems`, `contains`, `minContains`, `maxContains`, `unevaluatedItems`, `minItems`, `maxItems`, `uniqueItems` | | Numbers | `minimum`, `maximum`, numeric `exclusiveMinimum`, numeric `exclusiveMaximum`, `multipleOf` | | Strings | `minLength`, `maxLength`, `pattern`, supported `format` names | -| Composition | `allOf`, `anyOf`, `oneOf`, `not` | +| Composition | `allOf`, `anyOf`, `oneOf`, `not`, `if`, `then`, `else` | | Schema values | Boolean schemas | -Unknown or unsupported schema keywords are ignored and therefore are not -enforced. Treat this as a warning, not forward-compatible validation: typos and -unsupported security constraints can silently weaken a schema. Audit every -schema against this table and retain RapidJSON or another validator when the -application depends on any other vocabulary. Remote references are unsupported. +Unknown or unsupported schema keywords are ignored in permissive subset mode +and therefore are not constraints. `Options::strict()` rejects unsupported +standard keywords; `Options::draft2020()` applies the implemented official +dialect and per-resource vocabulary policy. The complete optional +format-assertion vocabulary, arbitrary-precision numbers, and other drafts +remain out of scope. URI resources, anchors, dynamic +references, and `unevaluated*` are available through the modern subset option. +External documents require an explicit resolver callback; pjson never performs +network I/O. `minLength` and `maxLength` count Unicode code points rather than UTF-8 bytes. `pattern` uses ECMAScript syntax and search semantics, but the default policy is intentionally narrower: pattern and subject sizes are capped and expressions outside a conservative safe subset fail validation. -`SchemaOptions::trustedRegex()` removes only regex restrictions and is -appropriate only for trusted schemas and instances. Traversal, reference, work, -and collected-error budgets remain active. Known formats are checked by default; -unknown format names are ignored. +`pJsonSchemaValidator::Options::trustedRegex()` removes only regex restrictions +and is appropriate only for trusted schemas and instances. Traversal, reference, +work, and collected-error budgets remain active. Known formats are checked by +default; unknown format names are ignored. ## Practical migration sequence -1. Change every DOM parse result to `pjson::unique_ptr` and check it before use. -2. Replace parse-error inspection and exceptions with `ParseError`; account for +1. Change every DOM parse result to a `pjson` value plus a `pJsonParser::Error`, and + check `error.ok` before use. +2. Replace parse-error inspection and exceptions with `pJsonParser::Error`; account for its reset-on-entry lifecycle. 3. Remove permissive syntax flags and choose explicit budgets and duplicate-key policy. @@ -310,9 +340,9 @@ unknown format names are ignored. only for construction and intentional mutation. 5. Replace member/array container iteration with `keys()`/`find(key)` and `size()`/`find(index)`. -6. Normalize numeric interfaces to `int64_t` and `double`, including SAX - callbacks and vectors. +6. Normalize numeric interfaces to `int64_t`, `uint64_t`, and `double`, including + SAX callbacks and vectors. 7. Replace Writer and pretty-boolean configuration with `SerializeOptions`, and handle invalid-UTF-8 output failure. -8. Verify every schema keyword is in pjson's documented subset and add - accepted/rejected tests for every relied-upon constraint. +8. Choose the default/strict subset or `Options::draft2020()` deliberately and + add accepted/rejected tests for every relied-upon constraint. diff --git a/docs/reference/mainpage.md b/docs/reference/mainpage.md index ff27790..ff0a4d7 100644 --- a/docs/reference/mainpage.md +++ b/docs/reference/mainpage.md @@ -7,24 +7,33 @@ types are intentionally excluded. ## Start here -- @ref ByteDance::pjson is the central DOM value and entry point. -- @ref ByteDance::pjson::Allocator, @ref ByteDance::pjson::ValueDeleter, and - ByteDance::pjson::unique_ptr support allocator-bound persistent DOM storage. -- @ref ByteDance::pjson::ParseOptions configures duplicate keys and input +- @ref ByteDance::pjson is the central DOM value. +- @ref ByteDance::pJsonParser is the separate configured parser. It consumes + the public DOM implementation; the DOM does not depend on the parser. +- @ref ByteDance::pjson::Allocator supports allocator-bound persistent DOM + storage; an allocator-configured parser returns the document by value, bound + to the chosen allocator. +- @ref ByteDance::pJsonParser::Options configures duplicate keys and input budgets; every parser enforces RFC 8259 syntax. -- @ref ByteDance::pjson::ParseError reports non-throwing parse failures. +- @ref ByteDance::pJsonParser::Error reports non-throwing parse failures. - @ref ByteDance::pjson::PointerError and @ref ByteDance::pjson::PatchError describe RFC 6901, RFC 6902, and RFC 7396 failures. - @ref ByteDance::pjson::PatchOptions bounds transactional patch amplification. -- @ref ByteDance::pjson::SerializeOptions controls formatting, escaping, and - key order, and bounds output size. +- @ref ByteDance::pjson::SerializeOptions controls formatting and escaping, + and bounds output size. - ByteDance::pjson::tryGet(), ByteDance::pjson::StringView, and ByteDance::pjson::findPointer() provide strict, non-vivifying reads. - ByteDance::pjson::applyPatch() and ByteDance::pjson::applyMergePatch() apply atomic RFC 6902 and RFC 7396 updates. -- @ref ByteDance::pjson::SaxHandler supports incremental, non-DOM parsing. -- @ref ByteDance::pjson::SchemaOptions and - @ref ByteDance::pjson::SchemaError configure and report schema validation. +- @ref ByteDance::pJsonParser::SaxHandler supports incremental, non-DOM parsing. +- @ref ByteDance::pJsonSchemaValidator validates a pjson value against a schema + (itself a pjson value); its nested @ref ByteDance::pJsonSchemaValidator::Options + and @ref ByteDance::pJsonSchemaValidator::Error configure and report schema + validation. It is a standalone helper in `` that consumes only + pjson's public API. Default options use its named subset dialect; + `Options::draft2020()` selects the required Draft 2020-12 vocabularies, + bundled meta-schemas, and per-resource vocabulary activation. Unsupported + dialects and required vocabularies fail compilation. Use the navigation tree to browse classes, nested option/error types, enums, typedefs, and every public overload. Each entry is generated from the current @@ -37,7 +46,7 @@ installed header, so the reference follows the API as it evolves. - @subpage migration-rapidjson The two migration guides call out behavioral differences that a mechanical API -rename would miss: ownership, vivifying access, signed numeric storage, +rename would miss: ownership, vivifying access, signed/unsigned numeric storage, duplicate-key policy, allocator provenance and lifetime, streaming, schema coverage, and pjson's status-based error model. diff --git a/docs/reference/pjson-api.dox b/docs/reference/pjson-api.dox index 447783f..e4e1b7d 100644 --- a/docs/reference/pjson-api.dox +++ b/docs/reference/pjson-api.dox @@ -15,6 +15,9 @@ /** @def PJSON_VERSION * @brief The complete library version as a string literal. */ +/** @def PJSON_ABI_VERSION + * @brief The major ABI generation implemented by the public binary interfaces. + */ /** * @class ByteDance::pjson @@ -22,33 +25,36 @@ * * A pjson owns its complete subtree. Copies are deep, and pointers returned by * find() or findPointer(), along with string views returned by tryGet(), remain - * borrowed from their owning tree. In-memory parsing and validation report - * data-domain failures through return values and error objects; - * exception-enabled input streams can still propagate stream exceptions while - * parseStream() buffers input. + * borrowed from their owning tree. Parsing is provided by the independent + * ByteDance::pJsonParser class rather than by pjson members. * * Every value is bound to a runtime allocator. Default construction uses the - * built-in allocator; allocator-aware construction and parsing use a borrowed - * pjson::Allocator that must outlive the complete tree. Copy and move assignment + * built-in allocator; allocator-aware construction and pJsonParser use a + * borrowed pjson::Allocator that must outlive the complete tree. Copy and move assignment * preserve the destination allocator. Storage transfer and swap are O(1) only * between values with the same allocator; use canSwap() when allocator - * provenance may differ. Every DOM parse() and parseStream() overload returns - * pjson::unique_ptr so pjson::ValueDeleter can release the root through its - * originating allocator. An empty pointer reports a parse failure. + * provenance may differ. Every pJsonParser DOM operation returns the parsed + * document by value, bound to the chosen allocator and freed on destruction; + * pass a pJsonParser::Error to distinguish failure from a successfully parsed + * literal null. * * operator[] is the auto-vivifying builder API. For observation without - * mutation, use find(), findPointer(), hasKey(), hasIndex(), or tryGet(). + * mutation, use find(), findIndex(), findPointer(), hasKey(), hasIndex(), at(), + * contains(), forEachMember()/forEachElement(), or tryGet(). * tryGet() requires the requested stored type and leaves its output unchanged - * on failure; only an integer-to-double widening conversion is permitted. - * Containers expose query + * on failure; integer-to-double widening and exact signed/unsigned integer + * reads are permitted. Containers expose query * and child-lookup operations rather than their raw storage types. * - * getType() distinguishes jsonNumberInt from jsonNumberDouble and reports - * objects as jsonObject. Numeric assignment and append overloads accept - * int64_t or double. Configure serialization through SerializeOptions; the - * compact toString() and write() overloads take no formatting boolean, and - * SerializeOptions::maxOutputBytes bounds generated output. PatchOptions - * bounds transactional JSON Patch and Merge Patch amplification. Invalid UTF-8 + * getType() distinguishes jsonNumberInt, jsonNumberUInt, and jsonNumberDouble + * and reports objects as jsonObject. Numeric assignment and append overloads + * accept the standard non-character integral and floating-point types. + * Configure serialization through + * SerializeOptions; the compact toString() and write() overloads take no + * formatting boolean, and SerializeOptions::maxOutputBytes bounds generated + * output. A stored non-finite double fails serialization by default; select an + * explicit SerializeOptions::NonFinitePolicy to map it. PatchOptions bounds + * transactional JSON Patch and Merge Patch amplification. Invalid UTF-8 * and logical output-size failures are detected before write() emits bytes. * * @see https://github.com/Pico-Developer/pjson/tree/main/docs Tutorials @@ -56,6 +62,23 @@ * @see migration-rapidjson */ +/** + * @class ByteDance::pJsonParser + * @brief Configured parser that produces pjson values or SAX events. + * + * pJsonParser is declared in . It owns parser options and + * borrows the selected pjson::Allocator, which must outlive the parser and all + * DOM values it creates. Parsing returns pjson by value. The parser stores no + * per-call mutable state and may be reused; callers must provide separate Error + * objects and SAX handlers for concurrent calls. The dependency is one-way: + * pJsonParser uses pjson, while the core pjson header and implementation do not + * include or call the parser. + * + * @see ByteDance::pJsonParser::Options + * @see ByteDance::pJsonParser::Error + * @see ByteDance::pJsonParser::SaxHandler + */ + /** * @struct ByteDance::pjson::Allocator * @brief Runtime allocation interface for persistent pjson DOM storage. @@ -63,27 +86,70 @@ * The allocator is borrowed and must outlive every bound value. allocate() must * return non-null storage honoring the requested size and alignment or throw; * deallocate() receives matching metadata and must not throw. The interface - * covers pjson nodes and string/array/object wrapper objects, not their internal - * standard-library allocations or transient algorithm scratch space. + * covers pjson nodes, non-null opaque implementation objects, and + * string/array/object wrapper objects, not their internal standard-library + * allocations or transient algorithm scratch space. */ /** - * @struct ByteDance::pjson::ValueDeleter - * @brief Stateless deleter for owning roots returned by DOM parsing. + * @class ByteDance::pJsonSchemaValidator + * @brief Validates pjson values using the subset or Draft 2020-12 contract. * - * The deleter obtains allocator provenance from the value. It does not own or - * extend the allocator's lifetime. + * pJsonSchemaValidator is a standalone helper declared in and + * built from focused private schema translation units. It is a pure consumer of pjson's public API and + * never touches the DOM's internal storage, so the core pjson class carries no + * schema or regex state. The implementation is isolated in its own translation + * unit for future optional-library packaging. + * Construct one validator from a schema (deep-copied on construction) and reuse + * it to validate many instances; validation never throws and never mutates its + * inputs. Default options implement pjson's explicitly named subset dialect; + * Options::draft2020() selects the required Draft 2020-12 vocabularies, bundled + * meta-schema validation, and per-resource vocabulary activation. A root + * `$schema` or Options::defaultDialectUri selects the effective dialect; + * unsupported dialects and required vocabularies fail compilation. Inspect isSchemaValid(), + * schemaErrors(), and dialect() before trusting validation results. + * + * @see ByteDance::pJsonSchemaValidator::Options + * @see ByteDance::pJsonSchemaValidator::Error */ /** - * @struct ByteDance::pjson::SchemaOptions + * @struct ByteDance::pJsonSchemaValidator::Options * @brief Bounds schema-validation work and controls optional format checks. * - * Recursive validation depth defaults to an absolute hard ceiling of 64. A + * Recursive validation depth defaults to an absolute hard ceiling of 32. A * zero value selects that ceiling, and larger values are clamped to it so no * caller configuration can make recursive keyword evaluation exceed the * conservative native-stack bound. Other zero-valued validation budgets retain their * documented hard ceilings; only regex byte limits use zero as unlimited. + * defaultDialectUri defaults to documentedSubsetDialectUri(); an empty value + * selects the same default. retrievalUri supplies the root's base when `$id` is + * absent. resolver is the only way to load an external + * schema resource; pjson never performs I/O. maxResolvedDocuments and + * maxResolvedBytes bound resolver amplification. modernSubset() enables modern + * `$ref` sibling semantics and the Draft 2020-12 annotation-only `format` + * default, while normal options preserve legacy Draft 7-compatible behavior. + * draft2020() additionally selects the official dialect and meta-schema + * compilation. + */ + +/** + * @struct ByteDance::pJsonSchemaValidator::Error + * @brief One schema or instance-validation failure. + * + * Provides a stable code, separate instance and schema locations, the + * triggering keyword, human-readable text, and optional nested combinator + * causes. category distinguishes compilation from instance validation. + */ + +/** + * @struct ByteDance::pjson::SerializeError + * @brief Structured result for non-throwing serialization. + * + * Code distinguishes invalid UTF-8, rejected non-finite numbers, output limits, + * allocation failure, physical stream failure, and unexpected internal errors. + * String output is transactional; logical stream failures are detected before + * emission, while a physical sink failure may leave a prefix. */ /** diff --git a/docs/scripts/validate-reference.py b/docs/scripts/validate-reference.py index ea5af4d..4eb2a31 100644 --- a/docs/scripts/validate-reference.py +++ b/docs/scripts/validate-reference.py @@ -17,29 +17,27 @@ "ByteDance", "ByteDance::pjson", "ByteDance::pjson::Allocator", - "ByteDance::pjson::ValueDeleter", - "ByteDance::pjson::ParseOptions", - "ByteDance::pjson::ParseError", "ByteDance::pjson::PointerError", "ByteDance::pjson::PatchError", "ByteDance::pjson::PatchOptions", "ByteDance::pjson::SerializeOptions", + "ByteDance::pjson::SerializeError", "ByteDance::pjson::StringView", - "ByteDance::pjson::SaxHandler", - "ByteDance::pjson::SchemaError", - "ByteDance::pjson::SchemaOptions", + "ByteDance::pJsonParser", + "ByteDance::pJsonParser::Options", + "ByteDance::pJsonParser::Error", + "ByteDance::pJsonParser::SaxHandler", + "ByteDance::pJsonSchemaValidator", + "ByteDance::pJsonSchemaValidator::Error", + "ByteDance::pJsonSchemaValidator::Options", } # Baseline overload counts make accidental omissions visible. APIs whose exact # shape is part of the breaking-contract check are also listed below by type. REQUIRED_MEMBERS = { "getVersion": 1, - "parse": 8, - "parseStream": 4, - "parseSax": 4, - "parseSaxStream": 2, - "toString": 2, - "write": 2, + "toString": 3, + "write": 3, "getType": 1, "isNull": 1, "isString": 1, @@ -49,32 +47,75 @@ "isBool": 1, "isArray": 1, "isObject": 1, + "isUInt": 1, + "isInteger": 1, "getAllocator": 1, "canSwap": 1, - "tryGet": 20, + "tryGet": 24, "size": 1, "empty": 1, "clear": 1, "keys": 1, "hasKey": 2, + "contains": 2, "hasIndex": 1, "find": 6, + "findIndex": 2, + "forEachMember": 2, + "forEachElement": 2, + "at": 4, + "null": 1, + "object": 1, + "array": 1, + "pushBack": 2, + "insertOrAssign": 2, + "reserve": 1, "escapePointerToken": 1, "findPointer": 8, - "operator[]": 3, - "operator=": 11, - "operator+=": 9, + "operator[]": 4, + "operator=": 30, + "operator+=": 27, "erase": 3, "applyPatch": 2, "applyMergePatch": 2, "operator==": 1, "operator!=": 1, +} + +REQUIRED_PARSER_MEMBERS = { + "pJsonParser": 2, + "options": 1, + "allocator": 1, + "parse": 4, + "parseStream": 2, + "parseSax": 4, + "parseSaxStream": 2, +} + +REQUIRED_SCHEMA_VALIDATOR_MEMBERS = { + "pJsonSchemaValidator": 1, "validate": 2, + "documentedSubsetDialectUri": 1, + "documentedSubsetVocabularyUri": 1, + "isSchemaValid": 1, + "schemaErrors": 1, + "dialect": 1, + "schema": 1, + "options": 1, +} + +REQUIRED_SCHEMA_OPTIONS_MEMBERS = { + "Options": 1, + "trustedRegex": 1, + "strict": 1, + "modernSubset": 1, } REMOVED_PUBLIC_MEMBERS = { "PJSONARRAY", "PJSONMAP", + "ArrayStorage", + "ObjectStorage", "getInt64", "getDouble", "getBool", @@ -82,19 +123,54 @@ "getArray", "getMap", "getIfExist", + "KeyOrder", + "AscendingKeys", + "DescendingKeys", + "keyOrder", "getArrayValues", "getInt64Or", "getDoubleOr", "getBoolOr", "getStringOr", - "at", "EncodeForJSON", "EncodeBase64ForJSON", "DecodeFromJSON", "DecodeBase64FromJSON", + "parse", + "parseStream", + "parseSax", + "parseSaxStream", } EXPECTED_PUBLIC_ENUMS = { + ("ByteDance::pJsonSchemaValidator::Error", "Category"): { + "InstanceValidation", + "SchemaCompilation", + }, + ("ByteDance::pJsonSchemaValidator::Error", "Code"): { + "None", + "FalseSchema", + "TypeMismatch", + "ConstMismatch", + "EnumMismatch", + "NumericConstraint", + "StringConstraint", + "ArrayConstraint", + "ObjectConstraint", + "FormatMismatch", + "CombinatorMismatch", + "ReferenceFailure", + "ReferenceCycle", + "RegexFailure", + "UnsupportedKeyword", + "InvalidSchema", + "UnsupportedDialect", + "UnsupportedVocabulary", + "ResolverFailure", + "ResourceLimit", + "AllocationFailure", + "InternalError", + }, ("ByteDance::pjson", "jsonType"): { "jsonNull", "jsonString", @@ -103,18 +179,38 @@ "jsonBoolean", "jsonArray", "jsonObject", + "jsonNumberUInt", }, ("ByteDance::pjson::Allocator", "AllocationKind"): { "NodeAllocation", "StringAllocation", "ArrayAllocation", "ObjectAllocation", + "ImplementationAllocation", }, - ("ByteDance::pjson::ParseOptions", "DuplicateKeyPolicy"): { + ("ByteDance::pJsonParser::Options", "DuplicateKeyPolicy"): { "RejectDuplicateKeys", "KeepFirstDuplicate", "KeepLastDuplicate", }, + ("ByteDance::pJsonParser::Options", "NumberPolicy"): { + "RejectUnrepresentableNumbers", + "AllowLossyNumbers", + }, + ("ByteDance::pJsonParser::Error", "Code"): { + "None", + "Syntax", + "InvalidEncoding", + "DuplicateKey", + "NumberRange", + "DepthLimit", + "InputLimit", + "NodeLimit", + "AllocationFailure", + "StreamError", + "CallbackError", + "InvalidArgument", + }, ("ByteDance::pjson::PointerError", "Code"): { "Ok", "InvalidSyntax", @@ -148,39 +244,64 @@ "AllocationFailure", "InternalError", }, - ("ByteDance::pjson::SerializeOptions", "KeyOrder"): { - "AscendingKeys", - "DescendingKeys", + ("ByteDance::pjson::SerializeOptions", "NonFinitePolicy"): { + "RejectNonFinite", + "NonFiniteToNull", + "NonFiniteToString", + }, + ("ByteDance::pjson::SerializeError", "Code"): { + "None", + "InvalidUtf8", + "NonFiniteNumber", + "OutputLimit", + "AllocationFailure", + "StreamFailure", + "InternalError", }, } EXPECTED_PARAMETER_TYPES = { + "operator[]": { + ("const std::string&",), + ("const char*",), + ("int",), + ("size_t",), + }, "tryGet": { ("int64_t&",), + ("uint64_t&",), ("double&",), ("bool&",), ("std::string&",), ("StringView&",), ("const std::string&", "int64_t&"), + ("const std::string&", "uint64_t&"), ("const std::string&", "double&"), ("const std::string&", "bool&"), ("const std::string&", "std::string&"), ("const std::string&", "StringView&"), ("const char*", "int64_t&"), + ("const char*", "uint64_t&"), ("const char*", "double&"), ("const char*", "bool&"), ("const char*", "std::string&"), ("const char*", "StringView&"), ("int", "int64_t&"), + ("int", "uint64_t&"), ("int", "double&"), ("int", "bool&"), ("int", "std::string&"), ("int", "StringView&"), }, - "toString": {(), ("const SerializeOptions&",)}, + "toString": { + (), + ("const SerializeOptions&",), + ("std::string&", "SerializeError&", "const SerializeOptions&"), + }, "write": { ("std::ostream&",), ("std::ostream&", "const SerializeOptions&"), + ("std::ostream&", "SerializeError&", "const SerializeOptions&"), }, "applyPatch": { ("const pjson&", "const PatchOptions&"), @@ -193,30 +314,115 @@ "operator=": { ("const pjson&",), ("pjson&&",), + ("std::nullptr_t",), ("const std::string&",), ("const char*",), ("const bool",), - ("const int64_t",), + ("const int",), + ("const unsigned int",), + ("const short",), + ("const unsigned short",), + ("const long",), + ("const unsigned long",), + ("const long long",), + ("const unsigned long long",), + ("const float",), ("const double",), + ("const long double",), ("const std::vector&",), ("const std::vector&",), - ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), ("const std::vector&",), + ("const std::vector&",), }, "operator+=": { ("const std::string&",), ("const char*",), ("const bool",), - ("const int64_t",), + ("const int",), + ("const unsigned int",), + ("const short",), + ("const unsigned short",), + ("const long",), + ("const unsigned long",), + ("const long long",), + ("const unsigned long long",), + ("const float",), ("const double",), + ("const long double",), ("const std::vector&",), ("const std::vector&",), - ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), + ("const std::vector&",), ("const std::vector&",), + ("const std::vector&",), }, } REQUIRED_PUBLIC_FIELDS = { + "ByteDance::pJsonParser::Options": { + "maxDepth", + "maxNodes", + "maxInputBytes", + "duplicateKeys", + "numberPolicy", + }, + "ByteDance::pJsonParser::Error": { + "ok", + "code", + "offset", + "line", + "column", + "message", + }, + "ByteDance::pjson::SerializeError": { + "code", + "message", + }, + "ByteDance::pJsonSchemaValidator::Error": { + "code", + "category", + "instanceLocation", + "schemaLocation", + "keyword", + "message", + "causes", + }, + "ByteDance::pJsonSchemaValidator::Options": { + "maxRegexPatternBytes", + "maxRegexSubjectBytes", + "allowUnsafeRegex", + "maxValidationDepth", + "maxRefResolutions", + "maxValidationWork", + "maxErrors", + "stopAfterFirstError", + "collectNestedCauses", + "validateFormats", + "strictSubset", + "refSiblings", + "retrievalUri", + "defaultDialectUri", + "resolver", + "resolverContext", + "maxResolvedDocuments", + "maxResolvedBytes", + }, "ByteDance::pjson::PatchOptions": { "maxOperations", "maxClonedNodes", @@ -228,7 +434,7 @@ "indentWidth", "indentCharacter", "escapeNonAscii", - "keyOrder", + "nonFinite", "maxOutputBytes", }, } @@ -243,6 +449,7 @@ "PJSON_VERSION_MAJOR", "PJSON_VERSION_MINOR", "PJSON_VERSION_PATCH", + "PJSON_ABI_VERSION", } REQUIRED_ALLOCATOR_MEMBERS = { "AllocationKind": 1, @@ -336,6 +543,46 @@ def main() -> int: if members[name] < minimum: errors.append(f"{name}: expected at least {minimum} overload(s), found {members[name]}") + parser_node = compounds.get("ByteDance::pJsonParser") + parser_members: collections.Counter[str] = collections.Counter() + if parser_node is not None: + parser_members.update( + node.findtext("name", default="") for node in parser_node.findall("member") + ) + for name, minimum in REQUIRED_PARSER_MEMBERS.items(): + if parser_members[name] < minimum: + errors.append( + f"pJsonParser::{name}: expected at least {minimum}, " + f"found {parser_members[name]}" + ) + + schema_validator_node = compounds.get("ByteDance::pJsonSchemaValidator") + schema_validator_members: collections.Counter[str] = collections.Counter() + if schema_validator_node is not None: + schema_validator_members.update( + node.findtext("name", default="") + for node in schema_validator_node.findall("member") + ) + for name, minimum in REQUIRED_SCHEMA_VALIDATOR_MEMBERS.items(): + if schema_validator_members[name] < minimum: + errors.append( + f"pJsonSchemaValidator::{name}: expected at least {minimum}, " + f"found {schema_validator_members[name]}" + ) + + schema_options_node = compounds.get("ByteDance::pJsonSchemaValidator::Options") + schema_options_members: collections.Counter[str] = collections.Counter() + if schema_options_node is not None: + schema_options_members.update( + node.findtext("name", default="") for node in schema_options_node.findall("member") + ) + for name, minimum in REQUIRED_SCHEMA_OPTIONS_MEMBERS.items(): + if schema_options_members[name] < minimum: + errors.append( + f"pJsonSchemaValidator::Options::{name}: expected at least {minimum}, " + f"found {schema_options_members[name]}" + ) + def compound_definition(name: str): """Load one compound XML definition when its index entry exists.""" node = compounds.get(name) @@ -361,16 +608,6 @@ def compound_definition(name: str): f"Allocator::{name}: expected at least {minimum}, " f"found {allocator_members[name]}" ) - deleter_definition = compound_definition("ByteDance::pjson::ValueDeleter") - if deleter_definition is not None: - undocumented = undocumented_public_members(deleter_definition) - if undocumented: - errors.append( - "undocumented ValueDeleter members: " + ", ".join(undocumented) - ) - if not deleter_definition.findall(".//memberdef[@prot='public'][name='operator()']"): - errors.append("missing ValueDeleter::operator()") - for compound_name, expected_fields in REQUIRED_PUBLIC_FIELDS.items(): definition = compound_definition(compound_name) if definition is None: @@ -382,8 +619,6 @@ def compound_definition(name: str): for field in sorted(expected_fields - actual_fields): errors.append(f"missing public field {compound_name}::{field}") - if members["unique_ptr"] < 1: - errors.append("missing allocator-aware pjson::unique_ptr typedef") if members["pjson"] < 6: errors.append("pjson: expected six allocator/default constructors") if members["swap"] < 1 or members["copyFrom"] < 1: @@ -419,20 +654,6 @@ def compound_definition(name: str): f"returns {result_type}, expected bool" ) - dom_parse_members = [ - member - for member in public_members - if member.findtext("name", default="") in {"parse", "parseStream"} - ] - for member in dom_parse_members: - result_type = normalized_xml_type(member.find("type")) - if result_type not in {"unique_ptr", "pjson::unique_ptr"}: - name = member.findtext("name", default="") - errors.append( - f"{signature(name, parameter_types(member))} returns {result_type}, " - "expected pjson::unique_ptr" - ) - constructors = [ member.findtext("argsstring", default="") for member in pjson_definition.findall(".//memberdef[@prot='public']") @@ -445,25 +666,37 @@ def compound_definition(name: str): f"found {len(allocator_constructors)}" ) - parse_signatures = [ - member.findtext("argsstring", default="") - for member in pjson_definition.findall(".//memberdef[@prot='public']") - if member.findtext("name", default="") in {"parse", "parseStream"} - ] - allocator_signatures = [signature for signature in parse_signatures if "Allocator &" in signature] - if len(allocator_signatures) < 6: + + parser_definition = compound_definition("ByteDance::pJsonParser") + if parser_definition is not None: + undocumented = undocumented_public_members(parser_definition) + if undocumented: errors.append( - f"allocator-aware parse APIs: expected six signatures, " - f"found {len(allocator_signatures)}" + "undocumented pJsonParser members: " + ", ".join(undocumented) ) + parser_public_members = parser_definition.findall(".//memberdef[@prot='public']") + for member in parser_public_members: + if member.findtext("name", default="") in {"parse", "parseStream"}: + result_type = normalized_xml_type(member.find("type")) + if result_type != "pjson": + name = member.findtext("name", default="") + errors.append( + f"pJsonParser::{signature(name, parameter_types(member))} returns " + f"{result_type}, expected pjson" + ) # Pin every public enum nested anywhere under pjson. Scanning all public # pjson compounds, rather than only the currently expected owners, also # makes a newly added enum fail until the reference contract is updated. actual_public_enums: dict[tuple[str, str], set[str]] = {} for compound_name in sorted(compounds): - if compound_name != "ByteDance::pjson" and not compound_name.startswith( - "ByteDance::pjson::" + if ( + compound_name != "ByteDance::pjson" + and not compound_name.startswith("ByteDance::pjson::") + and compound_name != "ByteDance::pJsonParser" + and not compound_name.startswith("ByteDance::pJsonParser::") + and compound_name != "ByteDance::pJsonSchemaValidator" + and not compound_name.startswith("ByteDance::pJsonSchemaValidator::") ): continue definition = compound_definition(compound_name) diff --git a/examples/src/02_building_values.cpp b/examples/src/02_building_values.cpp index c84e37c..f3e7c01 100644 --- a/examples/src/02_building_values.cpp +++ b/examples/src/02_building_values.cpp @@ -54,12 +54,11 @@ int main() { // --- Serialization options -------------------------------------------- // Start with pretty-print defaults, then make the relevant layout choices - // explicit. Key ordering applies independently at every object level. + // explicit. Object member order is intentionally unspecified. pjson::SerializeOptions options = pjson::SerializeOptions::prettyPrinted(); options.indentWidth = 2; options.indentCharacter = ' '; options.escapeNonAscii = false; - options.keyOrder = pjson::SerializeOptions::AscendingKeys; options.maxOutputBytes = size_t(64) * 1024 * 1024; std::cout << doc.toString(options) << "\n"; return 0; diff --git a/examples/src/03_parsing_and_reading.cpp b/examples/src/03_parsing_and_reading.cpp index 6df4db0..20751ee 100644 --- a/examples/src/03_parsing_and_reading.cpp +++ b/examples/src/03_parsing_and_reading.cpp @@ -9,6 +9,7 @@ // Referenced by docs/03-parsing-and-reading.md. // #include "pjson.h" +#include "pjson_parser.h" #include @@ -25,15 +26,16 @@ int main() { "friends": [ {"name":"Bob"}, {"name":"Cid"} ] })"; - // Every DOM parse overload returns pjson::unique_ptr; it is empty on failure. - pjson::ParseError parseError; - pjson::unique_ptr doc = pjson::parse(text, parseError); - if (!doc) { + // Every DOM parse overload returns a pjson value; pass a pJsonParser::Error to + // learn whether parsing succeeded. + pJsonParser::Error parseError; + pjson doc = pJsonParser().parse(text, parseError); + if (!parseError.ok) { std::cerr << parseError.line << ':' << parseError.column << ": " << parseError.message << "\n"; return 1; } - const pjson& j = *doc; + const pjson& j = doc; // --- Strict scalar reads ---------------------------------------------- // StringView borrows the stored bytes, so write the explicit length rather @@ -60,7 +62,7 @@ int main() { if (scoresNode && scoresNode->isArray()) { for (size_t i = 0; i < scoresNode->size(); ++i) { int64_t value = 0; - const pjson* score = scoresNode->find(static_cast(i)); + const pjson* score = scoresNode->findIndex(i); if (score && score->tryGet(value)) std::cout << " " << value; } @@ -83,7 +85,7 @@ int main() { if (const pjson* friendsNode = j.find("friends")) { if (friendsNode->isArray()) { for (size_t i = 0; i < friendsNode->size(); ++i) { - const pjson* friend_ = friendsNode->find(static_cast(i)); + const pjson* friend_ = friendsNode->findIndex(i); pjson::StringView friendName; if (friend_ && friend_->tryGet("name", friendName)) { std::cout << " "; diff --git a/examples/src/04_editing.cpp b/examples/src/04_editing.cpp index 2b585d7..592759a 100644 --- a/examples/src/04_editing.cpp +++ b/examples/src/04_editing.cpp @@ -9,6 +9,7 @@ // elements, and re-serialize. Referenced by docs/04-editing.md. // #include "pjson.h" +#include "pjson_parser.h" #include #include @@ -18,16 +19,17 @@ using namespace ByteDance; // Parses a seed document, mutates it through several APIs, and prints the result. int main() { // --- Parse a mutable document ----------------------------------------- - auto doc = pjson::parse(R"({ + pJsonParser::Error parseError; + pjson j = pJsonParser().parse(R"({ "user": { "name": "Ada", "roles": ["admin", "dev"] }, "count": 2, "deprecated": true - })"); - if (!doc) { + })", + parseError); + if (!parseError.ok) { std::cerr << "parse failed\n"; return 1; } - pjson& j = *doc; // --- Direct DOM edits ------------------------------------------------- // Change a value in place. @@ -49,31 +51,30 @@ int main() { // --- Standards-based transformations --------------------------------- // Apply a sequence of JSON Pointer edits atomically (RFC 6902): the test // must succeed before the reviewer role is appended. - pjson::ParseError parseError; - auto patch = pjson::parse(R"([ + pjson patch = pJsonParser().parse(R"([ {"op":"test", "path":"/count", "value":"two"}, {"op":"add", "path":"/user/roles/-", "value":"reviewer"} ])", - parseError); - if (!patch) { + parseError); + if (!parseError.ok) { std::cerr << "could not parse patch: " << parseError.message << "\n"; return 1; } pjson::PatchError error; pjson::PatchOptions limits; - if (!j.applyPatch(*patch, error, limits)) { + if (!j.applyPatch(patch, error, limits)) { std::cerr << "patch failed at operation " << error.opIndex << ": " << error.message << "\n"; return 1; } // Merge Patch recursively updates objects; null removes an object member. // Removing a missing member, as here, is a successful no-op. - auto merge = pjson::parse(R"({"user":{"nickname":null}})", parseError); - if (!merge) { + pjson merge = pJsonParser().parse(R"({"user":{"nickname":null}})", parseError); + if (!parseError.ok) { std::cerr << "could not parse merge patch: " << parseError.message << "\n"; return 1; } - if (!j.applyMergePatch(*merge, error, limits)) { + if (!j.applyMergePatch(merge, error, limits)) { std::cerr << "merge patch failed: " << error.message << "\n"; return 1; } diff --git a/examples/src/05_parsing_and_errors.cpp b/examples/src/05_parsing_and_errors.cpp index 8e7d495..f328bf6 100644 --- a/examples/src/05_parsing_and_errors.cpp +++ b/examples/src/05_parsing_and_errors.cpp @@ -10,21 +10,22 @@ // Referenced by docs/05-parsing-and-errors.md. // #include "pjson.h" +#include "pjson_parser.h" #include using namespace ByteDance; // Attempts one parse and prints either its compact form or the precise failure -// location. Reporting parse APIs reset ParseError on entry. -static void tryParse(const char* label, const std::string& text, const pjson::ParseOptions& opt) { - pjson::ParseError err; - auto doc = pjson::parse(text, err, opt); +// location. Reporting parse APIs reset pJsonParser::Error on entry. +static void tryParse(const char* label, const std::string& text, const pJsonParser::Options& opt) { + pJsonParser::Error err; + pjson doc = pJsonParser(opt).parse(text, err); std::cout << label << ": "; - if (doc) { + if (err.ok) { pjson::SerializeOptions compact; compact.maxOutputBytes = size_t(64) * 1024 * 1024; - std::cout << "OK -> " << doc->toString(compact) << "\n"; + std::cout << "OK -> " << doc.toString(compact) << "\n"; } else { std::cout << "FAILED at " << err.line << ':' << err.column << " (byte " << err.offset << ", " << err.message << ")\n"; @@ -34,7 +35,7 @@ static void tryParse(const char* label, const std::string& text, const pjson::Pa // Exercises invalid syntax, duplicate-key policy, and a nesting-depth budget. int main() { // --- JSON syntax ------------------------------------------------------- - pjson::ParseOptions defaults; + pJsonParser::Options defaults; tryParse("trailing comma", "[1, 2, ]", defaults); tryParse("uppercase keyword", "NULL", defaults); std::string rawTab = "\"a\tb\""; @@ -42,13 +43,13 @@ int main() { // Duplicate policy does not relax the JSON grammar. tryParse("duplicate (reject)", R"({"id":1,"id":2})", defaults); - pjson::ParseOptions keepLast; - keepLast.duplicateKeys = pjson::ParseOptions::KeepLastDuplicate; + pJsonParser::Options keepLast; + keepLast.duplicateKeys = pJsonParser::Options::KeepLastDuplicate; tryParse("duplicate (keep last)", R"({"id":1,"id":2})", keepLast); // --- Resource limits -------------------------------------------------- // Guard against runaway nesting. - pjson::ParseOptions shallow; + pJsonParser::Options shallow; shallow.maxDepth = 3; tryParse("deep nesting (maxDepth=3)", "[[[[1]]]]", shallow); return 0; diff --git a/examples/src/06_schema_validation.cpp b/examples/src/06_schema_validation.cpp index 7ca9aaf..640d52c 100644 --- a/examples/src/06_schema_validation.cpp +++ b/examples/src/06_schema_validation.cpp @@ -5,11 +5,13 @@ //===----------------------------------------------------------------------===// // 06 — Schema validation // -// Validate a document against a JSON-Schema-subset schema (itself a pjson -// value), collecting applicable failures within configured budgets. +// Validate a document against a schema (itself a pjson value), using pjson's +// default subset dialect and collecting failures within configured budgets. // Referenced by docs/06-schema-validation.md. // #include "pjson.h" +#include "pjson_parser.h" +#include "pjson_schema.h" #include #include @@ -22,7 +24,8 @@ int main() { // --- Define the schema ------------------------------------------------- // Local $defs keep shared constraints in one place; $ref resolves them by // RFC 6901 fragment pointers within this same schema document. - auto schema = pjson::parse(R"({ + pJsonParser::Error parseError; + pjson schema = pJsonParser().parse(R"({ "$defs": { "displayName": { "type": "string", "minLength": 1 }, "emailAddress": { "type": "string", "pattern": "@" } @@ -40,43 +43,58 @@ int main() { "items": { "type": "string", "enum": ["admin", "user", "guest"] } } } - })"); + })", + parseError); // --- Validate a conforming instance ----------------------------------- - auto good = pjson::parse(R"({ + pJsonParser::Error goodError; + pjson good = pJsonParser().parse(R"({ "name": "Ada", "age": 36, "email": "ada@example.com", "joined": "2025-01-02", "roles": ["admin"] - })"); - if (!schema || !good) { + })", + goodError); + if (!parseError.ok || !goodError.ok) { std::cerr << "could not parse schema or valid example\n"; return 1; } // These limits bound traversal and reference work. Known string formats, // such as the date above, are checked because validateFormats is enabled. - pjson::SchemaOptions options; + // The schema is compiled once into a reusable validator; validate() then + // checks any number of instances against it. + pJsonSchemaValidator::Options options; options.maxValidationDepth = 64; options.maxRefResolutions = 1024; options.validateFormats = true; - std::cout << "good is valid: " << (good->validate(*schema, options) ? "yes" : "no") << "\n"; + pJsonSchemaValidator validator(schema, options); + if (!validator.isSchemaValid()) { + for (const pJsonSchemaValidator::Error& e : validator.schemaErrors()) { + std::cerr << "invalid schema at " << e.schemaLocation << ": " << e.message << "\n"; + } + return 1; + } + std::cout << "good is valid: " << (validator.validate(good) ? "yes" : "no") << "\n"; // --- Collect failures for a non-conforming instance ------------------- - auto bad = pjson::parse(R"({ + pJsonParser::Error badError; + pjson bad = pJsonParser().parse(R"({ "name": "", "age": 200, "email": "nope", "joined": "2025-01-02", "roles": ["root"], "extra": 1 - })"); - if (!bad) { + })", + badError); + if (!badError.ok) { std::cerr << "could not parse invalid example\n"; return 1; } - std::vector errors; + std::vector errors; // This overload appends applicable failures up to the configured budget; // each path is an RFC 6901 JSON Pointer identifying the offending value. - bool ok = bad->validate(*schema, errors, options); + bool ok = validator.validate(bad, errors); std::cout << "bad is valid: " << (ok ? "yes" : "no") << "\n"; std::cout << "failures:\n"; - for (const pjson::SchemaError& e : errors) { - std::cout << " " << (e.path.empty() ? "(root)" : e.path) << ": " << e.message << "\n"; + for (const pJsonSchemaValidator::Error& e : errors) { + std::cout << " " << (e.instanceLocation.empty() ? "(root)" : e.instanceLocation) << ": " + << e.message << "\n"; } return 0; } diff --git a/examples/src/07_address_book.cpp b/examples/src/07_address_book.cpp index 973cb70..e6ab1c4 100644 --- a/examples/src/07_address_book.cpp +++ b/examples/src/07_address_book.cpp @@ -10,8 +10,9 @@ // result. Referenced by docs/07-capstone-address-book.md. // #include "pjson.h" +#include "pjson_parser.h" +#include "pjson_schema.h" -#include #include #include #include @@ -22,8 +23,8 @@ namespace { // Builds the schema every contact must satisfy. The embedded literal is // fixed application data, so parsing it is expected to succeed. - pjson::unique_ptr contactSchema() { - return pjson::parse(R"({ + pjson contactSchema() { + return pJsonParser().parse(R"({ "type": "object", "required": ["id", "name", "emails"], "properties": { @@ -37,22 +38,19 @@ namespace { // Adds a deep copy of a valid contact to the book. Invalid contacts leave // the book unchanged and produce one line for every validation failure. - bool addContact(pjson& book, const pjson& schema, const pjson& contact) { - std::vector errors; - if (!contact.validate(schema, errors)) { + bool addContact(pjson& book, const pJsonSchemaValidator& validator, const pjson& contact) { + std::vector errors; + if (!validator.validate(contact, errors)) { std::cout << " rejected contact:\n"; - for (const pjson::SchemaError& e : errors) { - std::cout << " " << (e.path.empty() ? "(root)" : e.path) << ": " << e.message - << "\n"; + for (const pJsonSchemaValidator::Error& e : errors) { + std::cout << " " << (e.instanceLocation.empty() ? "(root)" : e.instanceLocation) + << ": " << e.message << "\n"; } return false; } - // Append by assigning to the next array index (there is no operator+= for a - // whole pjson value; indexing auto-extends the array, then we copy in). - pjson& contacts = book["contacts"]; - if (contacts.size() > static_cast(INT_MAX)) - return false; - contacts[static_cast(contacts.size())] = contact; + // Append the whole contact value; pushBack promotes to an array and + // deep-copies the supplied value. + book["contacts"].pushBack(contact); return true; } @@ -61,11 +59,17 @@ namespace { // Runs the address-book workflow: initialize, ingest, reject, edit, and query. int main() { // --- Initialize the store --------------------------------------------- - pjson::unique_ptr schema = contactSchema(); - if (!schema) { + pjson schema = contactSchema(); + if (schema.isNull()) { std::cerr << "could not parse the embedded schema\n"; return 1; } + // Compile the schema once; every contact is checked against this validator. + pJsonSchemaValidator validator(schema); + if (!validator.isSchemaValid()) { + std::cerr << "embedded contact schema is unsupported\n"; + return 1; + } // Start an empty address book. pjson book; @@ -80,27 +84,30 @@ int main() { ada["emails"] += "ada@example.com"; ada["tags"] += "pioneer"; std::cout << "adding Ada...\n"; - addContact(book, *schema, ada); + addContact(book, validator, ada); // 2) Accept a contact that arrives as a JSON payload. std::cout << "adding incoming payload...\n"; - auto incoming = pjson::parse(R"({ + pJsonParser::Error incomingError; + pjson incoming = pJsonParser().parse(R"({ "id": 2, "name": "Bob", "emails": ["bob@example.com", "b@work.com"] - })"); - if (!incoming) { + })", + incomingError); + if (!incomingError.ok) { std::cerr << "could not parse incoming contact\n"; return 1; } - addContact(book, *schema, *incoming); + addContact(book, validator, incoming); // 3) Reject an invalid contact. std::cout << "adding invalid contact...\n"; - auto invalid = pjson::parse(R"({ "id": 0, "name": "", "emails": [] })"); - if (!invalid) { + pJsonParser::Error invalidError; + pjson invalid = pJsonParser().parse(R"({ "id": 0, "name": "", "emails": [] })", invalidError); + if (!invalidError.ok) { std::cerr << "could not parse invalid-contact fixture\n"; return 1; } - addContact(book, *schema, *invalid); + addContact(book, validator, invalid); // --- Edit and query --------------------------------------------------- // 4) Edit the store: give Ada a second email, then look someone up. @@ -109,7 +116,7 @@ int main() { std::cout << "\nlookup id=2: "; const pjson* contacts = book.find("contacts"); for (size_t i = 0; contacts && i < contacts->size(); ++i) { - const pjson* contact = contacts->find(static_cast(i)); + const pjson* contact = contacts->findIndex(i); int64_t id = 0; std::string name; if (contact && contact->tryGet("id", id) && id == int64_t(2) && diff --git a/examples/src/08_streaming.cpp b/examples/src/08_streaming.cpp index 0916038..1e82881 100644 --- a/examples/src/08_streaming.cpp +++ b/examples/src/08_streaming.cpp @@ -7,6 +7,7 @@ // Referenced by docs/11-streaming.md. // #include "pjson.h" +#include "pjson_parser.h" #include #include @@ -17,7 +18,7 @@ using namespace ByteDance; // SAX handler that summarizes every JSON number encountered anywhere in the // stream. Unoverridden callbacks accept and ignore non-numeric events. -struct NumberSummary : pjson::SaxHandler { +struct NumberSummary : pJsonParser::SaxHandler { size_t count = 0; double total = 0.0; @@ -42,8 +43,8 @@ int main() { // --- Incremental input ------------------------------------------------- std::istringstream input(R"({"readings":[10,12.5,8,9.5]})"); NumberSummary summary; - pjson::ParseError error; - if (!pjson::parseSaxStream(input, summary, error)) { + pJsonParser::Error error; + if (!pJsonParser().parseSaxStream(input, summary, error)) { std::cerr << error.line << ':' << error.column << ": " << error.message << '\n'; return 1; } diff --git a/examples/src/09_custom_allocator.cpp b/examples/src/09_custom_allocator.cpp index 66ad03d..bd450c3 100644 --- a/examples/src/09_custom_allocator.cpp +++ b/examples/src/09_custom_allocator.cpp @@ -7,6 +7,7 @@ // Referenced by docs/12-custom-allocators.md. // #include "pjson.h" +#include "pjson_parser.h" #include #include @@ -15,6 +16,7 @@ #include using ByteDance::pjson; +using ByteDance::pJsonParser; // Minimal instrumentation allocator for the example. It delegates storage to // global new/delete while counting pjson's four persistent allocation kinds. @@ -24,7 +26,7 @@ class CountingAllocator : public pjson::Allocator { // maintaining per-kind lifetime totals and one aggregate live-block count. CountingAllocator() : _liveBlocks(0) { - for (size_t i = 0; i < 4; ++i) { + for (size_t i = 0; i < kAllocationKindCount; ++i) { _allocations[i] = 0; _deallocations[i] = 0; } @@ -59,13 +61,16 @@ class CountingAllocator : public pjson::Allocator { size_t liveBlocks() const { return _liveBlocks; } private: + static const size_t kAllocationKindCount = + static_cast(pjson::Allocator::ImplementationAllocation) + size_t(1); + // AllocationKind is deliberately contiguous, so it is a safe statistics index. static size_t index(AllocationKind kind) { return static_cast(kind); } // Keep allocation and deallocation totals even after all live blocks have // been released so the example can report lifetime activity separately. - size_t _allocations[4]; - size_t _deallocations[4]; + size_t _allocations[kAllocationKindCount]; + size_t _deallocations[kAllocationKindCount]; size_t _liveBlocks; }; @@ -73,9 +78,10 @@ class CountingAllocator : public pjson::Allocator { // and transfers both within and across allocator domains. int main() { // --- Default allocation ------------------------------------------------ - // Every parse overload uses the provenance-aware pjson::unique_ptr owner. - pjson::unique_ptr ordinary = pjson::parse(R"({"storage":"default"})"); - if (!ordinary) + // Every parse overload returns a pjson value bound to the default allocator. + pJsonParser::Error ordinaryError; + pjson ordinary = pJsonParser().parse(R"({"storage":"default"})", ordinaryError); + if (!ordinaryError.ok) return 1; // --- Custom allocator domains ----------------------------------------- @@ -91,21 +97,20 @@ int main() { direct["values"] += int64_t(1); direct["values"] += int64_t(2); - pjson::ParseError error; - // Allocator-aware parsing returns pjson::unique_ptr; its custom deleter - // returns the dynamically allocated root through `first`. - pjson::unique_ptr parsed = - pjson::parse(R"({"kind":"parsed root","values":[3,4]})", error, first); - if (!parsed) { + pJsonParser::Error error; + // Allocator-aware parsing returns a pjson value bound to `first`; its + // storage is released through `first` when the value is destroyed. + pjson parsed = pJsonParser(first).parse(R"({"kind":"parsed root","values":[3,4]})", error); + if (!error.ok) { std::cerr << error.message << '\n'; return 1; } // --- Transfer between domains ------------------------------------- // Explicit allocator construction deep-copies into another domain. - pjson rehomed(*parsed, second); - if (direct.canSwap(*parsed)) - direct.swap(*parsed); // same allocator: constant-time exchange + pjson rehomed(parsed, second); + if (direct.canSwap(parsed)) + direct.swap(parsed); // same allocator: constant-time exchange // Move assignment preserves the destination allocator. Because these // allocators differ, this may allocate while deep-transferring the tree. diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt index fdab683..594b78e 100644 --- a/fuzz/CMakeLists.txt +++ b/fuzz/CMakeLists.txt @@ -77,5 +77,8 @@ endfunction() pjson_add_fuzzer(pjson_fuzz_parse fuzz_parse.cpp) pjson_add_fuzzer(pjson_fuzz_stream fuzz_stream.cpp) +pjson_add_fuzzer(pjson_fuzz_serialize fuzz_serialize.cpp) pjson_add_fuzzer(pjson_fuzz_schema fuzz_schema.cpp) +pjson_add_fuzzer(pjson_fuzz_pointer fuzz_pointer.cpp) pjson_add_fuzzer(pjson_fuzz_patch fuzz_patch.cpp) +pjson_add_fuzzer(pjson_fuzz_merge_patch fuzz_merge_patch.cpp) diff --git a/fuzz/README.md b/fuzz/README.md index 6833d58..5e828c4 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -5,22 +5,25 @@ ## Harnesses -The fuzz build provides four Clang/libFuzzer targets: +The fuzz build provides seven Clang/libFuzzer targets: - `pjson_fuzz_parse` exercises strict DOM parsing only, while varying duplicate-key policy and bounded parse budgets across the same bytes. - `pjson_fuzz_stream` compares DOM, buffered stream, SAX-buffer, and chunked SAX-stream behavior under the same option variants. +- `pjson_fuzz_serialize` checks transactional structured serialization, stream + equivalence, output budgets, UTF-8 rejection, and non-finite policies. - `pjson_fuzz_schema` parses a schema and instance and verifies agreement between both validation overloads. Its input is `schema`, one newline byte, then `instance`; inputs without a newline are split at their midpoint. -- `pjson_fuzz_patch` splits its input into `document` and `patch`, then drives - RFC 6902 JSON Patch when the second half parses as an array, otherwise RFC - 7396 Merge Patch. It checks atomic failure and stable serialization after - success. +- `pjson_fuzz_pointer` compares mutable, const, string, and C-string RFC 6901 + lookups and verifies that lookups never mutate the document. +- `pjson_fuzz_patch` drives RFC 6902 JSON Patch with varied resource limits. +- `pjson_fuzz_merge_patch` independently drives RFC 7396 Merge Patch. Both + mutation targets check atomic failure and stable serialization after success. -Inputs under `corpus/patch/` intentionally cover both successful and failing -patch documents so coverage includes rollback and diagnostic paths. +Inputs under `corpus/{serialize,pointer,patch,merge_patch}` cover successful, +failing, Unicode, embedded-NUL, output-limit, and deep/wide paths. ## Bounded local smoke @@ -31,12 +34,12 @@ Run the bounded smoke used in CI with: ``` On Linux and macOS, `build.sh --fuzz` probes for a usable Clang/libFuzzer -toolchain, configures `-DPJSON_BUILD_FUZZERS=ON`, builds all four harnesses, +toolchain, configures `-DPJSON_BUILD_FUZZERS=ON`, builds all seven harnesses, and replays each checked-in seed corpus with deterministic bounds: - `-runs=1000` - `-seed=1337` -- `-max_len=4096` +- `-max_len=65536` - `-timeout=5` Checked-in seeds are read-only inputs under `corpus/`; generated corpus entries @@ -59,7 +62,8 @@ cmake -S . -B out/build-fuzz \ -DPJSON_BUILD_BENCHMARKS=OFF \ -DPJSON_BUILD_FUZZERS=ON cmake --build out/build-fuzz --parallel --target \ - pjson_fuzz_parse pjson_fuzz_stream pjson_fuzz_schema pjson_fuzz_patch + pjson_fuzz_parse pjson_fuzz_stream pjson_fuzz_serialize pjson_fuzz_schema \ + pjson_fuzz_pointer pjson_fuzz_patch pjson_fuzz_merge_patch ``` External-engine builds pass linker input through the cache variable @@ -79,5 +83,5 @@ cmake -S . -B out/build-fuzz \ Repository-local OSS-Fuzz wiring is under `../oss-fuzz/`. Its build script configures `PJSON_BUILD_FUZZERS=ON`, passes -`PJSON_FUZZING_ENGINE="${LIB_FUZZING_ENGINE}"`, builds all four harnesses, and -packages per-target seed corpora from `fuzz/corpus/{parse,stream,schema,patch}`. +`PJSON_FUZZING_ENGINE="${LIB_FUZZING_ENGINE}"`, builds all seven harnesses, and +packages per-target seed corpora from their matching `fuzz/corpus/` directories. diff --git a/fuzz/corpus/merge_patch/nested.seed b/fuzz/corpus/merge_patch/nested.seed new file mode 100644 index 0000000..534ec39 --- /dev/null +++ b/fuzz/corpus/merge_patch/nested.seed @@ -0,0 +1,2 @@ +{"a":{"b":1,"drop":true},"array":[1,2]} +{"a":{"b":2,"drop":null},"array":{"now":"object"}} diff --git a/fuzz/corpus/merge_patch/over-4k.seed b/fuzz/corpus/merge_patch/over-4k.seed new file mode 100644 index 0000000..c9d4a62 --- /dev/null +++ b/fuzz/corpus/merge_patch/over-4k.seed @@ -0,0 +1,2 @@ +{"payload":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"} +{"payload":null,"replacement":true} diff --git a/fuzz/corpus/merge_patch/scalar.seed b/fuzz/corpus/merge_patch/scalar.seed new file mode 100644 index 0000000..bd398ad --- /dev/null +++ b/fuzz/corpus/merge_patch/scalar.seed @@ -0,0 +1,2 @@ +{"old":true} +[1,2,3] diff --git a/fuzz/corpus/pointer/basic.seed b/fuzz/corpus/pointer/basic.seed new file mode 100644 index 0000000..222c239 --- /dev/null +++ b/fuzz/corpus/pointer/basic.seed @@ -0,0 +1,2 @@ +{"wide":{"key":"value"},"array":[0,1,2,3]} +/wide/key diff --git a/fuzz/corpus/pointer/escaped.seed b/fuzz/corpus/pointer/escaped.seed new file mode 100644 index 0000000..11a28bb --- /dev/null +++ b/fuzz/corpus/pointer/escaped.seed @@ -0,0 +1,2 @@ +{"":0,"a/b":{"~key":[1,2,3]}} +/a~1b/~0key/1 diff --git a/fuzz/corpus/pointer/invalid.seed b/fuzz/corpus/pointer/invalid.seed new file mode 100644 index 0000000..46df270 --- /dev/null +++ b/fuzz/corpus/pointer/invalid.seed @@ -0,0 +1,2 @@ +{"items":[1,2]} +/items/01 diff --git a/fuzz/corpus/serialize/options.seed b/fuzz/corpus/serialize/options.seed new file mode 100644 index 0000000..3e06f4e --- /dev/null +++ b/fuzz/corpus/serialize/options.seed @@ -0,0 +1 @@ +{"z":"é/\u0000","a":[-0.0,18446744073709551615,true,null]} diff --git a/fuzz/corpus/serialize/over-4k.seed b/fuzz/corpus/serialize/over-4k.seed new file mode 100644 index 0000000..6f8f138 --- /dev/null +++ b/fuzz/corpus/serialize/over-4k.seed @@ -0,0 +1 @@ +{"payload":"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"} diff --git a/fuzz/fuzz_merge_patch.cpp b/fuzz/fuzz_merge_patch.cpp new file mode 100644 index 0000000..933c995 --- /dev/null +++ b/fuzz/fuzz_merge_patch.cpp @@ -0,0 +1,51 @@ +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); + +#include "fuzz_util.h" + +#include +#include +#include + +using ByteDance::pjson; +using ByteDance::pJsonParser; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size > pjson_fuzz::kMaxInputBytes) + return 0; + + const std::string input(pjson_fuzz::bytes(data, size), size); + std::string documentInput; + std::string patchInput; + pjson_fuzz::splitOnNewlineOrMidpoint(input, documentInput, patchInput); + + pJsonParser::Error documentError; + pJsonParser::Error patchError; + pjson document = pJsonParser().parse(documentInput, documentError); + pjson patch = pJsonParser().parse(patchInput, patchError); + if (!documentError.ok || !patchError.ok) + return 0; + + for (size_t variant = 0; variant < 2; ++variant) { + const pjson::PatchOptions options = + pjson_fuzz::patchOptionsVariant(data, size, variant * size_t(4)); + pjson detailed = document; + pjson::PatchError error; + const bool detailedOk = detailed.applyMergePatch(patch, error, options); + pjson_fuzz::require(detailedOk == error.ok); + + pjson simple = document; + const bool simpleOk = simple.applyMergePatch(patch, options); + pjson_fuzz::require(simpleOk == detailedOk); + pjson_fuzz::require(simple == detailed); + if (!detailedOk) + pjson_fuzz::require(detailed == document); + else { + pJsonParser::Error roundTripError; + pjson roundTrip = pJsonParser().parse(detailed.toString(), roundTripError); + pjson_fuzz::require(roundTripError.ok); + pjson_fuzz::require(roundTrip == detailed); + } + } + return 0; +} diff --git a/fuzz/fuzz_parse.cpp b/fuzz/fuzz_parse.cpp index 57d9284..41d97b0 100644 --- a/fuzz/fuzz_parse.cpp +++ b/fuzz/fuzz_parse.cpp @@ -8,6 +8,7 @@ #include using ByteDance::pjson; +using ByteDance::pJsonParser; namespace { @@ -16,33 +17,30 @@ namespace { // Exercises one parser configuration and checks successful // values across both compact and pretty serialization modes. void exerciseParser(const uint8_t* data, size_t size, size_t variantOffset) { - const pjson::ParseOptions options = + const pJsonParser::Options options = pjson_fuzz::parseOptionsVariant(data, size, variantOffset); - pjson::ParseError error; - pjson::unique_ptr value = pjson::parse(pjson_fuzz::bytes(data, size), size, error, options); - // The returned value and explicit status must agree on whether parsing succeeded. - pjson_fuzz::require(static_cast(value) == error.ok); - if (!value) + pJsonParser::Error error; + pjson value = pJsonParser(options).parse(pjson_fuzz::bytes(data, size), size, error); + if (!error.ok) return; - // Compact output must be a stable, value-preserving representation. - const std::string compact = value->toString(); - pjson::ParseOptions compactOptions = options; + // Compact output must be a valid, value-preserving representation. + const std::string compact = value.toString(); + pJsonParser::Options compactOptions = options; compactOptions.maxInputBytes = compact.size(); - pjson::ParseError compactError; - pjson::unique_ptr reparsed = pjson::parse(compact, compactError, compactOptions); - pjson_fuzz::require(reparsed != nullptr); + pJsonParser::Error compactError; + pjson reparsed = pJsonParser(compactOptions).parse(compact, compactError); pjson_fuzz::require(compactError.ok); - pjson_fuzz::require(*reparsed == *value); - pjson_fuzz::require(reparsed->toString() == compact); + pjson_fuzz::require(reparsed == value); // Pretty printing may change whitespace, but never the represented JSON value. - const std::string pretty = value->toString(pjson::SerializeOptions::prettyPrinted()); - pjson::ParseOptions prettyOptions = options; + const std::string pretty = value.toString(pjson::SerializeOptions::prettyPrinted()); + pJsonParser::Options prettyOptions = options; prettyOptions.maxInputBytes = pretty.size(); - pjson::unique_ptr prettyParsed = pjson::parse(pretty, prettyOptions); - pjson_fuzz::require(prettyParsed != nullptr); - pjson_fuzz::require(*prettyParsed == *value); + pJsonParser::Error prettyError; + pjson prettyParsed = pJsonParser(prettyOptions).parse(pretty, prettyError); + pjson_fuzz::require(prettyError.ok); + pjson_fuzz::require(prettyParsed == value); } } // namespace diff --git a/fuzz/fuzz_patch.cpp b/fuzz/fuzz_patch.cpp index c24da5d..56bc126 100644 --- a/fuzz/fuzz_patch.cpp +++ b/fuzz/fuzz_patch.cpp @@ -8,54 +8,60 @@ #include using ByteDance::pjson; +using ByteDance::pJsonParser; namespace { - // Applies either RFC 6902 JSON Patch or RFC 7396 Merge Patch and checks the - // non-throwing API contracts that are observable from fuzz-side callers. + // Applies RFC 6902 JSON Patch and checks the non-throwing API contracts + // that are observable from fuzz-side callers. void exercisePatchVariant(const uint8_t* data, size_t size, const std::string& documentInput, const std::string& patchInput, size_t variantOffset) { - const pjson::ParseOptions options = + const pJsonParser::Options options = pjson_fuzz::parseOptionsVariant(data, size, variantOffset); - pjson::unique_ptr original = pjson::parse(documentInput, options); - pjson::unique_ptr patch = pjson::parse(patchInput, options); - if (!original || !patch) + pJsonParser::Error originalError; + pJsonParser::Error patchError; + pjson original = pJsonParser(options).parse(documentInput, originalError); + pjson patch = pJsonParser(options).parse(patchInput, patchError); + if (!originalError.ok || !patchError.ok) return; - const bool useJsonPatch = patch->isArray(); - pjson working = *original; + if (!patch.isArray()) + return; + pjson working = original; pjson::PatchError detailedError; - const bool detailedOk = useJsonPatch ? working.applyPatch(*patch, detailedError) - : working.applyMergePatch(*patch, detailedError); + const pjson::PatchOptions patchOptions = + pjson_fuzz::patchOptionsVariant(data, size, variantOffset); + const bool detailedOk = working.applyPatch(patch, detailedError, patchOptions); pjson_fuzz::require(detailedOk == detailedError.ok); - pjson simple = *original; - const bool simpleOk = - useJsonPatch ? simple.applyPatch(*patch) : simple.applyMergePatch(*patch); + pjson simple = original; + const bool simpleOk = simple.applyPatch(patch, patchOptions); pjson_fuzz::require(simpleOk == detailedOk); if (!detailedOk) { // Failure must leave the document unchanged because patch application is atomic. - pjson_fuzz::require(working == *original); - pjson_fuzz::require(simple == *original); + pjson_fuzz::require(working == original); + pjson_fuzz::require(simple == original); return; } // Successful mutation must serialize and reparse stably. pjson_fuzz::require(working == simple); const std::string compact = working.toString(); - pjson::ParseOptions compactOptions = options; + pJsonParser::Options compactOptions = options; compactOptions.maxInputBytes = compact.size(); - pjson::unique_ptr reparsed = pjson::parse(compact, compactOptions); - pjson_fuzz::require(reparsed != nullptr); - pjson_fuzz::require(*reparsed == working); + pJsonParser::Error reparsedError; + pjson reparsed = pJsonParser(compactOptions).parse(compact, reparsedError); + pjson_fuzz::require(reparsedError.ok); + pjson_fuzz::require(reparsed == working); const std::string pretty = working.toString(pjson::SerializeOptions::prettyPrinted()); - pjson::ParseOptions prettyOptions = options; + pJsonParser::Options prettyOptions = options; prettyOptions.maxInputBytes = pretty.size(); - pjson::unique_ptr prettyParsed = pjson::parse(pretty, prettyOptions); - pjson_fuzz::require(prettyParsed != nullptr); - pjson_fuzz::require(*prettyParsed == working); + pJsonParser::Error prettyError; + pjson prettyParsed = pJsonParser(prettyOptions).parse(pretty, prettyError); + pjson_fuzz::require(prettyError.ok); + pjson_fuzz::require(prettyParsed == working); } } // namespace diff --git a/fuzz/fuzz_pointer.cpp b/fuzz/fuzz_pointer.cpp new file mode 100644 index 0000000..c60667d --- /dev/null +++ b/fuzz/fuzz_pointer.cpp @@ -0,0 +1,46 @@ +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); + +#include "fuzz_util.h" + +#include +#include +#include + +using ByteDance::pjson; +using ByteDance::pJsonParser; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size > pjson_fuzz::kMaxInputBytes) + return 0; + + const std::string input(pjson_fuzz::bytes(data, size), size); + std::string documentInput; + std::string pointer; + pjson_fuzz::splitOnNewlineOrMidpoint(input, documentInput, pointer); + pJsonParser::Error parseError; + pjson document = pJsonParser().parse(documentInput, parseError); + if (!parseError.ok) + return 0; + + const std::string before = document.toString(); + pjson::PointerError mutableError; + pjson* mutableResult = document.findPointer(pointer, mutableError); + const pjson& constDocument = document; + pjson::PointerError constError; + const pjson* constResult = constDocument.findPointer(pointer, constError); + pjson_fuzz::require((mutableResult != nullptr) == (constResult != nullptr)); + pjson_fuzz::require(mutableError.ok == constError.ok); + pjson_fuzz::require(mutableError.code == constError.code); + pjson_fuzz::require(mutableError.tokenIndex == constError.tokenIndex); + pjson_fuzz::require(mutableError.token == constError.token); + pjson_fuzz::require(document.toString() == before); + if (mutableResult != nullptr) + pjson_fuzz::require(*mutableResult == *constResult); + + if (pointer.find('\0') == std::string::npos) { + const pjson* cStringResult = constDocument.findPointer(pointer.c_str()); + pjson_fuzz::require((cStringResult != nullptr) == (constResult != nullptr)); + } + return 0; +} diff --git a/fuzz/fuzz_schema.cpp b/fuzz/fuzz_schema.cpp index 52aa1da..1724668 100644 --- a/fuzz/fuzz_schema.cpp +++ b/fuzz/fuzz_schema.cpp @@ -9,6 +9,7 @@ #include using ByteDance::pjson; +using ByteDance::pJsonParser; // Schema-validation consistency. @@ -24,17 +25,21 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { pjson_fuzz::splitOnNewlineOrMidpoint(input, schemaInput, documentInput); // Only pairs that are both valid strict JSON values can exercise schema validation. - const pjson::ParseOptions options = pjson_fuzz::parseOptionsVariant(data, size, 0U); - pjson::unique_ptr schema = pjson::parse(schemaInput, options); - pjson::unique_ptr document = pjson::parse(documentInput, options); - if (!schema || !document) + const pJsonParser::Options options = pjson_fuzz::parseOptionsVariant(data, size, 0U); + pJsonParser::Error schemaError; + pJsonParser::Error documentError; + pjson schema = pJsonParser(options).parse(schemaInput, schemaError); + pjson document = pJsonParser(options).parse(documentInput, documentError); + if (!schemaError.ok || !documentError.ok) return 0; // Detailed and simple validation must agree, and errors exist exactly on failure. - const pjson::SchemaOptions schemaOptions = pjson_fuzz::boundedSchemaOptions(data, size, 4U); - std::vector errors; - const bool detailed = document->validate(*schema, errors, schemaOptions); - const bool simple = document->validate(*schema, schemaOptions); + const ByteDance::pJsonSchemaValidator::Options schemaOptions = + pjson_fuzz::boundedSchemaOptions(data, size, 4U); + ByteDance::pJsonSchemaValidator validator(schema, schemaOptions); + std::vector errors; + const bool detailed = validator.validate(document, errors); + const bool simple = validator.validate(document); pjson_fuzz::require(simple == detailed); pjson_fuzz::require(detailed == errors.empty()); return 0; diff --git a/fuzz/fuzz_serialize.cpp b/fuzz/fuzz_serialize.cpp new file mode 100644 index 0000000..20b217f --- /dev/null +++ b/fuzz/fuzz_serialize.cpp @@ -0,0 +1,77 @@ +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); + +#include "fuzz_util.h" + +#include +#include +#include +#include +#include + +using ByteDance::pjson; +using ByteDance::pJsonParser; + +namespace { + void exercise(const pjson& value, const uint8_t* data, size_t size, size_t offset) { + pjson::SerializeOptions options; + options.pretty = (pjson_fuzz::pickByte(data, size, offset, 0) & 1U) != 0; + options.indentWidth = pjson_fuzz::pickByte(data, size, offset + 1U, 2) % 9U; + options.indentCharacter = + (pjson_fuzz::pickByte(data, size, offset + 2U, 0) & 1U) != 0 ? '\t' : ' '; + options.escapeNonAscii = (pjson_fuzz::pickByte(data, size, offset + 3U, 0) & 1U) != 0; + options.nonFinite = static_cast( + pjson_fuzz::pickByte(data, size, offset + 4U, 0) % 3U); + const size_t limits[] = {0U, 1U, 32U, 4096U, pjson_fuzz::kMaxInputBytes}; + options.maxOutputBytes = limits[pjson_fuzz::pickByte(data, size, offset + 5U, 0) % 5U]; + + std::string output = "preserved"; + pjson::SerializeError error; + const bool ok = value.toString(output, error, options); + pjson_fuzz::require(ok == (error.code == pjson::SerializeError::None)); + if (!ok) { + pjson_fuzz::require(output == "preserved"); + return; + } + + std::ostringstream stream; + pjson::SerializeError streamError; + pjson_fuzz::require(value.write(stream, streamError, options)); + pjson_fuzz::require(streamError.code == pjson::SerializeError::None); + pjson_fuzz::require(stream.str() == output); + pJsonParser::Error parseError; + pjson roundTrip = pJsonParser().parse(output, parseError); + pjson_fuzz::require(parseError.ok); + if (value.isDouble()) { + double number = 0.0; + value.tryGet(number); + if (number != number || number == std::numeric_limits::infinity() || + number == -std::numeric_limits::infinity()) + return; + } + pjson_fuzz::require(roundTrip == value); + } +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size > pjson_fuzz::kMaxInputBytes) + return 0; + pJsonParser::Error parseError; + pjson parsed = pJsonParser().parse(pjson_fuzz::bytes(data, size), size, parseError); + if (parseError.ok) + exercise(parsed, data, size, 0); + + pjson arbitraryString; + arbitraryString = std::string(pjson_fuzz::bytes(data, size), size); + exercise(arbitraryString, data, size, 8); + + const double nonFiniteValues[] = {std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN()}; + for (size_t i = 0; i < size_t(3); ++i) { + pjson nonFinite; + nonFinite = nonFiniteValues[i]; + exercise(nonFinite, data, size, size_t(16) + i * size_t(8)); + } + return 0; +} diff --git a/fuzz/fuzz_stream.cpp b/fuzz/fuzz_stream.cpp index b87ea26..1ce24a6 100644 --- a/fuzz/fuzz_stream.cpp +++ b/fuzz/fuzz_stream.cpp @@ -11,6 +11,7 @@ #include using ByteDance::pjson; +using ByteDance::pJsonParser; namespace { @@ -66,7 +67,7 @@ namespace { // Records both event count and content so buffered and streamed SAX traces can be compared. // The small methods below all fold a distinct event tag plus any payload // into the same order-sensitive digest and always continue parsing. - struct DigestHandler : pjson::SaxHandler { + struct DigestHandler : pJsonParser::SaxHandler { DigestHandler() : digest(1469598103934665603ULL) , events(0) {} @@ -102,6 +103,12 @@ namespace { return true; } + bool onUInt(uint64_t value) override { + mark(7); + mix(value); + return true; + } + // Canonical serialization gives floating-point values a stable byte representation. bool onDouble(double value) override { mark(5); @@ -139,40 +146,37 @@ namespace { // option variant. void exerciseStreams(const uint8_t* data, size_t size, const std::string& input, size_t chunkSize, size_t variantOffset) { - const pjson::ParseOptions options = + const pJsonParser::Options options = pjson_fuzz::parseOptionsVariant(data, size, variantOffset); // Contiguous DOM parsing provides the baseline status and value. - pjson::ParseError bufferError; - pjson::unique_ptr buffered = - pjson::parse(input.c_str(), input.size(), bufferError, options); - pjson_fuzz::require(static_cast(buffered) == bufferError.ok); + pJsonParser::Error bufferError; + pjson buffered = pJsonParser(options).parse(input.c_str(), input.size(), bufferError); - // Chunk boundaries must not affect DOM acceptance or serialized output. + // Chunk boundaries must not affect DOM acceptance or represented value. ChunkedStream domInput(input, chunkSize); - pjson::ParseError streamError; - pjson::unique_ptr streamed = pjson::parseStream(domInput, streamError, options); - pjson_fuzz::require(static_cast(streamed) == streamError.ok); - pjson_fuzz::require(static_cast(buffered) == static_cast(streamed)); - if (buffered) - pjson_fuzz::require(buffered->toString() == streamed->toString()); + pJsonParser::Error streamError; + pjson streamed = pJsonParser(options).parseStream(domInput, streamError); + pjson_fuzz::require(bufferError.ok == streamError.ok); + if (bufferError.ok) + pjson_fuzz::require(buffered == streamed); // Capture the SAX trace from the same contiguous baseline input. DigestHandler bufferHandler; - pjson::ParseError saxBufferError; - const bool saxBuffer = - pjson::parseSax(input.c_str(), input.size(), bufferHandler, saxBufferError, options); + pJsonParser::Error saxBufferError; + const bool saxBuffer = pJsonParser(options).parseSax(input.c_str(), input.size(), + bufferHandler, saxBufferError); pjson_fuzz::require(saxBuffer == saxBufferError.ok); // Streamed SAX parsing must agree on status, event count, order, and payloads. ChunkedStream saxInput(input, chunkSize); DigestHandler streamHandler; - pjson::ParseError saxStreamError; + pJsonParser::Error saxStreamError; const bool saxStream = - pjson::parseSaxStream(saxInput, streamHandler, saxStreamError, options); + pJsonParser(options).parseSaxStream(saxInput, streamHandler, saxStreamError); pjson_fuzz::require(saxStream == saxStreamError.ok); pjson_fuzz::require(saxBuffer == saxStream); - pjson_fuzz::require(static_cast(buffered) == saxBuffer); + pjson_fuzz::require(bufferError.ok == saxBuffer); // Failure can be detected before any callbacks for a bounded in-memory // input but only after prefix callbacks for a stream, so compare traces // only when both parsers consumed the complete document successfully. diff --git a/fuzz/fuzz_util.h b/fuzz/fuzz_util.h index fd347cd..84b4228 100644 --- a/fuzz/fuzz_util.h +++ b/fuzz/fuzz_util.h @@ -15,6 +15,8 @@ #define PJSON_FUZZ_UTIL_H #include "pjson.h" +#include "pjson_parser.h" +#include "pjson_schema.h" #include #include @@ -43,18 +45,18 @@ namespace pjson_fuzz { // Builds a parser configuration while varying duplicate-key // policy and resource budgets across inputs. - inline ByteDance::pjson::ParseOptions parseOptionsVariant(const uint8_t* data, size_t size, - size_t offset = 0) { - ByteDance::pjson::ParseOptions options; + inline ByteDance::pJsonParser::Options parseOptionsVariant(const uint8_t* data, size_t size, + size_t offset = 0) { + ByteDance::pJsonParser::Options options; switch (pickByte(data, size, offset, 0) % 3U) { case 0: - options.duplicateKeys = ByteDance::pjson::ParseOptions::RejectDuplicateKeys; + options.duplicateKeys = ByteDance::pJsonParser::Options::RejectDuplicateKeys; break; case 1: - options.duplicateKeys = ByteDance::pjson::ParseOptions::KeepFirstDuplicate; + options.duplicateKeys = ByteDance::pJsonParser::Options::KeepFirstDuplicate; break; default: - options.duplicateKeys = ByteDance::pjson::ParseOptions::KeepLastDuplicate; + options.duplicateKeys = ByteDance::pJsonParser::Options::KeepLastDuplicate; break; } @@ -70,9 +72,9 @@ namespace pjson_fuzz { // Schema validation gets its own bounded knobs so one input can drive both // parser and validator resource limits. - inline ByteDance::pjson::SchemaOptions boundedSchemaOptions(const uint8_t* data, size_t size, - size_t offset = 0) { - ByteDance::pjson::SchemaOptions options; + inline ByteDance::pJsonSchemaValidator::Options + boundedSchemaOptions(const uint8_t* data, size_t size, size_t offset = 0) { + ByteDance::pJsonSchemaValidator::Options options; static const size_t kPatternBudgets[] = {32U, 64U, 256U, 1024U}; static const size_t kSubjectBudgets[] = {128U, 512U, 4096U, 16384U}; static const size_t kValidationDepths[] = {16U, 64U, 256U, 1024U}; @@ -90,6 +92,20 @@ namespace pjson_fuzz { return options; } + inline ByteDance::pjson::PatchOptions patchOptionsVariant(const uint8_t* data, size_t size, + size_t offset = 0) { + ByteDance::pjson::PatchOptions options; + static const size_t kOperationBudgets[] = {1U, 8U, 64U, 10000U}; + static const size_t kNodeBudgets[] = {8U, 128U, 4096U, 1000000U}; + static const size_t kByteBudgets[] = {64U, 4096U, 65536U, 64U * 1024U * 1024U}; + static const size_t kWorkBudgets[] = {16U, 512U, 16384U, 1000000U}; + options.maxOperations = kOperationBudgets[pickByte(data, size, offset, 0) % 4U]; + options.maxClonedNodes = kNodeBudgets[pickByte(data, size, offset + 1U, 1) % 4U]; + options.maxClonedBytes = kByteBudgets[pickByte(data, size, offset + 2U, 2) % 4U]; + options.maxWork = kWorkBudgets[pickByte(data, size, offset + 3U, 3) % 4U]; + return options; + } + // Raw input adaptation. // Returns a non-null character pointer for empty input and preserves all other bytes. diff --git a/oss-fuzz/build.sh b/oss-fuzz/build.sh index cebf62d..ab70797 100755 --- a/oss-fuzz/build.sh +++ b/oss-fuzz/build.sh @@ -36,13 +36,15 @@ cmake -S "${PJSON_SOURCE_DIR}" -B "${PJSON_FUZZ_BUILD_DIR}" \ -DPJSON_BUILD_FUZZERS=ON \ -DPJSON_FUZZING_ENGINE="${LIB_FUZZING_ENGINE}" cmake --build "${PJSON_FUZZ_BUILD_DIR}" --parallel --target \ - pjson_fuzz_parse pjson_fuzz_stream pjson_fuzz_schema pjson_fuzz_patch + pjson_fuzz_parse pjson_fuzz_stream pjson_fuzz_serialize pjson_fuzz_schema \ + pjson_fuzz_pointer pjson_fuzz_patch pjson_fuzz_merge_patch # ---- Runtime bundle ----------------------------------------------------- # Each executable receives matching runtime options and the shared JSON token # dictionary under the basename convention understood by OSS-Fuzz. -for target in pjson_fuzz_parse pjson_fuzz_stream pjson_fuzz_schema pjson_fuzz_patch; do +for target in pjson_fuzz_parse pjson_fuzz_stream pjson_fuzz_serialize pjson_fuzz_schema \ + pjson_fuzz_pointer pjson_fuzz_patch pjson_fuzz_merge_patch; do cp "${PJSON_FUZZ_BUILD_DIR}/fuzz/${target}" "${OUT}/${target}" cp "${PJSON_SOURCE_DIR}/oss-fuzz/${target}.options" "${OUT}/${target}.options" cp "${PJSON_SOURCE_DIR}/fuzz/json.dict" "${OUT}/${target}.dict" @@ -51,7 +53,7 @@ done # Package each checked-in seed directory at the archive root, as required by # OSS-Fuzz's _seed_corpus.zip discovery convention. The subshell keeps # the loop's working directory stable between harnesses. -for corpus in parse stream schema patch; do +for corpus in parse stream serialize schema pointer patch merge_patch; do ( cd "${PJSON_SOURCE_DIR}/fuzz/corpus/${corpus}" zip -q -r "${OUT}/pjson_fuzz_${corpus}_seed_corpus.zip" . diff --git a/oss-fuzz/pjson_fuzz_merge_patch.options b/oss-fuzz/pjson_fuzz_merge_patch.options new file mode 100644 index 0000000..e8a9ab6 --- /dev/null +++ b/oss-fuzz/pjson_fuzz_merge_patch.options @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +[libfuzzer] +max_len = 65536 +timeout = 5 +rss_limit_mb = 2048 diff --git a/oss-fuzz/pjson_fuzz_parse.options b/oss-fuzz/pjson_fuzz_parse.options index 406da9c..542fc08 100644 --- a/oss-fuzz/pjson_fuzz_parse.options +++ b/oss-fuzz/pjson_fuzz_parse.options @@ -3,7 +3,7 @@ [libfuzzer] # Bound generated inputs so routine mutations emphasize parser state coverage. -max_len = 4096 +max_len = 65536 # Stop one input if parsing does not complete within five seconds. timeout = 5 # Leave enough headroom for sanitizer overhead while catching runaway growth. diff --git a/oss-fuzz/pjson_fuzz_patch.options b/oss-fuzz/pjson_fuzz_patch.options index 323d35d..45cf1e8 100644 --- a/oss-fuzz/pjson_fuzz_patch.options +++ b/oss-fuzz/pjson_fuzz_patch.options @@ -3,7 +3,7 @@ [libfuzzer] # Bound generated inputs so routine mutations emphasize patch-operation paths. -max_len = 4096 +max_len = 65536 # Stop one patch/document pair if processing exceeds five seconds. timeout = 5 # Leave enough headroom for sanitizer overhead while catching runaway growth. diff --git a/oss-fuzz/pjson_fuzz_pointer.options b/oss-fuzz/pjson_fuzz_pointer.options new file mode 100644 index 0000000..e8a9ab6 --- /dev/null +++ b/oss-fuzz/pjson_fuzz_pointer.options @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +[libfuzzer] +max_len = 65536 +timeout = 5 +rss_limit_mb = 2048 diff --git a/oss-fuzz/pjson_fuzz_schema.options b/oss-fuzz/pjson_fuzz_schema.options index ccfaa36..6941c63 100644 --- a/oss-fuzz/pjson_fuzz_schema.options +++ b/oss-fuzz/pjson_fuzz_schema.options @@ -3,7 +3,7 @@ [libfuzzer] # Bound generated inputs so routine mutations emphasize schema state coverage. -max_len = 4096 +max_len = 65536 # Stop one schema/instance pair if validation exceeds five seconds. timeout = 5 # Leave enough headroom for sanitizer overhead while catching runaway growth. diff --git a/oss-fuzz/pjson_fuzz_serialize.options b/oss-fuzz/pjson_fuzz_serialize.options new file mode 100644 index 0000000..e8a9ab6 --- /dev/null +++ b/oss-fuzz/pjson_fuzz_serialize.options @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +[libfuzzer] +max_len = 65536 +timeout = 5 +rss_limit_mb = 2048 diff --git a/oss-fuzz/pjson_fuzz_stream.options b/oss-fuzz/pjson_fuzz_stream.options index ea88faa..93ec543 100644 --- a/oss-fuzz/pjson_fuzz_stream.options +++ b/oss-fuzz/pjson_fuzz_stream.options @@ -3,7 +3,7 @@ [libfuzzer] # Bound generated inputs so routine mutations emphasize streaming boundaries. -max_len = 4096 +max_len = 65536 # Stop one input if a streaming path does not complete within five seconds. timeout = 5 # Leave enough headroom for sanitizer overhead while catching runaway growth. diff --git a/packaging/vcpkg/ports/pjson/vcpkg.json b/packaging/vcpkg/ports/pjson/vcpkg.json index a8e2aed..e2fe214 100644 --- a/packaging/vcpkg/ports/pjson/vcpkg.json +++ b/packaging/vcpkg/ports/pjson/vcpkg.json @@ -1,6 +1,6 @@ { "name": "pjson", - "version-semver": "1.0.0", + "version-semver": "2.0.0", "description": "An ultra-simple JSON value type for C++11", "homepage": "https://github.com/Pico-Developer/pjson", "license": "Apache-2.0", diff --git a/pjsonlib/CMakeLists.txt b/pjsonlib/CMakeLists.txt index 251e61c..ed4dded 100644 --- a/pjsonlib/CMakeLists.txt +++ b/pjsonlib/CMakeLists.txt @@ -9,7 +9,34 @@ set (INCLUDE_DIR "include") set (SRC_FILES ${SRC_FILES} ${SRC_DIR}/pjson.cpp +${SRC_DIR}/pjson_parser.cpp +${SRC_DIR}/pjson_patch.cpp +${SRC_DIR}/pjson_pointer.cpp +${SRC_DIR}/pjson_schema.cpp +${SRC_DIR}/pjson_schema_dialect.cpp +${SRC_DIR}/pjson_schema_format.cpp +${SRC_DIR}/pjson_schema_regex.cpp +${SRC_DIR}/pjson_schema_uri.cpp +${SRC_DIR}/pjson_schema_value.cpp +${SRC_DIR}/pjson_serialize.cpp +${SRC_DIR}/third_party/ryu/ryu/d2s.c ) +set_source_files_properties(${SRC_DIR}/third_party/ryu/ryu/d2s.c PROPERTIES LANGUAGE CXX) + +set(PJSON_META_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/src/third_party/json-schema-2020-12") +file(READ "${PJSON_META_ROOT}/schema.json" PJSON_META_SCHEMA) +file(READ "${PJSON_META_ROOT}/meta/core.json" PJSON_META_CORE) +file(READ "${PJSON_META_ROOT}/meta/applicator.json" PJSON_META_APPLICATOR) +file(READ "${PJSON_META_ROOT}/meta/unevaluated.json" PJSON_META_UNEVALUATED) +file(READ "${PJSON_META_ROOT}/meta/validation.json" PJSON_META_VALIDATION) +file(READ "${PJSON_META_ROOT}/meta/meta-data.json" PJSON_META_METADATA) +file(READ "${PJSON_META_ROOT}/meta/format-annotation.json" PJSON_META_FORMAT_ANNOTATION) +file(READ "${PJSON_META_ROOT}/meta/format-assertion.json" PJSON_META_FORMAT_ASSERTION) +file(READ "${PJSON_META_ROOT}/meta/content.json" PJSON_META_CONTENT) +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/pjson_schema_builtins.h.in + ${CMAKE_CURRENT_BINARY_DIR}/generated/pjson_schema_builtins.h + @ONLY) # Warning flags differ by compiler: GCC/Clang use -Wall -Wextra, MSVC uses /W4. if (MSVC) @@ -25,13 +52,26 @@ add_library(pjson::pjson ALIAS ${TARGET_NAME}) target_compile_features(${TARGET_NAME} PUBLIC cxx_std_11) target_compile_options(${TARGET_NAME} PRIVATE ${PJSON_WARN_FLAGS}) +target_compile_definitions(${TARGET_NAME} PRIVATE PJSON_BUILDING_LIBRARY) +if(BUILD_SHARED_LIBS) + target_compile_definitions(${TARGET_NAME} PUBLIC PJSON_SHARED) +endif() +target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated) +target_include_directories(${TARGET_NAME} SYSTEM PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/third_party + ${CMAKE_CURRENT_SOURCE_DIR}/src/third_party/ryu) set_target_properties(${TARGET_NAME} PROPERTIES VERSION "${PROJECT_VERSION}" - SOVERSION "${PROJECT_VERSION_MAJOR}" + SOVERSION "${PJSON_ABI_VERSION}" CXX_EXTENSIONS OFF EXPORT_NAME pjson - WINDOWS_EXPORT_ALL_SYMBOLS ON ) +if(BUILD_SHARED_LIBS) + set_target_properties(${TARGET_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES + ) +endif() # Consumers get the include dir whether they build in-tree or install. target_include_directories(${TARGET_NAME} PUBLIC @@ -51,7 +91,10 @@ install(TARGETS ${TARGET_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" ) -install(FILES ${INCLUDE_DIR}/pjson.h +install(FILES + ${INCLUDE_DIR}/pjson.h + ${INCLUDE_DIR}/pjson_parser.h + ${INCLUDE_DIR}/pjson_schema.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" ) @@ -94,6 +137,11 @@ set(PJSON_PC_PREFIX_FROM_PCFILEDIR ".") cmake_path(RELATIVE_PATH PJSON_PC_PREFIX_FROM_PCFILEDIR BASE_DIRECTORY "${PJSON_INSTALL_PKGCONFIGDIR}") cmake_path(NORMAL_PATH PJSON_PC_PREFIX_FROM_PCFILEDIR) +if(BUILD_SHARED_LIBS) + set(PJSON_PC_COMPILE_FLAGS "-DPJSON_SHARED") +else() + set(PJSON_PC_COMPILE_FLAGS "") +endif() configure_file( "${CMAKE_CURRENT_LIST_DIR}/../cmake/pjson.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/pjson.pc" @@ -106,3 +154,15 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pjson.pc" install(FILES "${CMAKE_CURRENT_LIST_DIR}/../LICENSE" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/pjson" ) +install(FILES + "${CMAKE_CURRENT_SOURCE_DIR}/src/third_party/srell/LICENSE.txt" + DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/pjson" + RENAME "LICENSE-SRELL") +install(FILES + "${CMAKE_CURRENT_SOURCE_DIR}/src/third_party/json-schema-2020-12/LICENSE.txt" + DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/pjson" + RENAME "LICENSE-JSON-SCHEMA") +install(FILES + "${CMAKE_CURRENT_SOURCE_DIR}/src/third_party/ryu/LICENSE.txt" + DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/pjson" + RENAME "LICENSE-RYU") diff --git a/pjsonlib/include/pjson.h b/pjsonlib/include/pjson.h index 097f012..119fb5a 100644 --- a/pjsonlib/include/pjson.h +++ b/pjsonlib/include/pjson.h @@ -16,9 +16,11 @@ // pjson — Praveen's JSON: an ultra-simple JSON value type for C++. // // A single class, ByteDance::pjson, represents any JSON value and offers an -// ergonomic obj["key"][i] = value building style plus parsing, serialization, -// lookup, mutation, equality, and JSON-Schema-subset validation. All method -// bodies live in pjson.cpp; this header only declares the interface. +// ergonomic obj["key"][i] = value building style plus serialization, lookup, +// mutation, and equality. Parsing lives in the separate ByteDance::pJsonParser +// helper declared by ; the DOM does not depend on that parser. +// JSON Schema validation similarly lives in ByteDance::pJsonSchemaValidator in +// , which consumes only the public DOM API. // // Author: Praveen Babu J D // License: Apache 2.0 @@ -29,18 +31,28 @@ // Library version. PJSON_VERSION is the string form ("MAJOR.MINOR.PATCH"); // the numeric parts allow compile-time checks, e.g. // #if PJSON_VERSION_MAJOR >= 1 -#define PJSON_VERSION_MAJOR 1 +#define PJSON_VERSION_MAJOR 2 #define PJSON_VERSION_MINOR 0 #define PJSON_VERSION_PATCH 0 -#define PJSON_VERSION "1.0.0" +#define PJSON_VERSION "2.0.0" +#define PJSON_ABI_VERSION 2 + +#if defined(_WIN32) && defined(PJSON_SHARED) +#if defined(PJSON_BUILDING_LIBRARY) +#define PJSON_API __declspec(dllexport) +#else +#define PJSON_API __declspec(dllimport) +#endif +#elif defined(PJSON_SHARED) && (defined(__GNUC__) || defined(__clang__)) +#define PJSON_API __attribute__((visibility("default"))) +#else +#define PJSON_API +#endif #include #include #include -#include -#include #include -#include #include namespace ByteDance { @@ -53,7 +65,7 @@ namespace ByteDance { /// destroyed, replaced, reset, erased, moved, swapped, cleared, or successfully /// patched. Unless an operation is `noexcept` or explicitly reports failures, /// allocation and standard-library exceptions may escape. - class pjson { + class PJSON_API pjson { public: //== Library version ================================================= /// Returns the process-lifetime semantic-version string for this library. @@ -61,31 +73,39 @@ namespace ByteDance { //== Types =========================================================== - // JSON value kind. Numbers are stored in one of two representations: - // whole numbers as a 64-bit signed integer (jsonNumberInt) and - // everything else as a double (jsonNumberDouble). + /// JSON value kind. + /// + /// Numbers are stored in one of three representations: signed whole + /// numbers as a 64-bit signed integer (jsonNumberInt), explicitly + /// unsigned whole numbers as a 64-bit unsigned integer (jsonNumberUInt), + /// and fractional/exponent values as a double (jsonNumberDouble). enum jsonType : int64_t { - jsonNull = 0, // stable zero-valued discriminator for the default state - jsonString, - jsonNumberInt, - jsonNumberDouble, - jsonBoolean, - jsonArray, //[ ] array - jsonObject, // { ... } map + jsonNull = 0, ///< JSON null and the stable default discriminator. + jsonString, ///< UTF-8 string value. + jsonNumberInt, ///< Signed 64-bit integer value. + jsonNumberDouble, ///< Binary64 number value. + jsonBoolean, ///< Boolean value. + jsonArray, ///< Ordered array value. + jsonObject, ///< Object with unique string keys. + jsonNumberUInt, ///< Unsigned 64-bit integer value. }; - // Runtime allocator for persistent DOM storage. The allocator is - // non-owning and must outlive every pjson value that refers to it. - // Allocation covers pjson child/root nodes plus the std::string, - // array, and object wrapper objects. Storage used internally by those - // standard-library objects and transient parser/algorithm scratch space - // continues to use the standard allocator. - struct Allocator { + /// Runtime allocator for persistent DOM storage. + /// + /// The allocator is non-owning and must outlive every pjson value that + /// refers to it. Allocation covers pjson child/root nodes, non-null + /// private implementations, and the std::string, array, and object + /// wrapper objects. Storage used internally by those standard-library + /// objects and transient parser/algorithm scratch space continues to use + /// the standard allocator. + struct PJSON_API Allocator { + /// Identifies the purpose and matching deallocation contract of storage. enum AllocationKind { - NodeAllocation = 0, - StringAllocation = 1, - ArrayAllocation = 2, - ObjectAllocation = 3 + NodeAllocation = 0, ///< Storage for a child or allocator-created root. + StringAllocation = 1, ///< Storage for a std::string wrapper. + ArrayAllocation = 2, ///< Storage for an array-container wrapper. + ObjectAllocation = 3, ///< Storage for an object-container wrapper. + ImplementationAllocation = 4 ///< Opaque state of one non-null pjson value. }; /// Enables destruction through an Allocator base pointer. @@ -97,230 +117,155 @@ namespace ByteDance { AllocationKind aKind) noexcept = 0; }; - // Stateless ownership for allocator-created nodes. Provenance is read - // from the node itself, so moving this pointer never transfers or owns - // the Allocator object. - struct ValueDeleter { - /// Destroys aValue's tree through its originating allocator; accepts null. - void operator()(pjson* aValue) const noexcept; - }; - typedef std::unique_ptr unique_ptr; - - // Bounds how much work a parse may do. Parsing always enforces RFC 8259 - // conformance and rejects: - // - unknown escapes (e.g. "\q") - // - lone/unpaired \u surrogates - // - upper/mixed-case keywords (NULL, True, FALSE) - // - raw control characters inside strings - // - malformed UTF-8 bytes - struct ParseOptions { - enum DuplicateKeyPolicy { RejectDuplicateKeys, KeepFirstDuplicate, KeepLastDuplicate }; - - int maxDepth; // nesting limit; values <= 0 enforce a one-level limit - size_t maxNodes; // max JSON values created (0 = unlimited) - size_t maxInputBytes; // max input length in bytes (0 = unlimited) - DuplicateKeyPolicy duplicateKeys; - /// Selects duplicate rejection, depth 512, one million nodes, and a - /// 64 MiB input limit. - ParseOptions(); - }; - - // Filled in by the error-reporting parse() overloads. `ok` is true when - // parsing succeeded; otherwise `offset` is the zero-based byte position, - // `line` is one-based, `column` is a one-based byte column, and - // `message` describes the first failure. Reporting parse APIs reset all - // fields on entry and leave this success state after a successful parse. - struct ParseError { - bool ok; - size_t offset; - size_t line; - size_t column; - std::string message; - /// Constructs a success state at the beginning of an input. - ParseError(); - }; - - // Structured JSON Pointer (RFC 6901) lookup failure. `tokenIndex` is - // zero-based and `token` is the decoded token that could not be - // resolved (or the source token when its escape sequence is invalid). - // std::string reporting overloads reset all fields on entry. A C-string - // overload can report allocation failure before copying the pointer text. - struct PointerError { + /// Structured JSON Pointer (RFC 6901) lookup failure. + /// + /// `tokenIndex` is zero-based and `token` is the decoded token that could + /// not be resolved (or the source token when its escape sequence is + /// invalid). std::string reporting overloads reset all fields on entry. A + /// C-string overload can report allocation failure before copying the + /// pointer text. + struct PJSON_API PointerError { + /// Stable categories for programmatic JSON Pointer failure handling. enum Code { - Ok, - InvalidSyntax, - InvalidEscape, - MissingTarget, - ExpectedContainer, - InvalidArrayIndex, - ArrayIndexOutOfRange, - AppendTokenNotAllowed, - AllocationFailure, - InternalError + Ok, ///< Lookup succeeded. + InvalidSyntax, ///< The pointer does not begin with slash or is malformed. + InvalidEscape, ///< A token contains an invalid tilde escape. + MissingTarget, ///< An object member or array element does not exist. + ExpectedContainer, ///< Traversal encountered a scalar before the final token. + InvalidArrayIndex, ///< An array token is not a canonical non-negative index. + ArrayIndexOutOfRange, ///< An array token exceeds the current array bounds. + AppendTokenNotAllowed, ///< The append token is invalid for lookup. + AllocationFailure, ///< Diagnostic construction could not allocate. + InternalError ///< An unexpected internal exception was contained. }; - bool ok; - Code code; - std::string pointer; - size_t tokenIndex; - std::string token; - std::string message; + bool ok; ///< True exactly when code is Ok. + Code code; ///< Stable machine-readable result category. + std::string pointer; ///< Pointer text supplied by the caller. + size_t tokenIndex; ///< Zero-based index of the failing token. + std::string token; ///< Decoded failing token when available. + std::string message; ///< Human-readable diagnostic; wording is not stable. /// Constructs a successful lookup state with no pointer or token details. PointerError(); }; - // Structured JSON Patch (RFC 6902) / Merge Patch (RFC 7396) failure. - // Patch application is atomic: failure leaves the target unchanged. - // Reporting patch APIs reset all fields on entry and on success. - struct PatchError { + /// Structured JSON Patch (RFC 6902) / Merge Patch (RFC 7396) failure. + /// + /// Patch application is atomic: failure leaves the target unchanged. + /// Reporting patch APIs reset all fields on entry and on success. + struct PJSON_API PatchError { + /// Stable categories for programmatic JSON Patch failure handling. enum Code { - Ok, - InvalidPatchDocument, - OperationNotObject, - MissingOp, - MissingPath, - MissingFrom, - MissingValue, - InvalidOp, - InvalidPath, - InvalidFrom, - TargetMissing, - InvalidArrayIndex, - ArrayIndexOutOfRange, - MoveRootNotAllowed, - MoveIntoDescendant, - TestFailed, - ResourceLimit, - AllocationFailure, - InternalError + Ok, ///< Patch application succeeded. + InvalidPatchDocument, ///< The patch document is not an array. + OperationNotObject, ///< An operation entry is not an object. + MissingOp, ///< An operation lacks a string `op` member. + MissingPath, ///< An operation lacks a string `path` member. + MissingFrom, ///< A copy or move lacks a string `from` member. + MissingValue, ///< An add, replace, or test lacks `value`. + InvalidOp, ///< The operation name is unsupported. + InvalidPath, ///< The destination JSON Pointer is invalid. + InvalidFrom, ///< The source JSON Pointer is invalid. + TargetMissing, ///< A required source, parent, or target is absent. + InvalidArrayIndex, ///< An array path token is not a valid index. + ArrayIndexOutOfRange, ///< An array path token exceeds valid bounds. + MoveRootNotAllowed, ///< A move attempts to remove the document root. + MoveIntoDescendant, ///< A move destination lies under its source. + TestFailed, ///< A test operation did not compare equal. + ResourceLimit, ///< A configured patch-work limit was reached. + AllocationFailure, ///< Patch application could not allocate. + InternalError ///< An unexpected internal exception was contained. }; - bool ok; - Code code; - size_t opIndex; - std::string op; - std::string path; - std::string from; - size_t tokenIndex; - std::string token; - std::string message; + bool ok; ///< True exactly when code is Ok. + Code code; ///< Stable machine-readable result category. + size_t opIndex; ///< Zero-based index of the failing operation. + std::string op; ///< Operation name when available. + std::string path; ///< Destination pointer when available. + std::string from; ///< Source pointer when available. + size_t tokenIndex; ///< Zero-based index of the failing pointer token. + std::string token; ///< Decoded failing pointer token when available. + std::string message; ///< Human-readable diagnostic; wording is not stable. /// Constructs a successful patch state with no operation or token details. PatchError(); }; - // Bounds transactional patch amplification. Zero selects the documented - // built-in ceiling rather than disabling a safety limit. Clone bytes - // include node storage plus string and object-key payload bytes. - struct PatchOptions { - size_t maxOperations; // default/hard ceiling: 10,000 - size_t maxClonedNodes; // default/hard ceiling: 1,000,000 - size_t maxClonedBytes; // default/hard ceiling: 64 MiB - size_t maxWork; // default/hard ceiling: 1,000,000 + /// Bounds transactional patch amplification. + /// + /// Zero selects the documented built-in ceiling rather than disabling a + /// safety limit. Clone bytes include node storage plus string and + /// object-key payload bytes. + struct PJSON_API PatchOptions { + size_t maxOperations; ///< Operation ceiling; zero selects the hard 10,000 limit. + size_t maxClonedNodes; ///< Clone-node ceiling; zero selects the hard 1,000,000 limit. + size_t maxClonedBytes; ///< Clone-byte ceiling; zero selects the hard 64 MiB limit. + size_t maxWork; ///< Work ceiling; zero selects the hard 1,000,000 limit. + /// Selects the documented finite default limits. PatchOptions(); }; - // Controls JSON serialization. The default produces the same compact, - // ascending-key output as toString()/write() without options. Pretty - // output places each array element/object member on its own line. Only - // space and tab are valid indentation characters; any other value is - // treated as a space so serialization always remains valid JSON. - // - // Objects are stored in std::map, so source/insertion order is not - // available. Key ordering is therefore explicitly ascending or - // descending according to std::map's bytewise std::string ordering. - struct SerializeOptions { - enum KeyOrder { AscendingKeys, DescendingKeys }; - - bool pretty; - size_t indentWidth; - char indentCharacter; - bool escapeNonAscii; - KeyOrder keyOrder; - size_t maxOutputBytes; // default 64 MiB; zero explicitly means unlimited - - /// Selects compact output, two-space indentation, and ascending keys. + /// Controls JSON serialization. + /// + /// The default produces compact output in native object-storage order. + /// Pretty output places each array + /// element/object member on its own line. Only space and tab are valid + /// indentation characters; any other value is treated as a space so + /// serialization always remains valid JSON. + /// + /// Object storage and serialization order are unspecified. + struct PJSON_API SerializeOptions { + // Governs how a stored non-finite double (NaN, +/-infinity) is + // serialized. JSON has no non-finite literal, so the default fails + // with a structured error rather than silently changing the value's + // type. NonFiniteToNull opts in to the legacy behavior of writing + // JSON null; NonFiniteToString writes the strings "NaN", + // "Infinity", and "-Infinity" for interoperability with permissive + // consumers. The chosen policy applies identically to compact, + // pretty, buffered, and streaming output. + /// Selects how stored NaN and infinity values are represented. + enum NonFinitePolicy { + RejectNonFinite, ///< Fail because JSON has no non-finite number literal. + NonFiniteToNull, ///< Emit non-finite values as JSON null. + NonFiniteToString ///< Emit "NaN" or signed "Infinity" strings. + }; + + bool pretty; ///< Enables line breaks and indentation. + size_t indentWidth; ///< Indentation characters per nesting level. + char indentCharacter; ///< Space or tab; invalid values are treated as space. + bool escapeNonAscii; ///< Emits non-ASCII code points as Unicode escapes. + NonFinitePolicy nonFinite; ///< Policy for NaN and infinity values. + size_t maxOutputBytes; ///< Output ceiling; zero explicitly means unlimited. + + /// Selects compact output, two-space indentation, and non-finite rejection. SerializeOptions(); /// Returns the defaults with pretty printing enabled. static SerializeOptions prettyPrinted(); }; - // Event sink for non-owning SAX parsing. Return false from any callback - // to cancel parsing; public parseSax* APIs return false for cancellation - // or thrown exceptions and populate ParseError when one is supplied. - // - // Callbacks are delivered in source order. Duplicate-key policy still - // applies: RejectDuplicateKeys fails on the duplicate key, - // KeepFirstDuplicate suppresses later duplicate-value subtrees, and - // KeepLastDuplicate accepts duplicates while still reporting both - // occurrences because a streaming SAX walk cannot retract prior events. - // String and key references are borrowed and remain valid only for the - // duration of their callback. The handler itself need only outlive the - // parseSax* call. Default callbacks accept the event and do nothing. - struct SaxHandler { - /// Enables destruction through a SaxHandler base pointer. - virtual ~SaxHandler(); - /// Receives a JSON null value; return false to cancel parsing. - virtual bool onNull(); - /// Receives a JSON boolean value; return false to cancel parsing. - virtual bool onBool(bool aValue); - /// Receives an integer-valued JSON number; return false to cancel parsing. - virtual bool onInt(int64_t aValue); - /// Receives a floating-point JSON number; return false to cancel parsing. - virtual bool onDouble(double aValue); - /// Receives borrowed decoded string bytes; return false to cancel parsing. - virtual bool onString(const std::string& aValue); - /// Marks the beginning of an array; return false to cancel parsing. - virtual bool onStartArray(); - /// Marks the end of an array; return false to cancel parsing. - virtual bool onEndArray(); - /// Marks the beginning of an object; return false to cancel parsing. - virtual bool onStartObject(); - /// Receives a borrowed decoded object key; return false to cancel parsing. - virtual bool onKey(const std::string& aKey); - /// Marks the end of an object; return false to cancel parsing. - virtual bool onEndObject(); - }; - - // One schema-validation failure: `path` is a JSON Pointer to the - // offending node (e.g. "/address/zip", "" for the document root) and - // `message` explains what was wrong. - struct SchemaError { - std::string path; - std::string message; - /// Constructs an error with an empty root path and message. - SchemaError(); - /// Constructs an error for aPath with the supplied diagnostic message. - SchemaError(const std::string& aPath, const std::string& aMsg); - }; + /// Structured outcome for the non-throwing serialization APIs. + struct PJSON_API SerializeError { + /// Stable categories for programmatic serialization failure handling. + enum Code { + None, ///< Serialization succeeded. + InvalidUtf8, ///< A stored string or object key is not valid UTF-8. + NonFiniteNumber, ///< NaN or infinity was rejected by the active policy. + OutputLimit, ///< maxOutputBytes or representable output size was exceeded. + AllocationFailure, ///< Temporary serialization storage could not be allocated. + StreamFailure, ///< The destination stream rejected a physical write. + InternalError ///< An unexpected internal exception was contained. + }; - // Bounds schema regular-expression work. By default only a conservative, - // non-ambiguous ECMAScript subset is accepted and both pattern/subject - // sizes are capped, preventing catastrophic std::regex backtracking. - // trustedRegex() restores unrestricted ECMAScript regex behavior for - // schemas and input controlled by the application. - struct SchemaOptions { - size_t maxRegexPatternBytes; // 0 = unlimited (default: 256) - size_t maxRegexSubjectBytes; // 0 = unlimited (default: 4096) - bool allowUnsafeRegex; // default false - /// Recursive validation depth (default and absolute hard ceiling: 64). - /// Zero selects 64, and larger values are clamped to 64. - size_t maxValidationDepth; - /// Resolved references (default 1024); zero selects the hard ceiling of 1024. - size_t maxRefResolutions; - /// Validation work units (default 1,000,000); zero selects that hard ceiling. - size_t maxValidationWork; - /// Reported errors (default 100); zero selects the hard ceiling of 100. - size_t maxErrors; - bool validateFormats; // validate known string formats (default true) - /// Selects bounded safe-regex, traversal, reference, work, error, and format defaults. - SchemaOptions(); - /// Disables only regex restrictions; all other defaults remain enabled. - static SchemaOptions trustedRegex(); + Code code; ///< Stable machine-readable result category. + std::string message; ///< Human-readable diagnostic; empty on success. + /// Constructs a successful serialization result. + SerializeError(); + /// Resets the result to success. + void reset() noexcept; }; //== Construction / lifetime ========================================= /// Constructs null using the process-lifetime default allocator. - pjson(); + pjson() noexcept; /// Constructs null bound to borrowed aAlloc, which must outlive this tree. explicit pjson(Allocator& aAlloc) noexcept; /// Destroys this value and its complete owned subtree. @@ -345,104 +290,35 @@ namespace ByteDance { void resetTo(jsonType aeType); /// Calls resetTo() only when the type differs, otherwise preserving contents. void resetIfNeeded(jsonType aeType); - // Same-allocator swap is O(1). A cross-allocator swap is rejected as a - // safe no-op; use canSwap() to test before requesting it. - /// Exchanges contents when allocators match; otherwise does nothing. + // Same-allocator, non-overlapping swap is O(1). Cross-allocator and + // ancestor/descendant swaps are rejected as safe no-ops; use canSwap() + // to test before requesting one. + /// Exchanges compatible, non-overlapping contents; otherwise does nothing. void swap(pjson& aOther) noexcept; /// Returns the borrowed allocator bound to this value. Allocator& getAllocator() const noexcept; - /// Returns whether swap(aOther) can exchange contents. + /// Returns whether swap(aOther) can exchange contents safely. bool canSwap(const pjson& aOther) const noexcept; - //== DOM parsing with the default allocator ========================== - // Each parse accepts exactly one JSON value followed only by whitespace. - // In-memory parse failures return an empty pointer; diagnostic overloads - // reset aError and describe the first failure. A byte span may contain - // embedded NUL bytes, but a null aSrc is always an error. - /// Parses aStr into an owning tree using the default allocator. - static pjson::unique_ptr parse(const std::string& aStr, - const ParseOptions& aOpts = ParseOptions()); - /// Parses the aSize-byte span at aSrc using the default allocator. - static pjson::unique_ptr parse(const char* aSrc, size_t aSize, - const ParseOptions& aOpts = ParseOptions()); - /// Parses aStr and reports the first failure in aError. - static pjson::unique_ptr parse(const std::string& aStr, ParseError& aError, - const ParseOptions& aOpts = ParseOptions()); - /// Parses the aSize-byte span and reports the first failure in aError. - static pjson::unique_ptr parse(const char* aSrc, size_t aSize, ParseError& aError, - const ParseOptions& aOpts = ParseOptions()); - - // parseStream() buffers the document in chunks while enforcing - // maxInputBytes. Stream or temporary-buffer exceptions may propagate. - /// Buffers and parses one document from aIn using the default allocator. - static pjson::unique_ptr parseStream(std::istream& aIn, - const ParseOptions& aOpts = ParseOptions()); - /// Buffers and parses aIn, reporting ordinary parse/read failures in aError. - static pjson::unique_ptr parseStream(std::istream& aIn, ParseError& aError, - const ParseOptions& aOpts = ParseOptions()); - - //== DOM parsing with a custom allocator ============================= - // Allocator-aware DOM parsing routes root/child nodes and string/array/ - // object wrapper objects through borrowed aAlloc. Standard-container - // backing buffers still use their standard allocators, as described by - // Allocator above. aAlloc must outlive the returned tree. - /// Parses aStr with allocator-backed nodes and wrapper objects. - static unique_ptr parse(const std::string& aStr, Allocator& aAlloc, - const ParseOptions& aOpts = ParseOptions()); - /// Parses a byte span with allocator-backed nodes and wrapper objects. - static unique_ptr parse(const char* aSrc, size_t aSize, Allocator& aAlloc, - const ParseOptions& aOpts = ParseOptions()); - /// Parses aStr with aAlloc and reports the first failure in aError. - static unique_ptr parse(const std::string& aStr, ParseError& aError, Allocator& aAlloc, - const ParseOptions& aOpts = ParseOptions()); - /// Parses a byte span with aAlloc and reports the first failure in aError. - static unique_ptr parse(const char* aSrc, size_t aSize, ParseError& aError, - Allocator& aAlloc, const ParseOptions& aOpts = ParseOptions()); - /// Buffers aIn, then parses with allocator-backed nodes and wrappers. - static unique_ptr parseStream(std::istream& aIn, Allocator& aAlloc, - const ParseOptions& aOpts = ParseOptions()); - /// Buffers and parses aIn with aAlloc, reporting ordinary failures in aError. - static unique_ptr parseStream(std::istream& aIn, ParseError& aError, Allocator& aAlloc, - const ParseOptions& aOpts = ParseOptions()); - - //== SAX parsing ===================================================== - // SAX parsing retains neither aHandler nor callback arguments. It returns - // false for invalid input, cancellation, stream failure, or a handler - // exception; callbacks already delivered before failure are not undone. - /// Parses aStr and emits its events to aHandler without building a DOM. - static bool parseSax(const std::string& aStr, SaxHandler& aHandler, - const ParseOptions& aOpts = ParseOptions()); - /// Parses the aSize-byte span and emits its events to aHandler. - static bool parseSax(const char* aSrc, size_t aSize, SaxHandler& aHandler, - const ParseOptions& aOpts = ParseOptions()); - /// SAX-parses aStr and reports failure or cancellation in aError. - static bool parseSax(const std::string& aStr, SaxHandler& aHandler, ParseError& aError, - const ParseOptions& aOpts = ParseOptions()); - /// SAX-parses a byte span and reports failure or cancellation in aError. - static bool parseSax(const char* aSrc, size_t aSize, SaxHandler& aHandler, - ParseError& aError, const ParseOptions& aOpts = ParseOptions()); - // True streaming SAX parse: reads the istream incrementally and never - // buffers the full document in memory. - /// Incrementally parses aIn and emits events to aHandler. - static bool parseSaxStream(std::istream& aIn, SaxHandler& aHandler, - const ParseOptions& aOpts = ParseOptions()); - /// Incrementally SAX-parses aIn and reports failure or cancellation in aError. - static bool parseSaxStream(std::istream& aIn, SaxHandler& aHandler, ParseError& aError, - const ParseOptions& aOpts = ParseOptions()); - //== Serialization =================================================== - // Non-finite stored doubles serialize as JSON null. Invalid UTF-8 in a - // string value or object key is a serialization failure: toString() throws, - // while write() sets failbit (and may propagate stream exceptions). - // toString() may also throw for allocation or length failure. + // Invalid UTF-8 and rejected non-finite doubles are serialization + // failures: the convenience toString() overloads throw, while legacy + // write() sets failbit (and may propagate enabled stream exceptions). + // The SerializeError overloads never throw and expose stable categories. /// Returns compact JSON using the default serialization options. std::string toString() const; /// Returns JSON serialized according to aOpts. std::string toString(const SerializeOptions& aOpts) const; + /// Serializes into aOut transactionally and reports failure without throwing. + bool toString(std::string& aOut, SerializeError& aError, + const SerializeOptions& aOpts = SerializeOptions()) const noexcept; /// Writes compact JSON to aOut using the default serialization options. void write(std::ostream& aOut) const; /// Writes JSON configured by aOpts to aOut. void write(std::ostream& aOut, const SerializeOptions& aOpts) const; + /// Writes JSON and reports logical or physical failure without throwing. + bool write(std::ostream& aOut, SerializeError& aError, + const SerializeOptions& aOpts = SerializeOptions()) const noexcept; //== Type inspection ================================================= /// Returns this node's stored JSON representation. @@ -451,10 +327,14 @@ namespace ByteDance { bool isNull() const; /// Returns whether this node stores a string. bool isString() const; - /// Returns whether this node stores either numeric representation. + /// Returns whether this node stores any numeric representation. bool isNumber() const; - /// Returns whether this node stores an integer representation. + /// Returns whether this node stores a signed-integer representation. bool isInt() const; + /// Returns whether this node stores an unsigned-integer representation. + bool isUInt() const; + /// Returns whether this node stores any integer representation (signed or unsigned). + bool isInteger() const; /// Returns whether this node stores a floating-point representation. bool isDouble() const; /// Returns whether this node stores a boolean. @@ -464,13 +344,14 @@ namespace ByteDance { /// Returns whether this node stores an object. bool isObject() const; - // Minimal C++11-compatible, non-owning view of a JSON string. A view - // aliases bytes owned by this pjson node and is valid only while that - // node remains alive and unchanged. Assignment, reset, swap, move, - // destruction, erasing the node, or replacing/resetting an ancestor - // invalidates it. Strings may contain embedded NUL bytes; use size() - // rather than strlen(). - class StringView { + /// Minimal C++11-compatible, non-owning view of a JSON string. + /// + /// A view aliases bytes owned by this pjson node and is valid only while + /// that node remains alive and unchanged. Assignment, reset, swap, move, + /// destruction, erasing the node, or replacing/resetting an ancestor + /// invalidates it. Strings may contain embedded NUL bytes; use size() + /// rather than strlen(). + class PJSON_API StringView { public: /// Constructs an empty view with data() == nullptr. StringView() noexcept; @@ -491,10 +372,15 @@ namespace ByteDance { }; // Strict typed access to this node. On a type mismatch, returns false - // and leaves aResult unchanged. Integers may widen to double; no other - // coercions are performed. StringView avoids a string copy. - /// Extracts an integer only when this node stores jsonNumberInt. + // and leaves aResult unchanged. A signed integer read accepts an + // unsigned value only when it fits in int64_t; an unsigned integer read + // accepts a signed value only when it is non-negative; a double read + // widens any integer. No other coercions are performed. StringView + // avoids a string copy. + /// Extracts a signed integer; an unsigned value must fit in int64_t. bool tryGet(int64_t& aResult) const noexcept; + /// Extracts an unsigned integer; a signed value must be non-negative. + bool tryGet(uint64_t& aResult) const noexcept; /// Extracts a numeric value, widening a stored integer when necessary. bool tryGet(double& aResult) const noexcept; /// Extracts a boolean only when this node stores jsonBoolean. @@ -512,14 +398,43 @@ namespace ByteDance { /// Empties a container without changing its type, or resets a scalar to null. void clear(); - /// Returns copied object keys in std::map order, or an empty vector otherwise. + /// Returns copied object keys in unspecified storage order, or an empty vector otherwise. std::vector keys() const; + //== Non-allocating traversal ======================================= + /// Callback for read-only object-member traversal. + typedef bool (*ConstMemberVisitor)(StringView aKey, const pjson& aValue, void* aContext); + /// Callback for mutable object-member traversal. + typedef bool (*MemberVisitor)(StringView aKey, pjson& aValue, void* aContext); + /// Callback for read-only array-element traversal. + typedef bool (*ConstElementVisitor)(const pjson& aValue, void* aContext); + /// Callback for mutable array-element traversal. + typedef bool (*ElementVisitor)(pjson& aValue, void* aContext); + /// + /// Traversal copies no object names and performs no per-member lookup. + /// Object members are visited in unspecified storage order and array + /// elements in index order. Borrowed arguments are valid only for the callback. The + /// opaque context is forwarded unchanged. Returning false stops early. + /// A callback must not resize the traversed container. Calling a traversal + /// method on the wrong container type is a no-op that returns true. + /// Visits each object member as (key view, const value); false stops early. + bool forEachMember(ConstMemberVisitor aVisitor, void* aContext) const; + /// Visits each object member as (key view, mutable value); false stops early. + bool forEachMember(MemberVisitor aVisitor, void* aContext); + /// Visits each array element as a const value; false stops early. + bool forEachElement(ConstElementVisitor aVisitor, void* aContext) const; + /// Visits each array element as a mutable value; false stops early. + bool forEachElement(ElementVisitor aVisitor, void* aContext); + //== Non-mutating lookup ============================================= /// Returns whether this object contains aKey. bool hasKey(const std::string& aKey) const; /// Returns whether this object contains non-null aKey; null returns false. bool hasKey(const char* aKey) const; + /// Alias for hasKey(aKey); reads more naturally at call sites. + bool contains(const std::string& aKey) const; + /// Alias for hasKey(aKey); reads more naturally at call sites. + bool contains(const char* aKey) const; /// Returns whether this array contains aIndex; negative indexes count from the end. bool hasIndex(int aIndex) const noexcept; @@ -542,6 +457,10 @@ namespace ByteDance { pjson* find(int aIndex) noexcept; /// Returns the read-only borrowed array child at aIndex, or null on failure. const pjson* find(int aIndex) const noexcept; + /// Returns the borrowed array child at a non-negative index, or null on failure. + pjson* findIndex(size_t aIndex) noexcept; + /// Returns the read-only borrowed array child at a non-negative index, or null. + const pjson* findIndex(size_t aIndex) const noexcept; // RFC 6901 lookup. The empty pointer addresses this value; every // non-empty pointer must begin with '/'. Lookups are iterative and @@ -570,6 +489,8 @@ namespace ByteDance { // unchanged. Negative indexes count from the end. /// Extracts the integer child at aKey without mutating this object. bool tryGet(const std::string& aKey, int64_t& aResult) const; + /// Extracts the unsigned-integer child at aKey without mutating this object. + bool tryGet(const std::string& aKey, uint64_t& aResult) const; /// Extracts the numeric child at aKey as a double. bool tryGet(const std::string& aKey, double& aResult) const; /// Extracts the boolean child at aKey. @@ -581,6 +502,8 @@ namespace ByteDance { /// Extracts the integer child at non-null aKey. bool tryGet(const char* aKey, int64_t& aResult) const; + /// Extracts the unsigned-integer child at non-null aKey. + bool tryGet(const char* aKey, uint64_t& aResult) const; /// Extracts the numeric child at non-null aKey as a double. bool tryGet(const char* aKey, double& aResult) const; /// Extracts the boolean child at non-null aKey. @@ -592,6 +515,8 @@ namespace ByteDance { /// Extracts the integer array child at aIndex. bool tryGet(int aIndex, int64_t& aResult) const noexcept; + /// Extracts the unsigned-integer array child at aIndex. + bool tryGet(int aIndex, uint64_t& aResult) const noexcept; /// Extracts the numeric array child at aIndex as a double. bool tryGet(int aIndex, double& aResult) const noexcept; /// Extracts the boolean array child at aIndex. @@ -604,30 +529,96 @@ namespace ByteDance { //== Building / mutable access ======================================= // operator[] is a direct builder API. A key access changes a non-object // into an object and creates a missing null child. An index access changes - // a non-array into an array; negative indexes count from the end and clamp - // before the beginning to zero, while indexes past the end grow the array - // with null children. A single access that would create more than one - // million children throws std::length_error before mutation. Use - // find()/tryGet() for reads. + // a non-array into an array; valid negative indexes count from the end, + // while an index before the beginning throws std::out_of_range without + // mutation. Non-negative indexes past the end grow the array with null + // children. A single access that would create more than one million + // children throws std::length_error before mutation. Use find()/tryGet() + // for reads. /// Returns or creates the child at aString. pjson& operator[](const std::string& aString); /// Returns or creates the child at aSkey; throws std::invalid_argument for null. pjson& operator[](const char* aSkey); /// Returns or creates the child at index under the auto-growth rules above. pjson& operator[](int index); + /// Returns or creates the child at a non-negative index. + pjson& operator[](size_t index); + + //== Factories and typed construction ================================ + // Explicit, unambiguous ways to create each JSON kind without relying on + // default construction having a particular type. Each uses the default + // allocator; the allocator-aware constructors remain available for + // custom-allocator trees. + /// Returns a JSON null value. + static pjson null(); + /// Returns an empty JSON object value. + static pjson object(); + /// Returns an empty JSON array value. + static pjson array(); + /// Replaces this value with JSON null (std::nullptr_t assignment). + pjson& operator=(std::nullptr_t); + + //== Checked access ================================================== + // at() is a checked, non-vivifying accessor. Unlike operator[], it never + // creates a child: a missing object key, an out-of-range index, or a + // wrong container type throws std::out_of_range. + /// Returns the existing child at aKey or throws std::out_of_range. + pjson& at(const std::string& aKey); + /// Returns the read-only child at aKey or throws std::out_of_range. + const pjson& at(const std::string& aKey) const; + /// Returns the existing element at aIndex or throws std::out_of_range. + pjson& at(size_t aIndex); + /// Returns the read-only element at aIndex or throws std::out_of_range. + const pjson& at(size_t aIndex) const; + + //== Generic child insertion ======================================== + // Append or assign arbitrary pjson values, not just scalars. A non-array + // target is promoted to an array by pushBack. Cross-allocator inserts + // deep-copy the value into this node's allocator. + /// Appends a deep copy of aValue, promoting this node to an array. + pjson& pushBack(const pjson& aValue); + /// Moves an independent same-allocator value; otherwise deep-copies it. + pjson& pushBack(pjson&& aValue); + /// Inserts or replaces the member aKey with a deep copy of aValue. + pjson& insertOrAssign(const std::string& aKey, const pjson& aValue); + /// Moves an independent same-allocator value; otherwise deep-copies it. + pjson& insertOrAssign(const std::string& aKey, pjson&& aValue); + /// Reserves capacity for at least aCount array elements (no-op for non-arrays + /// unless this node is first made an array); returns *this for chaining. + pjson& reserve(size_t aCount); // Assign a scalar value, replacing whatever this node was. Numbers are - // stored as int64_t (integers) or double (floating point). + // stored as int64_t (signed integers), uint64_t (unsigned integers), or + // double (floating point). An explicit uint64_t assignment retains the + // unsigned type identity even for small values. /// Replaces this value with a copy of aString. pjson& operator=(const std::string& aString); /// Replaces this value with aCString; throws std::invalid_argument for null. pjson& operator=(const char* aCString); /// Replaces this value with aBool. pjson& operator=(const bool aBool); - /// Replaces this value with aInt. - pjson& operator=(const int64_t aInt); - /// Replaces this value with aDouble; non-finite values serialize as null. + /// Replaces this value with a signed integer. + pjson& operator=(const int aInt); + /// Replaces this value with an unsigned integer. + pjson& operator=(const unsigned int aUInt); + /// Replaces this value with a signed short integer. + pjson& operator=(const short aInt); + /// Replaces this value with an unsigned short integer. + pjson& operator=(const unsigned short aUInt); + /// Replaces this value with a signed long integer. + pjson& operator=(const long aInt); + /// Replaces this value with an unsigned long integer. + pjson& operator=(const unsigned long aUInt); + /// Replaces this value with a signed long-long integer. + pjson& operator=(const long long aInt); + /// Replaces this value with an unsigned long-long integer. + pjson& operator=(const unsigned long long aUInt); + /// Replaces this value with a floating-point number. + pjson& operator=(const float aFloat); + /// Replaces this value with aDouble; the non-finite policy governs output. pjson& operator=(const double aDouble); + /// Narrows a long double to the library's binary64 number representation. + pjson& operator=(const long double aDouble); // Vector assignment atomically replaces this node with an array of copied // children allocated through this node's allocator. @@ -635,10 +626,28 @@ namespace ByteDance { pjson& operator=(const std::vector& aValueArray); /// Replaces this value with a copied boolean array. pjson& operator=(const std::vector& aValueArray); - /// Replaces this value with a copied integer array. - pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied native-integer array. + pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied native unsigned-integer array. + pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied signed-short array. + pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied unsigned-short array. + pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied signed-long array. + pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied unsigned-long array. + pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied signed-long-long array. + pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied unsigned-long-long array. + pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied float array. + pjson& operator=(const std::vector& aValueArray); /// Replaces this value with a copied double array. pjson& operator=(const std::vector& aValueArray); + /// Replaces this value with a copied long-double array, narrowing each value to double. + pjson& operator=(const std::vector& aValueArray); // Scalar append adds one copied child. If this node is not already an // array, its previous value is discarded rather than retained. @@ -648,10 +657,28 @@ namespace ByteDance { pjson& operator+=(const char* aValue); /// Appends aValue as a boolean child. pjson& operator+=(const bool aValue); - /// Appends aValue as an integer child. - pjson& operator+=(const int64_t aValue); + /// Appends aValue as a signed integer child. + pjson& operator+=(const int aValue); + /// Appends aValue as an unsigned integer child. + pjson& operator+=(const unsigned int aValue); + /// Appends aValue as a signed-short child. + pjson& operator+=(const short aValue); + /// Appends aValue as an unsigned-short child. + pjson& operator+=(const unsigned short aValue); + /// Appends aValue as a signed-long child. + pjson& operator+=(const long aValue); + /// Appends aValue as an unsigned-long child. + pjson& operator+=(const unsigned long aValue); + /// Appends aValue as a signed-long-long child. + pjson& operator+=(const long long aValue); + /// Appends aValue as an unsigned-long-long child. + pjson& operator+=(const unsigned long long aValue); + /// Appends aValue as a floating-point child. + pjson& operator+=(const float aValue); /// Appends aValue as a double child. pjson& operator+=(const double aValue); + /// Narrows aValue to the library's binary64 number representation and appends it. + pjson& operator+=(const long double aValue); // Vector append copies every element. A non-array's prior value is // discarded; even an empty vector promotes a non-array to an empty array. @@ -659,10 +686,28 @@ namespace ByteDance { pjson& operator+=(const std::vector& aValueArray); /// Appends every boolean in aValueArray. pjson& operator+=(const std::vector& aValueArray); - /// Appends every integer in aValueArray. - pjson& operator+=(const std::vector& aValueArray); + /// Appends every native integer in aValueArray. + pjson& operator+=(const std::vector& aValueArray); + /// Appends every native unsigned integer in aValueArray. + pjson& operator+=(const std::vector& aValueArray); + /// Appends every signed short in aValueArray. + pjson& operator+=(const std::vector& aValueArray); + /// Appends every unsigned short in aValueArray. + pjson& operator+=(const std::vector& aValueArray); + /// Appends every signed long in aValueArray. + pjson& operator+=(const std::vector& aValueArray); + /// Appends every unsigned long in aValueArray. + pjson& operator+=(const std::vector& aValueArray); + /// Appends every signed long long in aValueArray. + pjson& operator+=(const std::vector& aValueArray); + /// Appends every unsigned long long in aValueArray. + pjson& operator+=(const std::vector& aValueArray); + /// Appends every float in aValueArray. + pjson& operator+=(const std::vector& aValueArray); /// Appends every double in aValueArray. pjson& operator+=(const std::vector& aValueArray); + /// Narrows and appends every long double in aValueArray. + pjson& operator+=(const std::vector& aValueArray); // Remove and free the child under a map key / at an array index. // Array indexes are zero-based and erasure shifts later elements left. @@ -701,70 +746,36 @@ namespace ByteDance { /// Returns the negation of operator==. bool operator!=(const pjson& aOther) const; - //== Schema validation =============================================== - // Validates this value against a schema that is itself a pjson object, - // using the documented JSON Schema subset; this is not a complete draft - // implementation. Returns true when the - // value conforms. Never throws. The second form appends reported - // keyword failures rather than stopping at the first. Errors inside - // non-selected anyOf/oneOf/not branches are intentionally suppressed, - // and a resource-budget failure can stop further validation. - // - // Supported keywords: - // type, enum, const, - // $ref (local JSON Pointer fragments), - // properties, patternProperties, propertyNames, required, - // dependentRequired, dependencies, additionalProperties, - // minProperties, maxProperties, - // items, minItems, maxItems, uniqueItems, - // minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, - // minLength, maxLength, pattern, format, - // allOf, anyOf, oneOf, not. - // A boolean schema (true/false) accepts/rejects everything. Unknown - // keywords and unsupported keyword shapes are ignored. Both inputs are - // borrowed and unchanged; the collecting overload appends to aErrors - // without clearing existing entries. Resource aborts may stop collection. - /// Returns whether this value satisfies aSchema under aOpts. - bool validate(const pjson& aSchema, - const SchemaOptions& aOpts = SchemaOptions()) const noexcept; - /// Validates and appends discovered failures to aErrors. - bool validate(const pjson& aSchema, std::vector& aErrors, - const SchemaOptions& aOpts = SchemaOptions()) const noexcept; + //== Numeric ordering ================================================ + // Exact ordering across the signed, unsigned, and double numeric kinds + // without rounding an integer through binary64. On success aOrder is set + // to -1, 0, or 1 for this value being less than, equal to, or greater + // than aOther. Returns false and leaves aOrder unchanged when either + // value is not a number, or when the comparison is unordered because a + // NaN is involved. This is the exact comparison callers (including + // schema validators) need for numeric bounds; equality alone is exposed + // through operator==. + /// Compares two stored numbers exactly; false when non-numeric/unordered. + bool tryCompareNumber(const pjson& aOther, int& aOrder) const noexcept; + + // NOTE: JSON Schema validation is no longer a member of pjson. It now + // lives in the standalone ByteDance::pJsonSchemaValidator helper declared + // in , which consumes only pjson's public API. This keeps + // the core DOM free of the schema/regex machinery. The validator carries + // its own Error and Options vocabulary types. private: //== Internal helpers ================================================ - // The parser, schema validator, and encoding routines live entirely in - // pjson.cpp as the pjsonImpl helper struct, so this header stays small. - // pjsonImpl is a friend so it can touch the data union directly; only - // the few instance helpers other members call are declared here. + // Encoding, ownership, and other DOM operations that need to touch the + // data members below live behind the pjsonImpl helper. Schema + // validation is deliberately separate and uses only the public API. + // pjsonImpl is a friend so it can reach private representation directly; + // no instance helper methods are declared here. friend struct pjsonImpl; - friend struct ValueDeleter; - - /// Iteratively deep-copies aFrom's contents using this node's allocator. - void copyContentsFrom(const pjson& aFrom); - - //== Data ============================================================ - typedef std::vector ArrayStorage; - typedef std::map ObjectStorage; - + // These two pointers are the stable ABI handle. Null values share a + // private implementation sentinel; non-null state belongs to _allocator. Allocator* _allocator; - bool _allocatorOwnedNode; - // Intrusive scratch link used only by allocation-free iterative tree - // destruction. It is null during normal object lifetime. - pjson* _disposeNext; - jsonType _eType = jsonType::jsonNull; - union Storage { - void* _pValueRaw; - ObjectStorage* _pValueMap; - ArrayStorage* _pValueArray; - int64_t _valueInt; - double _valueDouble; - bool _valueBool; - std::string* _pValueString; - - /// Initializes the raw representation to null. - Storage(); - } _uValue; + pjsonImpl* _pImpl; }; //======================================================================== }; // end namespace ByteDance diff --git a/pjsonlib/include/pjson_parser.h b/pjsonlib/include/pjson_parser.h new file mode 100644 index 0000000..4ebadae --- /dev/null +++ b/pjsonlib/include/pjson_parser.h @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +#ifndef PRAVEENJSON_PARSER_H +#define PRAVEENJSON_PARSER_H + +#include "pjson.h" + +namespace ByteDance { + struct pJsonParserImpl; + /// Configured, reusable JSON parser for DOM and SAX input. + /// + /// The parser depends on the pjson DOM, while pjson itself has no parser + /// dependency. A parser borrows its allocator, owns a copy of its options, + /// and keeps no mutable per-call state, so it may be reused for many inputs. + class PJSON_API pJsonParser { + public: + /// Bounds parsing work and selects duplicate-key and number policies. + struct PJSON_API Options { + /// Controls how repeated object member names are handled. + enum DuplicateKeyPolicy { + RejectDuplicateKeys, ///< Fail when a name occurs more than once. + KeepFirstDuplicate, ///< Retain the first value and discard later values. + KeepLastDuplicate ///< Replace an earlier value with the last value. + }; + /// Controls numeric tokens that cannot be represented exactly. + enum NumberPolicy { + RejectUnrepresentableNumbers, ///< Reject range overflow and nonzero underflow. + AllowLossyNumbers ///< Store the nearest finite double when possible. + }; + + int maxDepth; ///< Nesting limit; non-positive means one level. + size_t maxNodes; ///< Maximum values processed; zero is unlimited. + size_t maxInputBytes; ///< Maximum input bytes; zero is unlimited. + DuplicateKeyPolicy duplicateKeys; ///< Duplicate object-name policy. + NumberPolicy numberPolicy; ///< Unrepresentable-number policy. + /// Selects strict defaults and bounded parser resources. + Options(); + }; + + /// Structured result for DOM and SAX parsing. + struct PJSON_API Error { + /// Stable categories for programmatic parse-failure handling. + enum Code { + None = 0, ///< Parsing succeeded. + Syntax, ///< The input violates JSON grammar. + InvalidEncoding, ///< UTF-8 or an escaped Unicode value is invalid. + DuplicateKey, ///< A repeated object name was rejected. + NumberRange, ///< A number is outside the configured representation policy. + DepthLimit, ///< Nesting exceeded the effective depth limit. + InputLimit, ///< Input exceeded maxInputBytes. + NodeLimit, ///< Values processed exceeded maxNodes. + AllocationFailure, ///< Parser, DOM, or callback allocation failed. + StreamError, ///< Reading from the input stream failed. + CallbackError, ///< A SAX callback cancelled or threw. + InvalidArgument ///< The caller supplied an invalid argument. + }; + + bool ok; ///< True when the most recent parse succeeded. + Code code; ///< Stable machine-facing result category. + size_t offset; ///< Zero-based input byte offset. + size_t line; ///< One-based source line. + size_t column; ///< One-based byte column. + std::string message; ///< Human-readable diagnostic; wording is not stable. + /// Constructs the successful start-of-input state. + Error(); + }; + + /// Event sink for non-owning SAX parsing. + struct PJSON_API SaxHandler { + /// Enables destruction through a handler base pointer. + virtual ~SaxHandler(); + /// Receives null; return false to cancel. + virtual bool onNull(); + /// Receives a boolean; return false to cancel. + virtual bool onBool(bool aValue); + /// Receives a signed integer; return false to cancel. + virtual bool onInt(int64_t aValue); + /// Receives an unsigned integer above the signed range; return false to cancel. + virtual bool onUInt(uint64_t aValue); + /// Receives a floating-point number; return false to cancel. + virtual bool onDouble(double aValue); + /// Receives a borrowed decoded string; return false to cancel. + virtual bool onString(const std::string& aValue); + /// Marks the beginning of an array; return false to cancel. + virtual bool onStartArray(); + /// Marks the end of an array; return false to cancel. + virtual bool onEndArray(); + /// Marks the beginning of an object; return false to cancel. + virtual bool onStartObject(); + /// Receives a borrowed decoded key; return false to cancel. + virtual bool onKey(const std::string& aKey); + /// Marks the end of an object; return false to cancel. + virtual bool onEndObject(); + }; + + /// Uses the default DOM allocator and default parser options. + explicit pJsonParser(const Options& aOptions = Options()); + /// Uses borrowed aAllocator, which must outlive this parser and its DOM results. + explicit pJsonParser(pjson::Allocator& aAllocator, const Options& aOptions = Options()); + /// Releases the private parser configuration. + ~pJsonParser(); + /// Copies the parser configuration while borrowing the same allocator. + pJsonParser(const pJsonParser& aOther); + /// Transfers parser configuration and leaves aOther usable with defaults. + pJsonParser(pJsonParser&& aOther) noexcept; + /// Copies parser configuration while borrowing aOther's allocator. + pJsonParser& operator=(const pJsonParser& aOther); + /// Transfers parser configuration and leaves aOther usable with defaults. + pJsonParser& operator=(pJsonParser&& aOther) noexcept; + + /// Returns the immutable options used by every parse call. + const Options& options() const noexcept; + /// Returns the allocator used for DOM results. + pjson::Allocator& allocator() const noexcept; + + /// Parses one string document by value; failure returns JSON null. + pjson parse(const std::string& aInput) const; + /// Parses one exact byte span by value; failure returns JSON null. + pjson parse(const char* aInput, size_t aSize) const; + /// Parses one string document and resets/reports aError. + pjson parse(const std::string& aInput, Error& aError) const; + /// Parses one exact byte span and resets/reports aError. + pjson parse(const char* aInput, size_t aSize, Error& aError) const; + /// Buffers and parses one stream document by value. + pjson parseStream(std::istream& aInput) const; + /// Buffers and parses one stream document and resets/reports aError. + pjson parseStream(std::istream& aInput, Error& aError) const; + + /// Emits events for one string document. + bool parseSax(const std::string& aInput, SaxHandler& aHandler) const; + /// Emits events for one exact byte span. + bool parseSax(const char* aInput, size_t aSize, SaxHandler& aHandler) const; + /// Emits string-document events and resets/reports aError. + bool parseSax(const std::string& aInput, SaxHandler& aHandler, Error& aError) const; + /// Emits byte-span events and resets/reports aError. + bool parseSax(const char* aInput, size_t aSize, SaxHandler& aHandler, Error& aError) const; + /// Incrementally emits events from a stream. + bool parseSaxStream(std::istream& aInput, SaxHandler& aHandler) const; + /// Incrementally emits events from a stream and resets/reports aError. + bool parseSaxStream(std::istream& aInput, SaxHandler& aHandler, Error& aError) const; + + private: + pJsonParserImpl* _impl; + }; +} // namespace ByteDance + +#endif // PRAVEENJSON_PARSER_H diff --git a/pjsonlib/include/pjson_schema.h b/pjsonlib/include/pjson_schema.h new file mode 100644 index 0000000..3a838c2 --- /dev/null +++ b/pjsonlib/include/pjson_schema.h @@ -0,0 +1,242 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// pjson_schema.h — standalone JSON Schema validation for pjson. +// +// pJsonSchemaValidator validates a pjson value against a schema that is itself a +// pjson value. It is a pure consumer of pjson's public API: it holds a compiled +// (deep-copied) schema plus options and validates many instances against it. +// The core pjson type has no schema dependency. The implementation remains in +// focused private translation units so it can later become an independently +// linked optional component without changing the DOM API. +// +// Default options preserve pjson's documented subset dialect. The explicit +// Options::draft2020() mode implements the required Draft 2020-12 vocabularies +// and meta-schema compilation; optional format-assertion, arbitrary-precision, +// and cross-draft behavior remains outside the contract. +// +// Author: Praveen Babu J D +// License: Apache 2.0 +// +#ifndef PRAVEENJSON_SCHEMA_H +#define PRAVEENJSON_SCHEMA_H + +#include "pjson.h" + +#include +#include +#include + +namespace ByteDance { + //==[Interface]============================================================ + /// Validates pjson values against a schema (itself a pjson value). + /// + /// Construct once from a schema; validate many instances. The schema is + /// deep-copied on construction, so the caller's schema value may change or + /// be destroyed afterward. Validation never throws and never mutates its + /// inputs. Construction resolves and owns external resources; validate() is + /// read-only and may be called concurrently when each caller uses its own + /// error vector. + /// + /// Dialect contract: default construction implements the named subset dialect. + /// Options::draft2020() selects official Draft 2020-12, bundled meta-schema + /// validation, and per-resource vocabulary activation. Custom meta-schemas + /// require explicit resolver-based loading. Unknown optional vocabularies are + /// annotations and unknown required vocabularies fail compilation. + /// + /// Supported keywords (subset defaults and required Draft 2020-12 vocabularies): + /// type, enum, const, $ref, $dynamicRef, $id, $anchor, $dynamicAnchor; + /// properties, patternProperties, propertyNames, required, + /// dependentRequired, dependencies, dependentSchemas, + /// additionalProperties, unevaluatedProperties, minProperties, maxProperties; + /// items, prefixItems, contains, minContains, maxContains, + /// minItems, maxItems, uniqueItems, unevaluatedItems; + /// minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf; + /// minLength, maxLength, pattern, format; + /// allOf, anyOf, oneOf, not, if, then, else. + /// A boolean schema (true/false) accepts/rejects everything. By default + /// unknown or unsupported keywords are ignored; strict() rejects unsupported + /// standard keywords. External references require an explicit Resolver; + /// pjson never performs network I/O. Regular expressions use a private Unicode-aware + /// ECMAScript engine, including property escapes. + class PJSON_API pJsonSchemaValidator { + public: + /// Resolves one absolute schema-document URI during construction. + /// Implementations populate aSchema and return true, or return false + /// when unavailable. The resolved document is copied into a cache owned + /// by the validator; pjson performs no implicit network I/O. The callback + /// and aContext need remain valid only until construction returns. + typedef bool (*Resolver)(const std::string& aDocumentUri, pjson& aSchema, void* aContext); + + //== Diagnostics ===================================================== + /// One schema-compilation or instance-validation failure. + struct PJSON_API Error { + /// Distinguishes an instance-validation failure from an invalid or + /// unsupported schema contract discovered while compiling the validator. + enum Category { + InstanceValidation, ///< The instance violates a valid schema. + SchemaCompilation ///< The schema contract is invalid or unsupported. + }; + + /// Stable machine-readable failure category. `keyword` identifies + /// the precise keyword when several keywords share a category. + enum Code { + None, ///< No failure. + FalseSchema, ///< A false boolean schema rejected the instance. + TypeMismatch, ///< The instance does not satisfy `type`. + ConstMismatch, ///< The instance does not satisfy `const`. + EnumMismatch, ///< The instance does not satisfy `enum`. + NumericConstraint, ///< A numeric assertion failed. + StringConstraint, ///< A string assertion failed. + ArrayConstraint, ///< An array assertion failed. + ObjectConstraint, ///< An object assertion failed. + FormatMismatch, ///< A known asserted format failed. + CombinatorMismatch, ///< A logical applicator did not satisfy its contract. + ReferenceFailure, ///< A schema reference could not be resolved. + ReferenceCycle, ///< Reference evaluation encountered a cycle. + RegexFailure, ///< A pattern was invalid or rejected by safety policy. + UnsupportedKeyword, ///< Strict mode found an unsupported standard keyword. + InvalidSchema, ///< A schema or keyword has an invalid shape or value. + UnsupportedDialect, ///< The selected schema dialect is unsupported. + UnsupportedVocabulary, ///< A required vocabulary is unsupported. + ResolverFailure, ///< The caller's external resolver could not load a schema. + ResourceLimit, ///< A compilation, validation, or diagnostic limit was hit. + AllocationFailure, ///< Validation could not allocate required temporary state. + InternalError ///< An unexpected exception was contained. + }; + + Code code; ///< Stable machine-readable failure category. + Category category; ///< Compilation or instance-validation phase. + std::string instanceLocation; ///< JSON Pointer into the instance; empty is root. + std::string schemaLocation; ///< Pointer or retrieval URI plus fragment for the schema. + std::string keyword; ///< Triggering keyword; empty for whole-schema failures. + std::string message; ///< Human-readable diagnostic, not a stable API token. + std::vector causes; ///< Optional bounded combinator branch failures. + /// Constructs an empty diagnostic. + Error(); + /// Constructs a fully located diagnostic. + Error(Code aCode, Category aCategory, const std::string& aInstanceLocation, + const std::string& aSchemaLocation, const std::string& aKeyword, + const std::string& aMessage); + }; + + //== Options ========================================================= + /// Bounds schema work and controls dialect, format, and resolver policy. + /// + /// By default only a conservative, non-ambiguous ECMAScript subset is + /// accepted and both pattern/subject sizes are capped, preventing + /// catastrophic backtracking. trustedRegex() restores unrestricted + /// ECMAScript regex behavior for trusted schemas/data; the backend still + /// enforces its own finite work ceiling. + struct PJSON_API Options { + size_t maxRegexPatternBytes; ///< 0 = unlimited (default: 256). + size_t maxRegexSubjectBytes; ///< 0 = unlimited (default: 4096). + bool allowUnsafeRegex; ///< Permits unrestricted ECMAScript regex (default false). + /// Recursive validation depth (default and absolute hard ceiling: 32). + /// Zero selects 32, and larger values are clamped to 32. + size_t maxValidationDepth; ///< Recursive validation depth budget. + /// Resolved references (default 1024); zero selects the hard ceiling of 1024. + size_t maxRefResolutions; ///< Resolved-reference budget. + /// Validation work units (default 1,000,000); zero selects that hard ceiling. + size_t maxValidationWork; ///< Total validation work-unit budget. + /// Reported errors (default 100); zero selects the hard ceiling of 100. + size_t maxErrors; ///< Collected top-level and nested diagnostic budget. + bool stopAfterFirstError; ///< Stops compilation or validation after the first error. + bool collectNestedCauses; ///< Retains bounded anyOf/oneOf branch failures. + bool validateFormats; ///< Validates known string formats (default true). + // Strict, fail-closed subset mode. When true, a schema that uses a + // standard validation/applicator keyword this validator does not + // implement makes validation fail rather than silently ignoring the + // constraint. Unknown non-standard extension keywords are still + // allowed as annotations. Default false keeps permissive behavior. + bool strictSubset; ///< Rejects unsupported standard keywords when true. + /// Applies `$ref` siblings using pjson's modern subset semantics. + /// The default false preserves legacy Draft 7 replacement semantics. + bool refSiblings; ///< True when `$ref` siblings must also be evaluated. + /// Allows an unknown absolute `$schema` URI to be resolved during + /// construction and interpreted through its `$vocabulary` object. + bool resolveCustomDialects; ///< Enables explicit custom meta-schema resolution. + /// Retrieval URI used as the initial base when the root has no `$id`. + /// Leave empty only when all references are absolute or local. + std::string retrievalUri; ///< Initial base URI for a root without `$id`. + /// Dialect used when the root schema has no `$schema`. An empty value + /// selects documentedSubsetDialectUri(). Use draft2020() instead of + /// setting this field directly when selecting the official dialect. + std::string defaultDialectUri; ///< Dialect used when `$schema` is absent. + Resolver resolver; ///< Construction-only external-schema resolver. + void* resolverContext; ///< Construction-only opaque resolver context. + size_t maxResolvedDocuments; ///< External-document budget (default 32). + size_t maxResolvedBytes; ///< Compact resolved-DOM byte budget (default 16 MiB). + /// Selects bounded safe-regex, traversal, reference, work, error, and format defaults. + Options(); + /// Disables only regex restrictions; all other defaults remain enabled. + static Options trustedRegex(); + /// Returns the defaults with strict fail-closed subset mode enabled. + static Options strict(); + /// Selects modern subset semantics: `$ref` siblings apply and + /// `format` is annotation-only unless the caller re-enables it. + static Options modernSubset(); + /// Selects the official Draft 2020-12 dialect, modern `$ref` + /// semantics, annotation-only format, and custom dialect resolution. + static Options draft2020(); + }; + + //== Construction ==================================================== + /// Compiles aSchema (deep-copied with the default allocator) and all + /// resolved resources. Resolver failures, malformed references, and + /// duplicate IDs/anchors are reported through isSchemaValid() and + /// schemaErrors(); allocation failure may still throw std::bad_alloc. + explicit pJsonSchemaValidator(const pjson& aSchema, const Options& aOptions = Options()); + /// Destroys the compiled schema. + ~pJsonSchemaValidator(); + + //== Validation ====================================================== + /// Returns whether aInstance conforms to the compiled schema. Never throws. + bool validate(const pjson& aInstance) const noexcept; + /// Validates and appends discovered failures to aErrors. If schema + /// compilation failed, appends schemaErrors() instead. Never throws. + bool validate(const pjson& aInstance, std::vector& aErrors) const noexcept; + + //== Dialect / compilation contract =================================== + /// URI naming pjson's explicitly documented JSON Schema subset dialect. + static const char* documentedSubsetDialectUri() noexcept; + /// URI naming the vocabulary implemented by the subset dialect. + static const char* documentedSubsetVocabularyUri() noexcept; + /// URI of the supported official JSON Schema Draft 2020-12 dialect. + static const char* draft2020DialectUri() noexcept; + /// Returns whether dialect and required-vocabulary compilation succeeded. + bool isSchemaValid() const noexcept; + /// Returns immutable schema-compilation diagnostics (paths address the schema). + const std::vector& schemaErrors() const noexcept; + /// Returns the effective dialect URI selected by `$schema` or the option default. + const std::string& dialect() const noexcept; + + //== Introspection =================================================== + /// Returns the compiled schema value (read-only). + const pjson& schema() const noexcept; + /// Returns the options in effect for this validator. + /// Construction-only resolver pointers are cleared in this snapshot. + const Options& options() const noexcept; + + private: + struct Impl; + pJsonSchemaValidator(const pJsonSchemaValidator&); + pJsonSchemaValidator& operator=(const pJsonSchemaValidator&); + + Impl* _impl; + }; +} // namespace ByteDance + +#endif /* !PRAVEENJSON_SCHEMA_H */ diff --git a/pjsonlib/src/pjson.cpp b/pjsonlib/src/pjson.cpp index 5fbac7f..55f6a4f 100644 --- a/pjsonlib/src/pjson.cpp +++ b/pjsonlib/src/pjson.cpp @@ -16,6060 +16,1880 @@ // Author: Praveen Babu J D // License: Apache 2.0 // -#include "pjson.h" +#include "pjson_internal.h" #include -#include +#include #include #include -#include #include -#include -#include -#include #include -#include #include -#include -#include -#include -#include +#include #include #include #include using namespace ByteDance; -//===----------------------------------------------------------------------===// -// pjsonImpl — all parsing, schema-validation, and encoding helpers. -// -// Keeping implementation-only operations in one friend struct leaves pjson.h -// declaration-focused while allowing these helpers to maintain DOM invariants. -//===----------------------------------------------------------------------===// -struct ByteDance::pjsonImpl { - // Public APIs deliberately hide the owning container representation. - typedef std::vector ArrayStorage; - typedef std::map ObjectStorage; - - // Parser state threaded through the recursive-descent scanner: the input - // buffer, cursor, options, current/maximum nesting depth, a running count - // of allocated nodes (bounded by maxNodes to stop memory-amplification - // attacks), and the first error encountered (if any). - struct ParseCtx { - const char* src; - size_t pos; - size_t end; - pjson::ParseOptions::DuplicateKeyPolicy duplicateKeys; - int depth; - int maxDepth; - size_t nodeCount; - size_t maxNodes; // 0 = unlimited - pjson::Allocator* allocator; - bool failed; - size_t errPos; - std::string errMsg; - }; - - // One suspended container in the iterative serializer. Exactly one of - // array/object is active according to isObject; the associated cursor - // always denotes the next child to emit. - struct SerializeFrame { - bool isObject; - size_t depth; - bool first; - const ArrayStorage* array; - size_t arrayIndex; - const ObjectStorage* object; - ObjectStorage::const_iterator objectIt; - ObjectStorage::const_reverse_iterator objectReverseIt; - }; - - // One compiled schema regex or a cached policy/syntax rejection. Keeping - // failures in the cache is as important as caching successful compilation: - // patternProperties must not repeatedly parse an invalid expression. - struct RegexCacheEntry { - enum State { Uninitialized, Ready, PatternTooLarge, UnsafePattern, InvalidPattern }; - - State state; - std::regex expression; - - RegexCacheEntry() - : state(Uninitialized) {} - }; - - // Mutable limits and recursion state shared by one schema-validation run. - // activeRefs tracks (instance, schema) pairs rather than schema nodes alone: - // revisiting a schema at a different instance is legitimate, while the same - // pair indicates a cyclic $ref evaluation. - struct SchemaValidationCtx { - const pjson& rootSchema; - const pjson::SchemaOptions& options; - std::vector* publicErrors; - size_t depth; - size_t refResolutions; - size_t workUsed; - size_t errorsUsed; - size_t publicErrorStart; - bool aborted; - std::vector> activeRefs; - std::map regexCache; - - // Starts a validation run with no active recursion or resolved references. - SchemaValidationCtx(const pjson& aRootSchema, const pjson::SchemaOptions& aOptions, - std::vector* aPublicErrors) - : rootSchema(aRootSchema) - , options(aOptions) - , publicErrors(aPublicErrors) - , depth(0) - , refResolutions(0) - , workUsed(0) - , errorsUsed(0) - , publicErrorStart(aPublicErrors == nullptr ? 0 : aPublicErrors->size()) - , aborted(false) {} - }; - - struct SchemaBudgetExceeded {}; - - // Facade over a caller or speculative error vector that enforces one shared - // per-validation diagnostic budget without exposing a public container type. - struct SchemaErrorSink { - std::vector& values; - SchemaValidationCtx& ctx; - bool reported; - size_t discardedFailures; - - SchemaErrorSink(std::vector& aValues, SchemaValidationCtx& aCtx, - bool aReported = true) - : values(aValues) - , ctx(aCtx) - , reported(aReported) - , discardedFailures(0) {} - - size_t size() const { return reported ? values.size() : discardedFailures; } - - void push_back(const pjson::SchemaError& error) { - if (ctx.aborted) - return; - if (!reported) { - // Speculative anyOf/oneOf/not branches need only a pass/fail - // signal. Retaining every hidden diagnostic would let an - // attacker amplify a compact schema into large scratch vectors. - (void)error; - if (discardedFailures != std::numeric_limits::max()) - ++discardedFailures; - return; - } - const size_t limit = ctx.options.maxErrors == 0 ? size_t(100) : ctx.options.maxErrors; - if (ctx.errorsUsed >= limit) { - ctx.aborted = true; - if (ctx.publicErrors != nullptr && - ctx.publicErrors->size() - ctx.publicErrorStart < limit) { - try { - ctx.publicErrors->push_back(pjson::SchemaError( - error.path, "schema validation error budget exceeded")); - } catch (...) { - // The validation result remains a safe failure even if - // the best-effort terminal diagnostic cannot allocate. - ctx.publicErrors = nullptr; - } - } - throw SchemaBudgetExceeded(); - } - values.push_back(error); - ++ctx.errorsUsed; - } - }; - - static bool _isWhitespace(char c); - static void _appendUtf8(uint32_t aCodePoint, std::string& aOut); - static bool _hex4(const char* aSrc, size_t aStart, uint32_t& aOut); - static int _utf8Len(const char* src, size_t pos, size_t end); - static std::string _formatDouble(double aValue); - static bool _parseDouble(const std::string& aText, double& aValue); - - static bool _fail(ParseCtx& c, size_t aPos, const char* aMsg); - static pjson* _newNode(ParseCtx& c); // budget-checked allocation (nullptr on overflow) - static bool _peek(ParseCtx& c, char& aOut); - static bool _skipColon(ParseCtx& c); - static bool _parseValue(ParseCtx& c, pjson*& aOut); - static bool _parseString(ParseCtx& c, pjson*& aOut); - static bool _extractString(ParseCtx& c, std::string& aOut); - static bool _decodeStringBody(ParseCtx& c, std::string& aOut, bool bStopAtQuote); - static bool _parseKeyword(ParseCtx& c, pjson*& aOut); - static bool _parseNumber(ParseCtx& c, pjson*& aOut); - static bool _parseArray(ParseCtx& c, pjson*& aOut); - static bool _parseObject(ParseCtx& c, pjson*& aOut); - static pjson::unique_ptr _parseTop(const char* aSrc, size_t aSize, - const pjson::ParseOptions& aOpts, pjson::ParseError* aErr, - pjson::Allocator& aAlloc); - static pjson::unique_ptr _parseStream(std::istream& aIn, const pjson::ParseOptions& aOpts, - pjson::ParseError* aErr, pjson::Allocator& aAlloc); - template - static bool _writeEscapedTo(Sink& aOut, const std::string& aIn, bool bEscapeNonAscii); - template - static bool _openOrEmit(Sink& aOut, const pjson* aValue, size_t aDepth, - const pjson::SerializeOptions& aOpts, - std::vector& aFrames); - template - static bool _writeValueTo(Sink& aOut, const pjson& aValue, - const pjson::SerializeOptions& aOpts); - static void _appendValue(std::string& aOut, const pjson& aValue, - const pjson::SerializeOptions& aOpts); - static bool _writeValue(std::ostream& aOut, const pjson& aValue, - const pjson::SerializeOptions& aOpts); - static bool _parseSaxTop(const char* aSrc, size_t aSize, pjson::SaxHandler& aHandler, - const pjson::ParseOptions& aOpts, pjson::ParseError* aErr); - static bool _parseSaxStream(std::istream& aIn, pjson::SaxHandler& aHandler, - const pjson::ParseOptions& aOpts, pjson::ParseError* aErr); - - static std::string _pointerAppend(const std::string& aBase, const std::string& aToken); - static bool _validateCtx(const pjson& aNode, const pjson& aSchema, const std::string& aPath, - SchemaErrorSink& aErrors, SchemaValidationCtx& aCtx); - static bool _validate(const pjson& aNode, const pjson& aSchema, const std::string& aPath, - std::vector& aErrors, - const pjson::SchemaOptions& aOpts) noexcept; - static bool _typeMatches(const pjson& aNode, const std::string& aTypeName); - static std::string _typeName(const pjson& aNode); - static bool _isSafeRegex(const std::string& aPattern); +/*static*/ +const char* pjson::getVersion() { + return PJSON_VERSION; +} - // Internal typed/storage access keeps representation and permissive - // conversion helpers out of the public API. Callers first establish type. - static ArrayStorage& _array(pjson& aValue) { return *aValue._uValue._pValueArray; } - static const ArrayStorage& _array(const pjson& aValue) { return *aValue._uValue._pValueArray; } - static ObjectStorage& _object(pjson& aValue) { return *aValue._uValue._pValueMap; } - static const ObjectStorage& _object(const pjson& aValue) { return *aValue._uValue._pValueMap; } - static int64_t _integer(const pjson& aValue) { return aValue._uValue._valueInt; } - static double _floating(const pjson& aValue) { return aValue._uValue._valueDouble; } - static double _numberAsDouble(const pjson& aValue) { - return aValue._eType == pjson::jsonNumberInt ? static_cast(aValue._uValue._valueInt) - : aValue._uValue._valueDouble; +namespace { + uint64_t mixObjectHashSeed(uint64_t value) noexcept { + value ^= value >> 30U; + value *= UINT64_C(0xbf58476d1ce4e5b9); + value ^= value >> 27U; + value *= UINT64_C(0x94d049bb133111eb); + return value ^ (value >> 31U); } - static bool _boolean(const pjson& aValue) { return aValue._uValue._valueBool; } - static const std::string& _string(const pjson& aValue) { return *aValue._uValue._pValueString; } - // Returns -1, 0, or 1, and 2 when either floating operand is NaN. - static int _compareNumbers(const pjson& aLeft, const pjson& aRight); - static bool _equalWithBudget(const pjson& aLeft, const pjson& aRight, SchemaValidationCtx& aCtx, - SchemaErrorSink& aErrors, const std::string& aPath, bool& aEqual); - - // Iteratively frees every descendant pjson of node's array/map, leaving the - // node's own top-level container allocated but empty (a no-op for scalars). - // Using an explicit work-list instead of the recursive destructor keeps - // teardown safe on arbitrarily deep documents. Marked noexcept: it is - // reached from ~pjson, so an allocation failure here terminates rather than - // escaping a destructor. - static void _disposeChildren(pjson& node) noexcept; - static pjson::Allocator& _defaultAllocator() noexcept; - static pjson* _allocateNode(pjson::Allocator& aAlloc); - static void _destroyNode(pjson* aValue) noexcept; - static pjson::unique_ptr _makeNode(pjson::Allocator& aAlloc); - static pjson::unique_ptr _cloneNode(const pjson& aValue, pjson::Allocator& aAlloc); -}; - -// File-scope aliases keep internal type names concise without exposing the -// owning containers in the public header. -typedef pjson::jsonType jsonType; -typedef pjsonImpl::ArrayStorage PJSONARRAY; -typedef pjsonImpl::ObjectStorage PJSONMAP; -typedef pjson::SchemaError SchemaError; -typedef pjson::SchemaOptions SchemaOptions; -typedef pjson::ParseOptions ParseOptions; -typedef pjson::ParseError ParseError; -typedef pjson::SaxHandler SaxHandler; -typedef pjsonImpl::ParseCtx ParseCtx; -// Schema validation still uses native recursion for applicator keywords. Keep -// its logical depth below a conservative stack-safe ceiling even when callers -// request a larger value. Consecutive local references are resolved iteratively -// but continue to consume this same logical-depth budget. -static const size_t kSchemaValidationDepthHardLimit = 64; - -namespace { - //===------------------------------------------------------------------===// - // Parse diagnostics and SAX cursor adapters - //===------------------------------------------------------------------===// + struct ObjectHashKey { + uint64_t first; + uint64_t second; + }; - // Converts a zero-based byte offset into one-based source coordinates. CRLF - // counts as one line ending; a lone CR or LF also starts a new line. - void lineAndColumn(const char* src, size_t size, size_t offset, size_t& line, size_t& column) { - line = 1; - column = 1; - const size_t end = offset < size ? offset : size; - for (size_t i = 0; i < end; ++i) { - if (src[i] == '\r') { - if (i + 1 < end && src[i + 1] == '\n') - ++i; - ++line; - column = 1; - } else if (src[i] == '\n') { - ++line; - column = 1; - } else { - ++column; + const ObjectHashKey& objectHashKey() noexcept { + static const ObjectHashKey key = []() { + const uint64_t clock = static_cast( + std::chrono::high_resolution_clock::now().time_since_epoch().count()); + const uintptr_t address = reinterpret_cast(&objectHashKey); + ObjectHashKey result = { + mixObjectHashSeed(clock ^ static_cast(address)), + mixObjectHashSeed(~clock ^ (static_cast(address) << 1U))}; + try { + std::random_device random; + const uint64_t first = (static_cast(random()) << 32U) ^ random(); + const uint64_t second = (static_cast(random()) << 32U) ^ random(); + result.first = mixObjectHashSeed(result.first ^ first); + result.second = mixObjectHashSeed(result.second ^ second); + } catch (...) { + // Clock and ASLR-derived state remain a non-throwing fallback. + (void)0; } - } + return result; + }(); + return key; } - // Publishes a buffer-parser failure, deriving source coordinates from the - // authoritative byte offset. A null destination intentionally discards it. - void setParseError(ParseError* err, const char* src, size_t size, size_t offset, - const std::string& message) { - if (!err) - return; - err->ok = false; - err->offset = offset; - lineAndColumn(src, size, offset, err->line, err->column); - err->message = message; + uint64_t rotateLeft(uint64_t value, unsigned int shift) noexcept { + return (value << shift) | (value >> (64U - shift)); } - // Restores the public error object to its successful, start-of-input state. - void resetParseError(ParseError* err) { - if (!err) - return; - err->ok = true; - err->offset = 0; - err->line = 1; - err->column = 1; - err->message.clear(); + void sipRound(uint64_t& v0, uint64_t& v1, uint64_t& v2, uint64_t& v3) noexcept { + v0 += v1; + v1 = rotateLeft(v1, 13U); + v1 ^= v0; + v0 = rotateLeft(v0, 32U); + v2 += v3; + v3 = rotateLeft(v3, 16U); + v3 ^= v2; + v0 += v3; + v3 = rotateLeft(v3, 21U); + v3 ^= v0; + v2 += v1; + v1 = rotateLeft(v1, 17U); + v1 ^= v2; + v2 = rotateLeft(v2, 32U); } - // Internal control-flow exception used to unwind immediately when a SAX - // callback returns false; parseDocument converts it back into ParseError. - class SaxParseCancelled : public std::exception { - public: - // Supplies a stable diagnostic if cancellation escapes an internal frame. - const char* what() const noexcept override { return "SAX parse aborted"; } - }; - - // Non-owning cursor over a contiguous input buffer. Positions are byte - // offsets, while line/column values are maintained incrementally. - class BufferSaxCursor { + // Adapts the process-wide operator new/delete pair to the allocator API. + class DefaultPjsonAllocator : public pjson::Allocator { public: - // Binds the cursor to caller-owned bytes, which must outlive parsing. - BufferSaxCursor(const char* src, size_t size) - : _src(src) - , _size(size) - , _pos(0) - , _line(1) - , _column(1) - , _prevWasCR(false) {} - - // Observes the next byte without advancing source coordinates. - bool peek(char& ch) { - if (_pos >= _size) - return false; - ch = _src[_pos]; - return true; - } - - // Consumes one byte and advances CR/LF-aware source coordinates. - bool get(char& ch) { - if (!peek(ch)) - return false; - advance(ch); - ++_pos; - return true; + // Allocates raw storage; size is the only parameter needed by operator new. + void* allocate(size_t aSize, size_t, AllocationKind) override { + return ::operator new(aSize); } - // Reports whether every byte in the fixed buffer has been consumed. - bool eof() const { return _pos >= _size; } - // A memory cursor cannot suffer an I/O failure. - bool failed() const { return false; } - // Returns the zero-based byte offset of the next input byte. - size_t position() const { return _pos; } - // Returns the one-based line containing the next input byte. - size_t line() const { return _line; } - // Returns the one-based column containing the next input byte. - size_t column() const { return _column; } - - private: - // Counts CRLF as one newline even though its bytes arrive separately. - void advance(char ch) { - if (ch == '\r') { - ++_line; - _column = 1; - _prevWasCR = true; - } else if (ch == '\n') { - if (_prevWasCR) { - _prevWasCR = false; - } else { - ++_line; - _column = 1; - } - } else { - ++_column; - _prevWasCR = false; - } + // Releases storage previously obtained from allocate. + void deallocate(void* aPtr, size_t, size_t, AllocationKind) noexcept override { + ::operator delete(aPtr); } - - const char* _src; - size_t _size; - size_t _pos; - size_t _line; - size_t _column; - bool _prevWasCR; }; - // Buffered cursor that gives the SAX parser the same interface for streams - // without first materializing the complete input. - class StreamSaxCursor { - public: - // Binds to a caller-owned stream and delays reads until bytes are needed. - explicit StreamSaxCursor(std::istream& in) - : _in(in) - , _used(0) - , _posInBuf(0) - , _pos(0) - , _line(1) - , _column(1) - , _prevWasCR(false) - , _failed(false) - , _eof(false) {} - - // Observes the next buffered byte, refilling on demand. - bool peek(char& ch) { - if (!ensure()) - return false; - ch = _buffer[_posInBuf]; - return true; + template + // Constructs an internal DOM object and returns raw storage on constructor failure. + T* allocateDomObject(pjson::Allocator& aAlloc, pjson::Allocator::AllocationKind aKind) { + void* storage = aAlloc.allocate(sizeof(T), alignof(T), aKind); + try { + return new (storage) T(); + } catch (...) { + aAlloc.deallocate(storage, sizeof(T), alignof(T), aKind); + throw; } + } - // Consumes one byte while maintaining absolute and source positions. - bool get(char& ch) { - if (!ensure()) - return false; - ch = _buffer[_posInBuf++]; - if (ch == '\r') { - ++_line; - _column = 1; - _prevWasCR = true; - } else if (ch == '\n') { - if (_prevWasCR) { - _prevWasCR = false; - } else { - ++_line; - _column = 1; - } - } else { - ++_column; - _prevWasCR = false; - } - ++_pos; - return true; - } + template + // Runs an internal object's destructor before returning its exact allocation. + void destroyDomObject(pjson::Allocator& aAlloc, T* aObject, + pjson::Allocator::AllocationKind aKind) noexcept { + if (aObject == nullptr) + return; + aObject->~T(); + aAlloc.deallocate(aObject, sizeof(T), alignof(T), aKind); + } +} // namespace - // Reports EOF only after both the stream and the refill buffer are empty. - bool eof() const { return _eof && _posInBuf >= _used; } - // Distinguishes an I/O failure from an ordinary end of stream. - bool failed() const { return _failed; } - // Returns the number of bytes consumed across all refills. - size_t position() const { return _pos; } - // Returns the one-based line containing the next input byte. - size_t line() const { return _line; } - // Returns the one-based column containing the next input byte. - size_t column() const { return _column; } +// SipHash-2-4 hashes untrusted object names with a process-specific key so +// reusable collision sets cannot target the implementation's default hash. +size_t pjsonImpl::ObjectHash::operator()(const std::string& aKey) const noexcept { + const ObjectHashKey& key = objectHashKey(); + uint64_t v0 = UINT64_C(0x736f6d6570736575) ^ key.first; + uint64_t v1 = UINT64_C(0x646f72616e646f6d) ^ key.second; + uint64_t v2 = UINT64_C(0x6c7967656e657261) ^ key.first; + uint64_t v3 = UINT64_C(0x7465646279746573) ^ key.second; + size_t offset = 0; + while (aKey.size() - offset >= size_t(8)) { + uint64_t word = 0; + for (unsigned int byte = 0; byte < 8U; ++byte) + word |= static_cast(static_cast(aKey[offset + byte])) + << (byte * 8U); + v3 ^= word; + sipRound(v0, v1, v2, v3); + sipRound(v0, v1, v2, v3); + v0 ^= word; + offset += 8; + } + uint64_t tail = static_cast(aKey.size()) << 56U; + for (size_t byte = 0; offset + byte < aKey.size(); ++byte) + tail |= static_cast(static_cast(aKey[offset + byte])) + << (byte * 8U); + v3 ^= tail; + sipRound(v0, v1, v2, v3); + sipRound(v0, v1, v2, v3); + v0 ^= tail; + v2 ^= UINT64_C(0xff); + for (unsigned int round = 0; round < 4U; ++round) + sipRound(v0, v1, v2, v3); + return static_cast(v0 ^ v1 ^ v2 ^ v3); +} - private: - // Makes one byte available unless EOF or an unrecoverable read failure - // has already been observed. Short reads with data are still usable. - bool ensure() { - if (_posInBuf < _used) - return true; - if (_eof || _failed) - return false; - // Pull directly from streambuf so a source that intentionally - // exposes one short chunk at a time is not mistaken for EOF by - // istream::read's exact-count semantics. One byte is sufficient for - // the parser; the streambuf retains any remaining get-area bytes. - std::streambuf* buffer = _in.rdbuf(); - if (buffer == nullptr || _in.bad()) { - _failed = true; - return false; - } - const std::streambuf::int_type next = buffer->sbumpc(); - if (!std::streambuf::traits_type::eq_int_type(next, - std::streambuf::traits_type::eof())) { - _buffer[0] = std::streambuf::traits_type::to_char_type(next); - _used = 1; - _posInBuf = 0; - return true; - } - if (_in.bad()) { - _failed = true; - return false; - } - _eof = true; - return false; - } +// Gives allocator implementations a safe virtual destruction point. +pjson::Allocator::~Allocator() {} +/*static*/ +// Returns the stateless process-lifetime allocator used by ordinary values. +pjson::Allocator& pjsonImpl::_defaultAllocator() noexcept { + static DefaultPjsonAllocator allocator; + return allocator; +} +/*static*/ +// Returns the immutable-by-convention process-lifetime representation shared by +// every null value. Mutating paths materialize a private implementation first. +pjsonImpl& pjsonImpl::_nullImpl() noexcept { + static pjsonImpl nullValue; + return nullValue; +} +/*static*/ +bool pjsonImpl::_isNullImpl(const pjsonImpl* aImpl) noexcept { + return aImpl == &_nullImpl(); +} +/*static*/ +pjsonImpl* pjsonImpl::_allocateImpl(pjson::Allocator& aAlloc) { + return allocateDomObject(aAlloc, pjson::Allocator::ImplementationAllocation); +} +/*static*/ +void pjsonImpl::_destroyImpl(pjson::Allocator& aAlloc, pjsonImpl* aImpl) noexcept { + if (!_isNullImpl(aImpl)) + destroyDomObject(aAlloc, aImpl, pjson::Allocator::ImplementationAllocation); +} +/*static*/ +// Constructs a child node in allocator storage. _destroyNode is used only for +// nodes created here; caller-created roots are destroyed with normal delete. +pjson* pjsonImpl::_allocateNode(pjson::Allocator& aAlloc) { + void* storage = + aAlloc.allocate(sizeof(pjson), alignof(pjson), pjson::Allocator::NodeAllocation); + try { + return new (storage) pjson(aAlloc); + } catch (...) { + aAlloc.deallocate(storage, sizeof(pjson), alignof(pjson), pjson::Allocator::NodeAllocation); + throw; + } +} +/*static*/ +// Destroys a library-created child node through its retained allocator. +void pjsonImpl::_destroyNode(pjson* aValue) noexcept { + if (aValue == nullptr) + return; + pjson::Allocator& allocator = *aValue->_allocator; + aValue->~pjson(); + allocator.deallocate(aValue, sizeof(pjson), alignof(pjson), pjson::Allocator::NodeAllocation); +} - std::istream& _in; - char _buffer[8192]; - size_t _used; - size_t _posInBuf; - size_t _pos; - size_t _line; - size_t _column; - bool _prevWasCR; - bool _failed; - bool _eof; - }; +//===----------------------------------------------------------------------===// +// DOM value lifetime, storage transfer, and type access +// +// A pjson's allocator identity is immutable. Contents may be transferred in +// constant time only between values using the same allocator; cross-allocator +// moves become deep copies so every descendant remains owned consistently. +//===----------------------------------------------------------------------===// - // Recursive-descent event parser shared by buffer and stream cursors. It - // applies the same grammar, resource budgets, and duplicate-key policy as - // DOM parsing, but can suppress callbacks for KeepFirstDuplicate values. - template struct SaxParser { - Cursor& cur; - SaxHandler& handler; - const ParseOptions& opts; - ParseError* err; - size_t nodeCount; +// Constructs an allocation-free null root using the default allocator. +pjson::pjson() noexcept + : _allocator(&pjsonImpl::_defaultAllocator()) + , _pImpl(&pjsonImpl::_nullImpl()) {} +// Constructs an allocation-free null root backed by a caller allocator. +pjson::pjson(Allocator& aAlloc) noexcept + : _allocator(&aAlloc) + , _pImpl(&pjsonImpl::_nullImpl()) {} +// Releases the active value and all descendants through their retained allocator. +pjson::~pjson() { + reset(); +} +// Deep-copies a value while preserving its allocator identity. +pjson::pjson(const pjson& aFrom) + : _allocator(aFrom._allocator) + , _pImpl(&pjsonImpl::_nullImpl()) { + pjsonImpl::_copyContentsInto(*this, aFrom); +} +// Deep-copies a value into a specifically selected allocator domain. +pjson::pjson(const pjson& aFrom, Allocator& aAlloc) + : _allocator(&aAlloc) + , _pImpl(&pjsonImpl::_nullImpl()) { + pjsonImpl::_copyContentsInto(*this, aFrom); +} +// Steals storage from a same-allocator source and leaves it as null. +pjson::pjson(pjson&& aFrom) noexcept + : _allocator(aFrom._allocator) + , _pImpl(aFrom._pImpl) { + aFrom._pImpl = &pjsonImpl::_nullImpl(); +} +// Steals when allocator domains match; otherwise deep-copies into aAlloc and +// resets the source only after the copy succeeds. +pjson::pjson(pjson&& aFrom, Allocator& aAlloc) + : _allocator(&aAlloc) + , _pImpl(&pjsonImpl::_nullImpl()) { + if (_allocator == aFrom._allocator) { + _pImpl = aFrom._pImpl; + aFrom._pImpl = &pjsonImpl::_nullImpl(); + } else { + pjsonImpl::_copyContentsInto(*this, aFrom); + aFrom.reset(); + } +} +// Replaces this value from an rvalue, transferring storage when allocator +// domains match after an ancestry-safety check. Self-move is a no-op. +// +// Aliasing safety (PJSON-COR-002): aFrom may be an ancestor or descendant of +// *this. A source descendant can be detached safely before the old destination +// tree is destroyed. When the destination is a descendant of the source, copy +// first because consuming the ancestor would invalidate the destination and can +// create an ownership cycle. +pjson& pjson::operator=(pjson&& aFrom) { + if (&aFrom == this) + return *this; + // A destination that lives inside aFrom cannot outlive a destructive move + // from that ancestor. Preserve value semantics by snapshotting the source + // before replacing the descendant; a moved-from object is permitted to + // retain its value. This also avoids creating an unreachable ownership + // cycle such as root["child"] = std::move(root). + if (pjsonImpl::_containsNode(aFrom, this)) { + copyFrom(aFrom); + return *this; + } - // Couples a cursor and event sink for one parse, with fresh node accounting. - SaxParser(Cursor& aCur, SaxHandler& aHandler, const ParseOptions& aOpts, ParseError* aErr) - : cur(aCur) - , handler(aHandler) - , opts(aOpts) - , err(aErr) - , nodeCount(0) {} + if (_allocator == aFrom._allocator) { + pjson snapshot(*_allocator); // null placeholder in the same allocator domain + pjsonImpl::_swapStorage(snapshot, aFrom); // snapshot adopts aFrom's storage; aFrom -> null + pjsonImpl::_swapStorage(*this, snapshot); // *this adopts that storage + // snapshot's destructor frees our previous contents, which may include the + // now-null aFrom node when aFrom was one of our descendants. + } else { + pjson tmp(std::move(aFrom), *_allocator); + pjsonImpl::_swapStorage(*this, tmp); + } - // Parses exactly one complete document, translating parser, handler, - // allocation, and stream failures into a stable non-throwing result. - bool parseDocument() noexcept { - try { - resetParseError(err); - if (!parseValue(0, true)) - return false; - if (!skipWhitespace()) - return false; - char ch = 0; - if (opts.maxInputBytes != 0 && cur.position() >= opts.maxInputBytes) { - if (cur.peek(ch)) - return failAt(opts.maxInputBytes, cur.line(), cur.column(), - "input exceeds maxInputBytes"); - } else if (cur.peek(ch)) { - return fail("trailing characters after JSON value"); - } - if (cur.failed()) - return fail("stream read failed"); + return *this; +} +// O(1) exchange of two nodes' private representations. noexcept, +// which is what lets the move operations and copy-and-swap assignment below +// offer their exception guarantees. +// +// Aliasing safety (PJSON-COR-002): swapping a node with one of its own +// ancestors or descendants would splice a container into its own child slot and +// create an ownership cycle. Such an overlapping swap is rejected as a safe +// no-op; callers that need it should copy instead. canSwap() already rejects +// cross-allocator pairs. +void pjson::swap(pjson& aOther) noexcept { + if (this == &aOther || !canSwap(aOther)) + return; + pjsonImpl::_swapStorage(*this, aOther); +} +// Performs the raw storage exchange with no aliasing or allocator checks. Used +// internally where the caller has already established that the two nodes are +// distinct, non-overlapping, and share an allocator domain. +/*static*/ +void pjsonImpl::_swapStorage(pjson& aLeft, pjson& aRight) noexcept { + std::swap(aLeft._pImpl, aRight._pImpl); +} +// Reports whether aNode is aRoot or a descendant of it. The walk is iterative so +// it stays stack-safe on deep documents. Allocation failure is treated +// conservatively as overlap, preserving the noexcept callers' safety contract. +/*static*/ +bool pjsonImpl::_containsNode(const pjson& aRoot, const pjson* aNode) noexcept { + if (aNode == nullptr) + return false; + try { + std::vector work; + work.push_back(&aRoot); + while (!work.empty()) { + const pjson* cur = work.back(); + work.pop_back(); + if (cur == aNode) return true; - } catch (const SaxParseCancelled&) { - return failNoThrow("SAX parse aborted"); - } catch (const std::bad_alloc&) { - return failNoThrow("SAX parse ran out of memory"); - } catch (const std::exception&) { - return failNoThrow("SAX parse or handler exception"); - } catch (...) { - return failNoThrow("SAX parse or handler exception"); - } - } - - // Dispatches one value at the current nesting depth. emit=false still - // validates and counts the subtree but deliberately skips callbacks. - bool parseValue(size_t depth, bool emit) { - if (!skipWhitespace()) - return false; - - char ch = 0; - if (!cur.peek(ch)) { - if (cur.failed()) - return fail("stream read failed"); - return fail("unexpected end of input; expected a value"); - } - - if (ch == '"') - return parseStringValue(emit); - if (ch == '{') - return parseObject(depth + 1, emit); - if (ch == '[') - return parseArray(depth + 1, emit); - if (ch == '-' || (ch >= '0' && ch <= '9')) - return parseNumberValue(emit); - return parseKeywordValue(emit); - } - - // Consumes only the four whitespace bytes admitted by JSON. - bool skipWhitespace() { - char ch = 0; - while (cur.peek(ch) && pjsonImpl::_isWhitespace(ch)) { - if (!getChar(ch)) - return false; + if (cur->_pImpl->_eType == pjson::jsonType::jsonArray) { + const pjsonImpl::ArrayStorage& arr = *cur->_pImpl->_pValueArray; + for (size_t i = 0; i < arr.size(); ++i) + work.push_back(arr[i]); + } else if (cur->_pImpl->_eType == pjson::jsonType::jsonObject) { + const pjsonImpl::ObjectStorage& obj = *cur->_pImpl->_pValueMap; + for (pjsonImpl::ObjectStorage::const_iterator it = obj.begin(); it != obj.end(); + ++it) + work.push_back(it->second); } - if (cur.failed()) - return fail("stream read failed"); - return true; - } - - // Parses a string value and emits it after it has consumed one node from - // the configured budget. Object keys are handled separately. - bool parseStringValue(bool emit) { - if (!reserveNode()) - return false; - std::string value; - if (!parseStringRaw(value)) - return false; - if (!emit) - return true; - return dispatch(handler.onString(value)); } + return false; + } catch (...) { + return true; + } +} +// Returns the allocator permanently associated with this value and its descendants. +pjson::Allocator& pjson::getAllocator() const noexcept { + return *_allocator; +} +// Reports whether contents can be exchanged without crossing allocator domains. +bool pjson::canSwap(const pjson& aOther) const noexcept { + return _allocator == aOther._allocator && + (this == &aOther || + (!pjsonImpl::_containsNode(*this, &aOther) && !pjsonImpl::_containsNode(aOther, this))); +} +// Copy assignment (copy-and-swap: safe even when aFrom aliases a child of +// this, because the deep copy completes before any of our storage is freed). +pjson& pjson::operator=(const pjson& aFrom) { + if (&aFrom == this) + return *this; - // Recognizes the lowercase null/boolean literals required by RFC 8259. - bool parseKeywordValue(bool emit) { - char ch = 0; - if (!cur.peek(ch)) - return fail("unexpected end of input; expected a value"); - - if (ch == 'n') { - if (!matchLiteral("null")) - return false; - if (!reserveNode()) - return false; - return !emit || dispatch(handler.onNull()); - } - if (ch == 't') { - if (!matchLiteral("true")) - return false; - if (!reserveNode()) - return false; - return !emit || dispatch(handler.onBool(true)); - } - if (ch == 'f') { - if (!matchLiteral("false")) - return false; - if (!reserveNode()) - return false; - return !emit || dispatch(handler.onBool(false)); - } - return fail("invalid JSON value"); - } - - // Scans the JSON number grammar before conversion. Integral tokens that - // overflow int64 are preserved as finite doubles rather than truncated. - bool parseNumberValue(bool emit) { - std::string text; - char ch = 0; - if (!cur.peek(ch)) - return fail("unexpected end of input; expected a value"); - - if (ch == '-') { - if (!getChar(ch)) - return false; - text.push_back(ch); - if (!cur.peek(ch)) - return fail("invalid number: expected digit"); - } - - if (ch == '0') { - if (!getChar(ch)) - return false; - text.push_back(ch); - } else if (ch >= '1' && ch <= '9') { - do { - if (!getChar(ch)) - return false; - text.push_back(ch); - } while (cur.peek(ch) && ch >= '0' && ch <= '9'); - } else { - return fail("invalid number: expected digit"); - } - - bool isFloat = false; - if (cur.peek(ch) && ch == '.') { - isFloat = true; - if (!getChar(ch)) - return false; - text.push_back(ch); - if (!cur.peek(ch) || ch < '0' || ch > '9') - return fail("invalid number: '.' must be followed by a digit"); - do { - if (!getChar(ch)) - return false; - text.push_back(ch); - } while (cur.peek(ch) && ch >= '0' && ch <= '9'); - } - - if (cur.peek(ch) && (ch == 'e' || ch == 'E')) { - isFloat = true; - if (!getChar(ch)) - return false; - text.push_back(ch); - if (cur.peek(ch) && (ch == '+' || ch == '-')) { - if (!getChar(ch)) - return false; - text.push_back(ch); - } - if (!cur.peek(ch) || ch < '0' || ch > '9') - return fail("invalid number: exponent must have a digit"); - do { - if (!getChar(ch)) - return false; - text.push_back(ch); - } while (cur.peek(ch) && ch >= '0' && ch <= '9'); - } - - if (!reserveNode()) - return false; - - if (isFloat) { - double d = 0.0; - if (!pjsonImpl::_parseDouble(text, d) || !std::isfinite(d)) - return fail("number out of range"); - return !emit || dispatch(handler.onDouble(d)); - } - - errno = 0; - const long long llVal = strtoll(text.c_str(), nullptr, 10); - if (errno == ERANGE) { - double d = 0.0; - if (!pjsonImpl::_parseDouble(text, d) || !std::isfinite(d)) - return fail("number out of range"); - return !emit || dispatch(handler.onDouble(d)); - } - return !emit || dispatch(handler.onInt(static_cast(llVal))); - } - - // Parses an array while explicitly tracking comma state so leading, - // repeated, missing, and trailing commas receive deterministic errors. - bool parseArray(size_t depth, bool emit) { - const size_t maxDepth = opts.maxDepth > 0 ? static_cast(opts.maxDepth) : 1U; - if (depth > maxDepth) - return fail("maximum nesting depth exceeded"); - if (!reserveNode()) - return false; - - char ch = 0; - if (!getChar(ch) || ch != '[') - return fail("unexpected end of input; expected a value"); - if (emit && !dispatch(handler.onStartArray())) - return false; - - bool expectValue = false; - bool any = false; - while (true) { - if (!skipWhitespace()) - return false; - if (!cur.peek(ch)) { - if (cur.failed()) - return fail("stream read failed"); - return fail("unterminated array"); - } - if (ch == ']') { - if (expectValue) - return fail("trailing comma in array"); - if (!getChar(ch)) - return false; - return !emit || dispatch(handler.onEndArray()); - } - if (ch == ',') { - if (!any || expectValue) - return fail("unexpected ',' in array"); - if (!getChar(ch)) - return false; - expectValue = true; - continue; - } - if (any && !expectValue) - return fail("missing ',' between array elements"); - if (!parseValue(depth, emit)) - return false; - any = true; - expectValue = false; - } - } - - // Parses an object and implements duplicate-key policy at event time. - // KeepFirst parses duplicate values with emit=false so malformed input - // and resource-limit violations cannot hide inside discarded members. - bool parseObject(size_t depth, bool emit) { - const size_t maxDepth = opts.maxDepth > 0 ? static_cast(opts.maxDepth) : 1U; - if (depth > maxDepth) - return fail("maximum nesting depth exceeded"); - if (!reserveNode()) - return false; - - char ch = 0; - if (!getChar(ch) || ch != '{') - return fail("unexpected end of input; expected a value"); - if (emit && !dispatch(handler.onStartObject())) - return false; - - bool expectMember = false; - bool any = false; - std::map seenKeys; - while (true) { - if (!skipWhitespace()) - return false; - if (!cur.peek(ch)) { - if (cur.failed()) - return fail("stream read failed"); - return fail("unterminated object"); - } - if (ch == '}') { - if (expectMember) - return fail("trailing comma in object"); - if (!getChar(ch)) - return false; - return !emit || dispatch(handler.onEndObject()); - } - if (ch == ',') { - if (!any || expectMember) - return fail("unexpected ',' in object"); - if (!getChar(ch)) - return false; - expectMember = true; - continue; - } - if (ch != '"') - return fail("expected '\"' to start an object key"); - if (any && !expectMember) - return fail("missing ',' between object members"); - - const size_t keyOffset = cur.position(); - const size_t keyLine = cur.line(); - const size_t keyColumn = cur.column(); - std::string key; - if (!parseStringRaw(key)) - return false; - if (!skipWhitespace()) - return false; - if (!getChar(ch) || ch != ':') - return fail("expected ':' after object key"); - - bool duplicate = false; - if (opts.duplicateKeys != ParseOptions::KeepLastDuplicate) { - duplicate = seenKeys.find(key) != seenKeys.end(); - } - if (duplicate && opts.duplicateKeys == ParseOptions::RejectDuplicateKeys) { - return failAt(keyOffset, keyLine, keyColumn, "duplicate object key"); - } - if (!duplicate && opts.duplicateKeys != ParseOptions::KeepLastDuplicate) - seenKeys[key] = true; - - const bool emitValue = - emit && !(duplicate && opts.duplicateKeys == ParseOptions::KeepFirstDuplicate); - if (emitValue && !dispatch(handler.onKey(key))) - return false; - if (!parseValue(depth, emitValue)) - return false; - any = true; - expectMember = false; - } - } - - // Decodes a quoted JSON string and rejects invalid Unicode/control bytes. - bool parseStringRaw(std::string& out) { - char ch = 0; - if (!getChar(ch) || ch != '"') - return fail("expected '\"' to start a string"); - - out.clear(); - while (true) { - if (!getChar(ch)) { - if (cur.failed()) - return fail("stream read failed"); - return fail("unterminated string"); - } - const unsigned char uch = static_cast(ch); - if (ch == '"') - return true; - if (ch == '\\') { - if (!getChar(ch)) - return fail("dangling escape at end of input"); - switch (ch) { - case '"': - out += '"'; - break; - case '\\': - out += '\\'; - break; - case '/': - out += '/'; - break; - case 'b': - out += '\b'; - break; - case 'f': - out += '\f'; - break; - case 'n': - out += '\n'; - break; - case 'r': - out += '\r'; - break; - case 't': - out += '\t'; - break; - case 'u': { - uint32_t cp = 0; - if (!readHex4(cp)) - return false; - if (cp >= 0xD800 && cp <= 0xDBFF) { - char slash = 0; - if (cur.peek(slash) && slash == '\\') { - if (!getChar(slash)) - return fail("invalid \\u escape"); - char u = 0; - if (!getChar(u)) - return fail("invalid \\u escape"); - if (u == 'u') { - std::string hex; - hex.reserve(4); - bool complete = true; - for (int i = 0; i < 4; ++i) { - char hx = 0; - if (!getChar(hx)) { - complete = false; - break; - } - hex.push_back(hx); - } - uint32_t low = 0; - const bool validLow = - complete && hex.size() == 4 && - pjsonImpl::_hex4(hex.c_str(), 0, low) && - low >= 0xDC00 && low <= 0xDFFF; - if (validLow) { - cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); - } else { - return fail("unpaired high surrogate"); - } - } else { - return fail("unpaired high surrogate"); - } - } else { - return fail("unpaired high surrogate"); - } - } else if (cp >= 0xDC00 && cp <= 0xDFFF) { - return fail("unpaired low surrogate"); - } - pjsonImpl::_appendUtf8(cp, out); - break; - } - default: - return fail("invalid escape sequence"); - } - continue; - } - if (uch < 0x20) { - return fail("unescaped control character in string"); - } - if (uch >= 0x80) { - out += static_cast(uch); - if (!consumeUtf8Tail(uch, out)) - return false; - continue; - } - out += static_cast(uch); - } - } - - // Consumes and validates the continuation bytes for an already-stored - // UTF-8 lead byte, including overlong, surrogate, and range checks. - bool consumeUtf8Tail(unsigned char lead, std::string& out) { - int need = 0; - uint32_t code = 0; - if ((lead & 0xE0U) == 0xC0U) { - need = 1; - code = lead & 0x1FU; - } else if ((lead & 0xF0U) == 0xE0U) { - need = 2; - code = lead & 0x0FU; - } else if ((lead & 0xF8U) == 0xF0U) { - need = 3; - code = lead & 0x07U; - } else { - return fail("invalid UTF-8 sequence"); - } - for (int i = 0; i < need; ++i) { - char ch = 0; - if (!getChar(ch)) - return fail("invalid UTF-8 sequence"); - const unsigned char byte = static_cast(ch); - if ((byte & 0xC0U) != 0x80U) - return fail("invalid UTF-8 sequence"); - code = (code << 6) | (byte & 0x3FU); - out += ch; - } - if ((need == 1 && code < 0x80U) || (need == 2 && code < 0x800U) || - (need == 3 && code < 0x10000U) || code > 0x10FFFFU || - (code >= 0xD800U && code <= 0xDFFFU)) { - return fail("invalid UTF-8 sequence"); - } - return true; - } - - // Reads exactly four hexadecimal digits following a \u escape. - bool readHex4(uint32_t& out) { - out = 0; - for (int i = 0; i < 4; ++i) { - char ch = 0; - if (!getChar(ch)) - return fail("invalid \\u escape"); - out <<= 4; - if (ch >= '0' && ch <= '9') - out |= static_cast(ch - '0'); - else if (ch >= 'a' && ch <= 'f') - out |= static_cast(10 + ch - 'a'); - else if (ch >= 'A' && ch <= 'F') - out |= static_cast(10 + ch - 'A'); - else - return fail("invalid \\u escape"); - } - return true; - } - - // Consumes one known lowercase JSON literal. - bool matchLiteral(const char* lit) { - for (size_t i = 0; lit[i] != '\0'; ++i) { - char ch = 0; - if (!getChar(ch)) - return fail("invalid JSON value"); - const char want = lit[i]; - if (ch != want) { - return fail("invalid JSON value"); - } - } - return true; - } - - // Centralizes byte-budget enforcement so no consuming parser path can - // advance beyond maxInputBytes. - bool getChar(char& ch) { - if (opts.maxInputBytes != 0 && cur.position() >= opts.maxInputBytes) - return failAt(opts.maxInputBytes, cur.line(), cur.column(), - "input exceeds maxInputBytes"); - return cur.get(ch); - } - - // Accounts for one JSON value even when its callbacks are suppressed. - bool reserveNode() { - if (opts.maxNodes != 0 && nodeCount >= opts.maxNodes) - return fail("document too large (node budget exceeded)"); - ++nodeCount; - return true; - } - - // Converts a handler's false return into an exception solely to unwind - // nested parse calls; the public SAX API never exposes the exception. - bool dispatch(bool ok) { - if (!ok) - throw SaxParseCancelled(); - return true; - } - - // Records a failure at the cursor's current source location. - bool fail(const std::string& message) { - if (err) { - err->ok = false; - err->offset = cur.position(); - err->line = cur.line(); - err->column = cur.column(); - err->message = message; - } - return false; - } - - // Records a failure at a saved location, such as a duplicate key's start. - bool failAt(size_t offset, size_t line, size_t column, const std::string& message) { - if (err) { - err->ok = false; - err->offset = offset; - err->line = line; - err->column = column; - err->message = message; - } - return false; - } - - // Catch-path diagnostics must not replace the original handler/parser - // failure with an allocation exception while assigning the message. - bool failNoThrow(const char* message) noexcept { - if (err) { - err->ok = false; - err->offset = cur.position(); - err->line = cur.line(); - err->column = cur.column(); - try { - err->message = message; - } catch (...) { - // basic_string::clear is non-allocating; retain the - // structured coordinates even when message assignment fails. - err->message.clear(); - } - } - return false; - } - }; -} // namespace - -//===----------------------------------------------------------------------===// -// Public configuration, diagnostics, and SAX defaults -// -// Constructors establish success-state diagnostics and conservative resource -// limits. The SAX base class accepts every event so clients can override only -// the callbacks they need; returning false from any override cancels parsing. -//===----------------------------------------------------------------------===// -/*static*/ -// Returns the compile-time library version string without transferring ownership. -const char* pjson::getVersion() { - return PJSON_VERSION; + pjson tmp(aFrom, *_allocator); + pjsonImpl::_swapStorage(*this, tmp); + return *this; } -// Establishes RFC 8259 parsing with bounded depth, node count, and input size. -pjson::ParseOptions::ParseOptions() - : maxDepth(512) - , maxNodes(1000000) - , maxInputBytes(size_t(64) * 1024U * 1024U) - , duplicateKeys(RejectDuplicateKeys) {} -// Establishes compact, UTF-8-preserving, ascending-key serialization. -pjson::SerializeOptions::SerializeOptions() - : pretty(false) - , indentWidth(2) - , indentCharacter(' ') - , escapeNonAscii(false) - , keyOrder(AscendingKeys) - , maxOutputBytes(size_t(64) * 1024U * 1024U) {} -/*static*/ -// Produces the default two-space pretty-printing preset. -pjson::SerializeOptions pjson::SerializeOptions::prettyPrinted() { - SerializeOptions o; - o.pretty = true; - return o; -} -// Constructs a success-state parse diagnostic at the start of input. -pjson::ParseError::ParseError() - : ok(true) - , offset(0) - , line(1) - , column(1) {} -// Constructs a success-state pointer diagnostic with no failing token. -pjson::PointerError::PointerError() - : ok(true) - , code(Ok) - , pointer() - , tokenIndex(0) - , token() - , message() {} -// Constructs a success-state patch diagnostic with no active operation. -pjson::PatchError::PatchError() - : ok(true) - , code(Ok) - , opIndex(0) - , op() - , path() - , from() - , tokenIndex(0) - , token() - , message() {} -// Establishes finite amplification limits for both JSON Patch variants. -pjson::PatchOptions::PatchOptions() - : maxOperations(10000) - , maxClonedNodes(1000000) - , maxClonedBytes(size_t(64) * 1024U * 1024U) - , maxWork(1000000) {} -// Gives polymorphic SAX handlers a safe virtual destruction point. -pjson::SaxHandler::~SaxHandler() {} -// Accepts a null event by default. -bool pjson::SaxHandler::onNull() { - return true; +// Returns the active storage tag. +pjson::jsonType pjson::getType() const { + return _pImpl->_eType; +}; +// Type predicates inspect the tag only and never coerce the stored value. +bool pjson::isNull() const { + return _pImpl->_eType == jsonNull; } -// Accepts a boolean event by default. -bool pjson::SaxHandler::onBool(bool) { - return true; +bool pjson::isString() const { + return _pImpl->_eType == jsonString; } -// Accepts an integer event by default. -bool pjson::SaxHandler::onInt(int64_t) { - return true; +bool pjson::isNumber() const { + return _pImpl->_eType == jsonNumberInt || _pImpl->_eType == jsonNumberUInt || + _pImpl->_eType == jsonNumberDouble; } -// Accepts a floating-point event by default. -bool pjson::SaxHandler::onDouble(double) { - return true; +bool pjson::isInt() const { + return _pImpl->_eType == jsonNumberInt; } -// Accepts a decoded string event by default. -bool pjson::SaxHandler::onString(const std::string&) { - return true; +bool pjson::isUInt() const { + return _pImpl->_eType == jsonNumberUInt; } -// Accepts an array-opening event by default. -bool pjson::SaxHandler::onStartArray() { - return true; +bool pjson::isInteger() const { + return _pImpl->_eType == jsonNumberInt || _pImpl->_eType == jsonNumberUInt; } -// Accepts an array-closing event by default. -bool pjson::SaxHandler::onEndArray() { - return true; +bool pjson::isDouble() const { + return _pImpl->_eType == jsonNumberDouble; } -// Accepts an object-opening event by default. -bool pjson::SaxHandler::onStartObject() { - return true; +bool pjson::isBool() const { + return _pImpl->_eType == jsonBoolean; } -// Accepts a decoded object-key event by default. -bool pjson::SaxHandler::onKey(const std::string&) { - return true; +bool pjson::isArray() const { + return _pImpl->_eType == jsonArray; } -// Accepts an object-closing event by default. -bool pjson::SaxHandler::onEndObject() { - return true; +bool pjson::isObject() const { + return _pImpl->_eType == jsonObject; } -// Constructs an empty schema diagnostic. -pjson::SchemaError::SchemaError() {} -// Captures one validation failure at its instance JSON Pointer. -pjson::SchemaError::SchemaError(const std::string& aPath, const std::string& aMsg) - : path(aPath) - , message(aMsg) {} -// Establishes bounded regex, recursion, and reference work with format checks enabled. -pjson::SchemaOptions::SchemaOptions() - : maxRegexPatternBytes(256) - , maxRegexSubjectBytes(4096) - , allowUnsafeRegex(false) - , maxValidationDepth(kSchemaValidationDepthHardLimit) - , maxRefResolutions(1024) - , maxValidationWork(1000000) - , maxErrors(100) - , validateFormats(true) {} -/*static*/ -// Removes regex size/safety restrictions for schemas from a trusted source; -// unrelated validation limits retain their defaults. -pjson::SchemaOptions pjson::SchemaOptions::trustedRegex() { - SchemaOptions o; - o.maxRegexPatternBytes = 0; - o.maxRegexSubjectBytes = 0; - o.allowUnsafeRegex = true; - return o; +// Constructs an empty non-owning view. +pjson::StringView::StringView() noexcept + : _data(nullptr) + , _size(0) {} +// Constructs a non-owning byte view; the caller controls the pointed-to lifetime. +pjson::StringView::StringView(const char* aData, size_t aSize) noexcept + : _data(aData) + , _size(aSize) {} +// Returns the first viewed byte, which may be null for an empty default view. +const char* pjson::StringView::data() const noexcept { + return _data; } -//===----------------------------------------------------------------------===// -// Allocator bridge and node ownership -// -// Containers and strings are constructed in allocator-provided storage. Nodes -// additionally remember whether their outer object came from that allocator so -// the uniform ValueDeleter can also destroy ordinary `new pjson` roots safely. -//===----------------------------------------------------------------------===// -namespace { - // Adapts the process-wide operator new/delete pair to the allocator API. - class DefaultPjsonAllocator : public pjson::Allocator { - public: - // Allocates raw storage; size is the only parameter needed by operator new. - void* allocate(size_t aSize, size_t, AllocationKind) override { - return ::operator new(aSize); - } - - // Releases storage previously obtained from allocate. - void deallocate(void* aPtr, size_t, size_t, AllocationKind) noexcept override { - ::operator delete(aPtr); - } - }; - - template - // Constructs an internal DOM object and returns raw storage on constructor failure. - T* allocateDomObject(pjson::Allocator& aAlloc, pjson::Allocator::AllocationKind aKind) { - void* storage = aAlloc.allocate(sizeof(T), alignof(T), aKind); - try { - return new (storage) T(); - } catch (...) { - aAlloc.deallocate(storage, sizeof(T), alignof(T), aKind); - throw; - } +// Returns the number of viewed bytes. +size_t pjson::StringView::size() const noexcept { + return _size; +} +// Reports whether the view contains no bytes. +bool pjson::StringView::empty() const noexcept { + return _size == 0; +} +// Exact extraction overloads leave the destination unchanged on type mismatch. +// A signed read accepts an unsigned value only when it fits in int64_t; an +// unsigned read accepts a signed value only when it is non-negative; a double +// read widens either integer representation. +bool pjson::tryGet(int64_t& aResult) const noexcept { + if (_pImpl->_eType == pjson::jsonType::jsonNumberInt) { + aResult = _pImpl->_valueInt; + return true; } - - template - // Runs an internal object's destructor before returning its exact allocation. - void destroyDomObject(pjson::Allocator& aAlloc, T* aObject, - pjson::Allocator::AllocationKind aKind) noexcept { - if (aObject == nullptr) - return; - aObject->~T(); - aAlloc.deallocate(aObject, sizeof(T), alignof(T), aKind); + if (_pImpl->_eType == pjson::jsonType::jsonNumberUInt && + _pImpl->_valueUInt <= static_cast(std::numeric_limits::max())) { + aResult = static_cast(_pImpl->_valueUInt); + return true; } -} // namespace -// Gives allocator implementations a safe virtual destruction point. -pjson::Allocator::~Allocator() {} -/*static*/ -// Returns the stateless process-lifetime allocator used by ordinary values. -pjson::Allocator& pjsonImpl::_defaultAllocator() noexcept { - static DefaultPjsonAllocator allocator; - return allocator; + return false; } -/*static*/ -// Constructs a node in allocator storage and marks its outer allocation so the -// deleter never mismatches allocator storage with operator delete. -pjson* pjsonImpl::_allocateNode(pjson::Allocator& aAlloc) { - void* storage = - aAlloc.allocate(sizeof(pjson), alignof(pjson), pjson::Allocator::NodeAllocation); - try { - pjson* value = new (storage) pjson(aAlloc); - value->_allocatorOwnedNode = true; - return value; - } catch (...) { - aAlloc.deallocate(storage, sizeof(pjson), alignof(pjson), pjson::Allocator::NodeAllocation); - throw; +bool pjson::tryGet(uint64_t& aResult) const noexcept { + if (_pImpl->_eType == pjson::jsonType::jsonNumberUInt) { + aResult = _pImpl->_valueUInt; + return true; } -} -/*static*/ -// Destroys a node through the mechanism that created its outer object. Child -// storage is released first by ~pjson using the node's retained allocator. -void pjsonImpl::_destroyNode(pjson* aValue) noexcept { - if (aValue == nullptr) - return; - if (!aValue->_allocatorOwnedNode) { - delete aValue; - return; + if (_pImpl->_eType == pjson::jsonType::jsonNumberInt && _pImpl->_valueInt >= 0) { + aResult = static_cast(_pImpl->_valueInt); + return true; } - pjson::Allocator& allocator = *aValue->_allocator; - aValue->~pjson(); - allocator.deallocate(aValue, sizeof(pjson), alignof(pjson), pjson::Allocator::NodeAllocation); -} -// Provides unique_ptr with the same origin-aware destruction used by DOM owners. -void pjson::ValueDeleter::operator()(pjson* aValue) const noexcept { - pjsonImpl::_destroyNode(aValue); -} - -//===----------------------------------------------------------------------===// -// DOM value lifetime, storage transfer, and type access -// -// A pjson's allocator identity is immutable. Contents may be transferred in -// constant time only between values using the same allocator; cross-allocator -// moves become deep copies so every descendant remains owned consistently. -//===----------------------------------------------------------------------===// - -// Initializes the inactive union representation before a type is selected. -pjson::Storage::Storage() - : _pValueRaw(nullptr) {} - -// Constructs a non-allocator-owned null root using the default allocator. -pjson::pjson() - : _allocator(&pjsonImpl::_defaultAllocator()) - , _allocatorOwnedNode(false) - , _disposeNext(nullptr) - , _eType(jsonType::jsonNull) - , _uValue() {} -// Constructs a non-allocator-owned null root backed by a caller allocator. -pjson::pjson(Allocator& aAlloc) noexcept - : _allocator(&aAlloc) - , _allocatorOwnedNode(false) - , _disposeNext(nullptr) - , _eType(jsonType::jsonNull) - , _uValue() {} -// Releases the active value and all descendants through their retained allocator. -pjson::~pjson() { - reset(); -} -// Deep-copies a value while preserving its allocator identity. -pjson::pjson(const pjson& aFrom) - : _allocator(aFrom._allocator) - , _allocatorOwnedNode(false) - , _disposeNext(nullptr) - , _eType(jsonType::jsonNull) - , _uValue() { - copyContentsFrom(aFrom); -} -// Deep-copies a value into a specifically selected allocator domain. -pjson::pjson(const pjson& aFrom, Allocator& aAlloc) - : _allocator(&aAlloc) - , _allocatorOwnedNode(false) - , _disposeNext(nullptr) - , _eType(jsonType::jsonNull) - , _uValue() { - copyContentsFrom(aFrom); -} -// Steals storage from a same-allocator source and leaves it as null. -pjson::pjson(pjson&& aFrom) noexcept - : _allocator(aFrom._allocator) - , _allocatorOwnedNode(false) - , _disposeNext(nullptr) - , _eType(jsonType::jsonNull) - , _uValue() { - static_assert(std::is_trivially_copyable::value, - "pjson storage must remain safe for bytewise transfer"); - _eType = aFrom._eType; - std::memcpy(&_uValue, &aFrom._uValue, sizeof(_uValue)); - aFrom._uValue._pValueRaw = nullptr; - aFrom._eType = jsonType::jsonNull; -} -// Steals when allocator domains match; otherwise deep-copies into aAlloc and -// resets the source only after the copy succeeds. -pjson::pjson(pjson&& aFrom, Allocator& aAlloc) - : _allocator(&aAlloc) - , _allocatorOwnedNode(false) - , _disposeNext(nullptr) - , _eType(jsonType::jsonNull) - , _uValue() { - if (_allocator == aFrom._allocator) { - _eType = aFrom._eType; - std::memcpy(&_uValue, &aFrom._uValue, sizeof(_uValue)); - aFrom._uValue._pValueRaw = nullptr; - aFrom._eType = jsonType::jsonNull; - } else { - copyContentsFrom(aFrom); - aFrom.reset(); - } -} -// Replaces this value from an rvalue, using constant-time transfer only when -// both allocator domains match. Self-move is a no-op. -pjson& pjson::operator=(pjson&& aFrom) { - if (&aFrom == this) - return *this; - - if (_allocator == aFrom._allocator) { - reset(); - _eType = aFrom._eType; - std::memcpy(&_uValue, &aFrom._uValue, sizeof(_uValue)); - aFrom._eType = jsonType::jsonNull; - aFrom._uValue._pValueRaw = nullptr; - } else { - pjson tmp(std::move(aFrom), *_allocator); - swap(tmp); - } - - return *this; -} -// O(1) exchange of two nodes' contents (type tag + inline storage). noexcept, -// which is what lets the move operations and copy-and-swap assignment below -// offer their exception guarantees. -void pjson::swap(pjson& aOther) noexcept { - static_assert(std::is_trivially_copyable::value, - "pjson storage must remain safe for bytewise swap"); - if (this == &aOther || !canSwap(aOther)) - return; - std::swap(_eType, aOther._eType); - Storage temp; - std::memcpy(&temp, &_uValue, sizeof(temp)); - std::memcpy(&_uValue, &aOther._uValue, sizeof(_uValue)); - std::memcpy(&aOther._uValue, &temp, sizeof(aOther._uValue)); -} -// Returns the allocator permanently associated with this value and its descendants. -pjson::Allocator& pjson::getAllocator() const noexcept { - return *_allocator; -} -// Reports whether contents can be exchanged without crossing allocator domains. -bool pjson::canSwap(const pjson& aOther) const noexcept { - return _allocator == aOther._allocator; -} -// Copy assignment (copy-and-swap: safe even when aFrom aliases a child of -// this, because the deep copy completes before any of our storage is freed). -pjson& pjson::operator=(const pjson& aFrom) { - if (&aFrom == this) - return *this; - - pjson tmp(aFrom, *_allocator); - swap(tmp); - return *this; -} -// Returns the active storage tag. -pjson::jsonType pjson::getType() const { - return _eType; -}; -// Type predicates inspect the tag only and never coerce the stored value. -bool pjson::isNull() const { - return _eType == jsonNull; -} -bool pjson::isString() const { - return _eType == jsonString; -} -bool pjson::isNumber() const { - return _eType == jsonNumberInt || _eType == jsonNumberDouble; -} -bool pjson::isInt() const { - return _eType == jsonNumberInt; -} -bool pjson::isDouble() const { - return _eType == jsonNumberDouble; -} -bool pjson::isBool() const { - return _eType == jsonBoolean; -} -bool pjson::isArray() const { - return _eType == jsonArray; -} -bool pjson::isObject() const { - return _eType == jsonObject; -} -// Constructs an empty non-owning view. -pjson::StringView::StringView() noexcept - : _data(nullptr) - , _size(0) {} -// Constructs a non-owning byte view; the caller controls the pointed-to lifetime. -pjson::StringView::StringView(const char* aData, size_t aSize) noexcept - : _data(aData) - , _size(aSize) {} -// Returns the first viewed byte, which may be null for an empty default view. -const char* pjson::StringView::data() const noexcept { - return _data; -} -// Returns the number of viewed bytes. -size_t pjson::StringView::size() const noexcept { - return _size; -} -// Reports whether the view contains no bytes. -bool pjson::StringView::empty() const noexcept { - return _size == 0; -} -// Exact extraction overloads leave the destination unchanged on type mismatch; -// only double extraction also accepts an integer through widening conversion. -bool pjson::tryGet(int64_t& aResult) const noexcept { - if (_eType != jsonType::jsonNumberInt) - return false; - aResult = _uValue._valueInt; - return true; -} -bool pjson::tryGet(double& aResult) const noexcept { - if (_eType == jsonType::jsonNumberInt) { - aResult = static_cast(_uValue._valueInt); - return true; - } - if (_eType != jsonType::jsonNumberDouble) - return false; - aResult = _uValue._valueDouble; - return true; -} -bool pjson::tryGet(bool& aResult) const noexcept { - if (_eType != jsonType::jsonBoolean) - return false; - aResult = _uValue._valueBool; - return true; -} -bool pjson::tryGet(std::string& aResult) const { - if (_eType != jsonType::jsonString) - return false; - aResult = *_uValue._pValueString; - return true; -} -bool pjson::tryGet(StringView& aResult) const noexcept { - if (_eType != jsonType::jsonString) - return false; - const std::string& value = *_uValue._pValueString; - aResult = StringView(value.data(), value.size()); - return true; -} -// Resets to the canonical null state, releasing any owned subtree. -void pjson::reset() { - resetTo(jsonType::jsonNull); -} -// Idempotent reset: rebuild as an empty value of aeType only when the node is -// not already that type, so an existing array/object keeps its contents. -void pjson::resetIfNeeded(jsonType aeType) { - if (_eType != aeType) { - resetTo(aeType); - } -} -// Rebuilds storage for aeType and initializes its empty/default value. The new -// container/string allocation occurs before teardown to preserve validity on throw. -void pjson::resetTo(pjson::jsonType aeType) { - // Reject forged enum values before allocation or teardown so the strong - // exception guarantee also covers an invalid requested discriminator. - if (aeType < jsonType::jsonNull || aeType > jsonType::jsonObject) - throw std::invalid_argument("invalid pjson::jsonType"); - - // Allocate the replacement before destroying the current value. If an - // allocation fails, *this remains unchanged and internally valid. - void* replacement = nullptr; - switch (aeType) { - case jsonType::jsonString: - replacement = allocateDomObject(*_allocator, Allocator::StringAllocation); - break; - case jsonType::jsonArray: - replacement = allocateDomObject(*_allocator, Allocator::ArrayAllocation); - break; - case jsonType::jsonObject: - replacement = allocateDomObject(*_allocator, Allocator::ObjectAllocation); - break; - default: - break; - } - - switch (_eType) { - case jsonType::jsonNull: { - _uValue._pValueRaw = nullptr; - break; - } - case jsonType::jsonString: { - destroyDomObject(*_allocator, _uValue._pValueString, Allocator::StringAllocation); - break; - } - case jsonType::jsonNumberInt: - case jsonType::jsonNumberDouble: - case jsonType::jsonBoolean: - break; - case jsonType::jsonArray: { - // Free descendants iteratively (safe on deep trees), then the vector. - pjsonImpl::_disposeChildren(*this); - destroyDomObject(*_allocator, _uValue._pValueArray, Allocator::ArrayAllocation); - break; - } - case jsonType::jsonObject: { - pjsonImpl::_disposeChildren(*this); - destroyDomObject(*_allocator, _uValue._pValueMap, Allocator::ObjectAllocation); - break; - } - } // end switch - _uValue._pValueRaw = nullptr; - - switch (aeType) { - case jsonType::jsonNull: { /* _uValue._pValueRaw = nullptr; */ - break; - } - case jsonType::jsonString: { - _uValue._pValueString = static_cast(replacement); - break; - } - case jsonType::jsonNumberInt: { - _uValue._valueInt = 0; - break; - } - case jsonType::jsonNumberDouble: { - _uValue._valueDouble = 0.0; - break; - } - case jsonType::jsonBoolean: { - _uValue._valueBool = false; - break; - } - case jsonType::jsonArray: { - _uValue._pValueArray = static_cast(replacement); - break; - } - case jsonType::jsonObject: { - _uValue._pValueMap = static_cast(replacement); - break; - } - } // end switch - _eType = aeType; -} -// Replaces this value with a deep copy allocated in this value's allocator. -// Building the replacement first gives the operation a strong guarantee. -void pjson::copyFrom(const pjson& aFrom) { - if (this == &aFrom) - return; - pjson replacement(aFrom, *_allocator); - swap(replacement); -} -// Populates this node from aFrom without recursion. If copying fails, partial -// descendants are reclaimed and this node is reset to a valid null state. -void pjson::copyContentsFrom(const pjson& aFrom) { - // Iterative deep copy. A recursive copy would overflow the stack on very - // deep documents, so we walk with an explicit work-list: each item pairs a - // source node with the destination node to populate from it. Scalars are - // copied immediately; array/map children are queued. - try { - resetTo(aFrom.getType()); - if (_eType != jsonType::jsonArray && _eType != jsonType::jsonObject) { - switch (_eType) { - case jsonType::jsonString: - *_uValue._pValueString = *(aFrom._uValue._pValueString); - break; - case jsonType::jsonNumberInt: - _uValue._valueInt = aFrom._uValue._valueInt; - break; - case jsonType::jsonNumberDouble: - _uValue._valueDouble = aFrom._uValue._valueDouble; - break; - case jsonType::jsonBoolean: - _uValue._valueBool = aFrom._uValue._valueBool; - break; - default: - break; // null: nothing to copy - } - return; - } - - struct Item { - const pjson* src; - pjson* dst; - }; - std::vector work; - Item start = {&aFrom, this}; - work.push_back(start); - - while (!work.empty()) { - Item cur = work.back(); - work.pop_back(); - const pjson& src = *cur.src; - pjson& dst = *cur.dst; - - // dst has already been resetTo(src type) by the parent (or caller). - if (src._eType == jsonType::jsonArray) { - dst._uValue._pValueArray->reserve(src._uValue._pValueArray->size()); - for (const pjson* elem : *src._uValue._pValueArray) { - unique_ptr child = pjsonImpl::_makeNode(*_allocator); - child->resetTo(elem->getType()); - dst._uValue._pValueArray->push_back(child.get()); - pjson* attached = child.release(); - if (elem->_eType == jsonType::jsonArray || - elem->_eType == jsonType::jsonObject) { - Item it = {elem, attached}; - work.push_back(it); - } else { - attached->copyContentsFrom(*elem); - } - } - } else { // jsonObject - for (const auto& kv : *src._uValue._pValueMap) { - unique_ptr child = pjsonImpl::_makeNode(*_allocator); - child->resetTo(kv.second->getType()); - const std::pair inserted = - dst._uValue._pValueMap->insert( - std::make_pair(kv.first, static_cast(nullptr))); - if (!inserted.second) - throw std::logic_error("duplicate key while copying pjson object"); - pjson* attached = child.release(); - inserted.first->second = attached; - if (kv.second->_eType == jsonType::jsonArray || - kv.second->_eType == jsonType::jsonObject) { - Item it = {kv.second, attached}; - work.push_back(it); - } else { - attached->copyContentsFrom(*kv.second); - } - } - } - } - } catch (...) { - reset(); - throw; - } -} -// Frees every descendant of node iteratively, so tearing down a very deep -// tree cannot overflow the call stack (as the recursive destructor would). -// Leaves node's own top-level array/map allocated but empty. -/*static*/ -void pjsonImpl::_disposeChildren(pjson& node) noexcept { - if (node._eType != jsonType::jsonArray && node._eType != jsonType::jsonObject) { - return; - } - // Use an intrusive pending list so teardown never allocates and therefore - // remains noexcept even for very deep trees or an exhausted heap. - pjson* pending = nullptr; - if (node._eType == jsonType::jsonArray) { - for (pjson* c : *node._uValue._pValueArray) { - c->_disposeNext = pending; - pending = c; - } - node._uValue._pValueArray->clear(); - } else { - for (const auto& kv : *node._uValue._pValueMap) { - kv.second->_disposeNext = pending; - pending = kv.second; - } - node._uValue._pValueMap->clear(); - } - - while (pending != nullptr) { - pjson* p = pending; - pending = p->_disposeNext; - p->_disposeNext = nullptr; - // Move this node's children into the work-list, then detach so its own - // destructor has nothing left to recurse into. - if (p->_eType == jsonType::jsonArray) { - for (pjson* c : *p->_uValue._pValueArray) { - c->_disposeNext = pending; - pending = c; - } - p->_uValue._pValueArray->clear(); - } else if (p->_eType == jsonType::jsonObject) { - for (const auto& kv : *p->_uValue._pValueMap) { - kv.second->_disposeNext = pending; - pending = kv.second; - } - p->_uValue._pValueMap->clear(); - } - pjsonImpl::_destroyNode(p); // now a leaf (or emptied container) - } -} -// Recognizes exactly the whitespace code points admitted by the JSON grammar. -bool pjsonImpl::_isWhitespace(char c) { - return c == ' ' || c == '\t' || c == '\n' || c == '\r'; -} -// Encodes a Unicode code point as UTF-8 and appends it to aOut. -/*static*/ -void pjsonImpl::_appendUtf8(uint32_t aCodePoint, std::string& aOut) { - if (aCodePoint <= 0x7F) { - aOut += static_cast(aCodePoint); - } else if (aCodePoint <= 0x7FF) { - aOut += static_cast(0xC0 | (aCodePoint >> 6)); - aOut += static_cast(0x80 | (aCodePoint & 0x3F)); - } else if (aCodePoint <= 0xFFFF) { - aOut += static_cast(0xE0 | (aCodePoint >> 12)); - aOut += static_cast(0x80 | ((aCodePoint >> 6) & 0x3F)); - aOut += static_cast(0x80 | (aCodePoint & 0x3F)); - } else { - aOut += static_cast(0xF0 | (aCodePoint >> 18)); - aOut += static_cast(0x80 | ((aCodePoint >> 12) & 0x3F)); - aOut += static_cast(0x80 | ((aCodePoint >> 6) & 0x3F)); - aOut += static_cast(0x80 | (aCodePoint & 0x3F)); - } -} -// Decodes exactly four hexadecimal bytes at aStart into one UTF-16 code unit. -bool pjsonImpl::_hex4(const char* aSrc, size_t aStart, uint32_t& aOut) { - aOut = 0; - for (int k = 0; k < 4; ++k) { - char h = aSrc[aStart + k]; - aOut <<= 4; - if (h >= '0' && h <= '9') - aOut |= static_cast(h - '0'); - else if (h >= 'a' && h <= 'f') - aOut |= static_cast(h - 'a' + 10); - else if (h >= 'A' && h <= 'F') - aOut |= static_cast(h - 'A' + 10); - else - return false; - } - return true; -} -// Returns the length (1..4) of the valid UTF-8 sequence starting at -// src[pos], or 0 if the bytes there are not valid UTF-8. Requires pos < end. -int pjsonImpl::_utf8Len(const char* src, size_t pos, size_t end) { - unsigned char c0 = static_cast(src[pos]); - int n; - uint32_t cp; - uint32_t lo; - if (c0 < 0x80) - return 1; - else if ((c0 & 0xE0) == 0xC0) { - n = 2; - cp = c0 & 0x1F; - lo = 0x80; - } else if ((c0 & 0xF0) == 0xE0) { - n = 3; - cp = c0 & 0x0F; - lo = 0x800; - } else if ((c0 & 0xF8) == 0xF0) { - n = 4; - cp = c0 & 0x07; - lo = 0x10000; - } else - return 0; // stray continuation / invalid lead - if (pos + static_cast(n) > end) - return 0; - for (int k = 1; k < n; ++k) { - unsigned char ck = static_cast(src[pos + k]); - if ((ck & 0xC0) != 0x80) - return 0; // not a continuation byte - cp = (cp << 6) | (ck & 0x3F); - } - if (cp < lo) - return 0; // overlong encoding - if (cp > 0x10FFFF) - return 0; // beyond Unicode - if (cp >= 0xD800 && cp <= 0xDFFF) - return 0; // surrogate half in UTF-8 - return n; -} -// Records the first parse error (byte offset + message) and returns false so -// callers can `return _fail(...)`. -/*static*/ -bool pjsonImpl::_fail(ParseCtx& c, size_t aPos, const char* aMsg) { - if (!c.failed) { - c.failed = true; - c.errPos = aPos; - c.errMsg = aMsg; - } - return false; -} -// Allocates a new pjson while enforcing the node budget. Returns nullptr (and -// records a "document too large" failure) once maxNodes values have been -// created, which caps total memory even for inputs that stay within maxDepth -// (e.g. a huge flat array). The caller propagates the nullptr as a parse error. -/*static*/ -pjson* pjsonImpl::_newNode(ParseCtx& c) { - if (c.maxNodes != 0 && c.nodeCount >= c.maxNodes) { - _fail(c, c.pos, "document too large (node budget exceeded)"); - return nullptr; - } - ++c.nodeCount; - return pjsonImpl::_allocateNode(*c.allocator); -} -/*static*/ -// Allocates a null node under the supplied allocator and wraps origin-aware cleanup. -pjson::unique_ptr pjsonImpl::_makeNode(pjson::Allocator& aAlloc) { - return pjson::unique_ptr(pjsonImpl::_allocateNode(aAlloc)); -} -/*static*/ -// Deep-clones a complete subtree into the supplied allocator domain. -pjson::unique_ptr pjsonImpl::_cloneNode(const pjson& aValue, pjson::Allocator& aAlloc) { - pjson::unique_ptr result = _makeNode(aAlloc); - result->copyContentsFrom(aValue); - return result; -} -// Decodes a JSON string body from c.pos into aOut. With bStopAtQuote, decoding -// stops at (and consumes) the first unescaped '"'. RFC 8259-invalid escapes, -// control bytes, surrogate halves, and UTF-8 are rejected. -/*static*/ -bool pjsonImpl::_decodeStringBody(ParseCtx& c, std::string& aOut, bool bStopAtQuote) { - aOut.clear(); - while (c.pos < c.end) { - unsigned char ch = static_cast(c.src[c.pos]); - if (bStopAtQuote && ch == '\"') { - ++c.pos; - return true; - } - if (ch == '\\') { - ++c.pos; - if (c.pos >= c.end) { - return _fail(c, c.pos, "dangling escape at end of input"); - } - char e = c.src[c.pos++]; - switch (e) { - case '\"': - aOut += '\"'; - break; - case '\\': - aOut += '\\'; - break; - case '/': - aOut += '/'; - break; - case 'b': - aOut += '\b'; - break; - case 'f': - aOut += '\f'; - break; - case 'n': - aOut += '\n'; - break; - case 'r': - aOut += '\r'; - break; - case 't': - aOut += '\t'; - break; - case 'u': { - uint32_t cp = 0; - if (c.pos + 4 > c.end || !pjsonImpl::_hex4(c.src, c.pos, cp)) { - return _fail(c, c.pos, "invalid \\u escape"); - } - c.pos += 4; - if (cp >= 0xD800 && cp <= 0xDBFF) { - // High surrogate: look for a following low surrogate. - uint32_t low = 0; - if (c.pos + 6 <= c.end && c.src[c.pos] == '\\' && c.src[c.pos + 1] == 'u' && - pjsonImpl::_hex4(c.src, c.pos + 2, low) && low >= 0xDC00 && - low <= 0xDFFF) { - cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); - c.pos += 6; - } else { - return _fail(c, c.pos, "unpaired high surrogate"); - } - } else if (cp >= 0xDC00 && cp <= 0xDFFF) { - return _fail(c, c.pos, "unpaired low surrogate"); - } - _appendUtf8(cp, aOut); - break; - } - default: - return _fail(c, c.pos - 1, "invalid escape sequence"); - } - } else if (ch < 0x20) { - return _fail(c, c.pos, "unescaped control character in string"); - } else if (ch >= 0x80) { - int n = pjsonImpl::_utf8Len(c.src, c.pos, c.end); - if (n == 0) { - return _fail(c, c.pos, "invalid UTF-8 sequence"); - } - aOut.append(c.src + c.pos, static_cast(n)); - c.pos += static_cast(n); - } else { - aOut += static_cast(ch); - ++c.pos; - } - } - if (bStopAtQuote) { - return _fail(c, c.pos, "unterminated string"); - } - return true; -} -// Formats a finite double with enough classic-locale precision to round-trip. -// A '.0' suffix is appended when the result would otherwise look like an -// integer, so the value re-parses into the double representation (type-stable). -/*static*/ -std::string pjsonImpl::_formatDouble(double aValue) { - if (!std::isfinite(aValue)) { - // JSON has no representation for NaN/Infinity. - return "null"; - } - std::string result; - for (int prec = 15; prec <= 17; ++prec) { - std::ostringstream out; - out.imbue(std::locale::classic()); - out << std::setprecision(prec) << aValue; - result = out.str(); - double parsed = 0.0; - if (_parseDouble(result, parsed) && parsed == aValue) { - break; - } - } - if (result.find_first_of(".eE") == std::string::npos) { - result += ".0"; - } - return result; -} -// Parses an ASCII JSON number independently of the process LC_NUMERIC locale. -bool pjsonImpl::_parseDouble(const std::string& aText, double& aValue) { - std::istringstream in(aText); - in.imbue(std::locale::classic()); - in >> std::noskipws >> aValue; - if (!in.fail()) - return in.peek() == std::char_traits::eof(); - // libstdc++/libc++ set failbit as well as eofbit for both underflow and - // overflow. Classify the range direction from the decimal exponent instead - // of trusting the implementation-specific saturated result. A negative - // effective decimal exponent cannot overflow binary64, so its finite zero or - // subnormal result is valid; nonnegative range failures are overflow. - if (!in.eof() || !std::isfinite(aValue)) - return false; - const size_t signOffset = !aText.empty() && aText[0] == '-' ? 1 : 0; - const size_t exponentMark = aText.find_first_of("eE"); - const size_t significandEnd = exponentMark == std::string::npos ? aText.size() : exponentMark; - const size_t point = aText.find('.', signOffset); - const size_t digitsBeforePoint = point != std::string::npos && point < significandEnd - ? point - signOffset - : significandEnd - signOffset; - size_t digitOrdinal = 0; - size_t firstNonzero = std::string::npos; - for (size_t i = signOffset; i < significandEnd; ++i) { - if (aText[i] == '.') - continue; - if (firstNonzero == std::string::npos && aText[i] != '0') - firstNonzero = digitOrdinal; - ++digitOrdinal; - } - if (firstNonzero == std::string::npos) - return true; // exact zero cannot overflow - - const int64_t kExponentCap = INT64_C(1000000000); - int64_t explicitExponent = 0; - if (exponentMark != std::string::npos) { - size_t i = exponentMark + 1; - bool negative = false; - if (i < aText.size() && (aText[i] == '+' || aText[i] == '-')) { - negative = aText[i] == '-'; - ++i; - } - for (; i < aText.size(); ++i) { - const int digit = aText[i] - '0'; - if (explicitExponent > (kExponentCap - digit) / 10) { - explicitExponent = kExponentCap; - break; - } - explicitExponent = explicitExponent * 10 + digit; - } - if (negative) - explicitExponent = -explicitExponent; - } - const int64_t baseExponent = - static_cast(digitsBeforePoint) - static_cast(firstNonzero) - 1; - const int64_t effectiveExponent = explicitExponent > kExponentCap - baseExponent ? kExponentCap - : explicitExponent < -kExponentCap - baseExponent - ? -kExponentCap - : explicitExponent + baseExponent; - return effectiveExponent < 0; -} -namespace { - //===------------------------------------------------------------------===// - // Serializer sink adapters - // - // The serializer targets this tiny common protocol. String failures throw - // as normal allocation/length errors; stream failures set failbit and are - // returned as false. This keeps traversal and escaping logic identical. - //===------------------------------------------------------------------===// - - // Appends serialized bytes directly to a caller-owned string. - class StringSink { - public: - // Binds to the output string without clearing its existing contents. - StringSink(std::string& aOut, size_t aLimit) - : _out(aOut) - , _limit(aLimit) - , _written(0) {} - - // Appends one byte. - void put(char aChar) { - account(1); - _out += aChar; - } - // Appends an exact byte range. - void write(const char* aData, size_t aSize) { - account(aSize); - _out.append(aData, aSize); - } - // Appends repeated indentation, preserving std::string's length checks. - bool repeat(char aChar, size_t aCount) { - // std::string::append performs the correct max_size check and - // throws std::length_error before attempting an impossible - // allocation. This keeps pathological indentation options from - // turning into an effectively unbounded byte-at-a-time loop. - account(aCount); - _out.append(aCount, aChar); - return true; - } - // Converts arithmetic overflow in indentation sizing into a length error. - bool fail() { throw std::length_error("JSON indentation exceeds string limits"); } - // A std::string cannot report failure state, so reject invalid UTF-8 by exception. - bool invalidUtf8() { throw std::invalid_argument("JSON string contains invalid UTF-8"); } - // A live string sink has no independent error state. - explicit operator bool() const { return true; } - - private: - void account(size_t amount) { - if (_limit != 0 && amount > _limit - std::min(_written, _limit)) - throw std::length_error("JSON output exceeds maxOutputBytes"); - _written += amount; - } - - std::string& _out; - size_t _limit; - size_t _written; - }; - - // Runs the exact serializer without retaining bytes. This preflight keeps - // logical failures (invalid UTF-8, indentation overflow, and output-budget - // exhaustion) from partially modifying a caller's stream. - class CountingSink { - public: - explicit CountingSink(size_t aLimit) - : _limit(aLimit) - , _written(0) - , _valid(true) - , _invalidUtf8(false) {} - - void put(char) { account(1); } - void write(const char*, size_t aSize) { account(aSize); } - bool repeat(char, size_t aCount) { return account(aCount); } - bool fail() { - _valid = false; - return false; - } - bool invalidUtf8() { - _invalidUtf8 = true; - return fail(); - } - explicit operator bool() const { return _valid; } - size_t size() const { return _written; } - bool hasInvalidUtf8() const { return _invalidUtf8; } - - private: - bool account(size_t aAmount) { - if (!_valid) - return false; - if (aAmount > std::numeric_limits::max() - _written || - (_limit != 0 && aAmount > _limit - std::min(_written, _limit))) { - _valid = false; - return false; - } - _written += aAmount; - return true; - } - - size_t _limit; - size_t _written; - bool _valid; - bool _invalidUtf8; - }; - - // Writes serialized bytes incrementally and reflects ostream failure state. - class StreamSink { - public: - // Binds to a caller-owned stream without changing its formatting flags. - StreamSink(std::ostream& aOut, size_t aLimit) - : _out(aOut) - , _limit(aLimit) - , _written(0) {} - - // Writes one byte through the stream buffer. - void put(char aChar) { - if (account(1)) - _out.put(aChar); - } - // Writes an exact byte range. - void write(const char* aData, size_t aSize) { - if (aSize > static_cast(std::numeric_limits::max())) { - _out.setstate(std::ios::failbit); - return; - } - if (!account(aSize)) - return; - _out.write(aData, static_cast(aSize)); - } - // Emits indentation in bounded blocks and rejects counts that cannot be - // represented by ostream::write's streamsize parameter. - bool repeat(char aChar, size_t aCount) { - const size_t maxWrite = - static_cast(std::numeric_limits::max()); - if (aCount > maxWrite || !account(aCount)) { - if (_out) - _out.setstate(std::ios::failbit); - return false; - } - - char block[256]; - std::memset(block, static_cast(aChar), sizeof(block)); - while (aCount != 0 && _out) { - const size_t amount = std::min(aCount, sizeof(block)); - _out.write(block, static_cast(amount)); - aCount -= amount; - } - return static_cast(_out); - } - // Marks non-I/O serializer failures in the stream's normal error state. - bool fail() { - _out.setstate(std::ios::failbit); - return false; - } - // Streaming reports invalid programmatic string data through failbit. - bool invalidUtf8() { return fail(); } - // Exposes the underlying stream state to generic serializer code. - explicit operator bool() const { return static_cast(_out); } - - private: - bool account(size_t amount) { - if (_limit != 0 && amount > _limit - std::min(_written, _limit)) { - _out.setstate(std::ios::failbit); - return false; - } - _written += amount; - return true; - } - - std::ostream& _out; - size_t _limit; - size_t _written; - }; - - template - // Writes depth * indentWidth characters after checking multiplication overflow. - bool writeIndent(Sink& out, size_t depth, const pjson::SerializeOptions& opts) { - const char indent = opts.indentCharacter == '\t' ? '\t' : ' '; - if (opts.indentWidth != 0 && depth > size_t(-1) / opts.indentWidth) - return out.fail(); - const size_t count = depth * opts.indentWidth; - return out.repeat(indent, count); - } - - // Writes one UTF-16 code unit in canonical lower-case \uXXXX form. - template bool writeUnicodeEscape(Sink& out, uint16_t value) { - static const char hex[] = "0123456789abcdef"; - char escape[6] = {'\\', - 'u', - hex[(value >> 12U) & 0x0FU], - hex[(value >> 8U) & 0x0FU], - hex[(value >> 4U) & 0x0FU], - hex[value & 0x0FU]}; - out.write(escape, sizeof(escape)); - return static_cast(out); - } -} // namespace - -template -// Writes a JSON string body, optionally converting every non-ASCII code point -// to one UTF-16 escape (or a surrogate pair) without adding surrounding quotes. -bool pjsonImpl::_writeEscapedTo(Sink& aOut, const std::string& aIn, bool bEscapeNonAscii) { - size_t i = 0; - while (i < aIn.size()) { - const unsigned char ch = static_cast(aIn[i]); - const char* escape = nullptr; - switch (ch) { - case '"': - escape = "\\\""; - break; - case '\\': - escape = "\\\\"; - break; - case '\b': - escape = "\\b"; - break; - case '\f': - escape = "\\f"; - break; - case '\n': - escape = "\\n"; - break; - case '\r': - escape = "\\r"; - break; - case '\t': - escape = "\\t"; - break; - default: - break; - } - if (escape) { - aOut.write(escape, 2); - ++i; - if (!aOut) - return false; - continue; - } - if (ch < 0x20) { - if (!writeUnicodeEscape(aOut, static_cast(ch))) - return false; - ++i; - continue; - } - if (ch < 0x80) { - aOut.put(static_cast(ch)); - ++i; - if (!aOut) - return false; - continue; - } - - const int byteCount = _utf8Len(aIn.data(), i, aIn.size()); - if (byteCount == 0) - return aOut.invalidUtf8(); - if (!bEscapeNonAscii) { - aOut.write(aIn.data() + i, static_cast(byteCount)); - i += static_cast(byteCount); - if (!aOut) - return false; - continue; - } - - uint32_t codePoint = ch & (byteCount == 2 ? 0x1FU : byteCount == 3 ? 0x0FU : 0x07U); - for (int k = 1; k < byteCount; ++k) - codePoint = (codePoint << 6U) | (static_cast(aIn[i + k]) & 0x3FU); - if (codePoint <= 0xFFFFU) { - if (!writeUnicodeEscape(aOut, static_cast(codePoint))) - return false; - } else { - codePoint -= 0x10000U; - const uint16_t high = static_cast(0xD800U + (codePoint >> 10U)); - const uint16_t low = static_cast(0xDC00U + (codePoint & 0x3FFU)); - if (!writeUnicodeEscape(aOut, high) || !writeUnicodeEscape(aOut, low)) - return false; - } - i += static_cast(byteCount); - } - return static_cast(aOut); -} - -template -// Emits a scalar or an empty container immediately. For a non-empty container, -// emits its opening delimiter and pushes a frame whose cursor is at its first -// child; the caller owns closing it after all children have been traversed. -bool pjsonImpl::_openOrEmit(Sink& aOut, const pjson* aValue, size_t aDepth, - const pjson::SerializeOptions& aOpts, - std::vector& aFrames) { - switch (aValue->_eType) { - case jsonType::jsonNull: - aOut.write("null", 4); - return static_cast(aOut); - case jsonType::jsonString: - aOut.put('"'); - if (!aOut || - !_writeEscapedTo(aOut, *aValue->_uValue._pValueString, aOpts.escapeNonAscii)) - return false; - aOut.put('"'); - return static_cast(aOut); - case jsonType::jsonNumberInt: { - const std::string text = std::to_string(aValue->_uValue._valueInt); - aOut.write(text.data(), text.size()); - return static_cast(aOut); - } - case jsonType::jsonNumberDouble: { - const std::string text = _formatDouble(aValue->_uValue._valueDouble); - aOut.write(text.data(), text.size()); - return static_cast(aOut); - } - case jsonType::jsonBoolean: - if (aValue->_uValue._valueBool) - aOut.write("true", 4); - else - aOut.write("false", 5); - return static_cast(aOut); - case jsonType::jsonArray: - if (aValue->_uValue._pValueArray->empty()) { - aOut.write("[]", 2); - return static_cast(aOut); - } - aOut.put('['); - break; - case jsonType::jsonObject: - if (aValue->_uValue._pValueMap->empty()) { - aOut.write("{}", 2); - return static_cast(aOut); - } - aOut.put('{'); - break; - } - if (!aOut) - return false; - - SerializeFrame frame; - frame.isObject = aValue->_eType == jsonType::jsonObject; - frame.depth = aDepth; - frame.first = true; - frame.array = frame.isObject ? nullptr : aValue->_uValue._pValueArray; - frame.arrayIndex = 0; - frame.object = frame.isObject ? aValue->_uValue._pValueMap : nullptr; - if (frame.isObject) { - frame.objectIt = frame.object->begin(); - frame.objectReverseIt = frame.object->rbegin(); - } - aFrames.push_back(frame); - return true; -} - -template -// Serializes without recursive C++ calls. Before descending, the parent cursor -// advances past the chosen child, so a pushed child frame cannot invalidate the -// parent's progress when the vector reallocates. -bool pjsonImpl::_writeValueTo(Sink& aOut, const pjson& aValue, - const pjson::SerializeOptions& aOpts) { - std::vector stack; - stack.reserve(32); - if (!_openOrEmit(aOut, &aValue, 0, aOpts, stack)) - return false; - - while (!stack.empty()) { - SerializeFrame& frame = stack.back(); - const pjson* child = nullptr; - const std::string* key = nullptr; - bool hasNext = false; - if (frame.isObject) { - if (aOpts.keyOrder == pjson::SerializeOptions::DescendingKeys) { - hasNext = frame.objectReverseIt != frame.object->rend(); - if (hasNext) { - key = &frame.objectReverseIt->first; - child = frame.objectReverseIt->second; - } - } else { - hasNext = frame.objectIt != frame.object->end(); - if (hasNext) { - key = &frame.objectIt->first; - child = frame.objectIt->second; - } - } - } else { - hasNext = frame.arrayIndex < frame.array->size(); - if (hasNext) - child = (*frame.array)[frame.arrayIndex]; - } - - if (hasNext) { - if (!frame.first) - aOut.put(','); - frame.first = false; - const size_t childDepth = frame.depth + 1; - const bool isObject = frame.isObject; - if (isObject) { - if (aOpts.keyOrder == pjson::SerializeOptions::DescendingKeys) - ++frame.objectReverseIt; - else - ++frame.objectIt; - } else { - ++frame.arrayIndex; - } - if (aOpts.pretty) { - aOut.put('\n'); - if (!writeIndent(aOut, childDepth, aOpts)) - return false; - } - if (isObject) { - aOut.put('"'); - if (!aOut || !_writeEscapedTo(aOut, *key, aOpts.escapeNonAscii)) - return false; - if (aOpts.pretty) - aOut.write("\": ", 3); - else - aOut.write("\":", 2); - } - if (!aOut || !_openOrEmit(aOut, child, childDepth, aOpts, stack)) - return false; - } else { - const size_t depth = frame.depth; - const bool isObject = frame.isObject; - stack.pop_back(); - if (aOpts.pretty) { - aOut.put('\n'); - if (!writeIndent(aOut, depth, aOpts)) - return false; - } - aOut.put(isObject ? '}' : ']'); - if (!aOut) - return false; - } - } - return static_cast(aOut); -} - -/*static*/ -// Appends one serialized value to an existing string. -void pjsonImpl::_appendValue(std::string& aOut, const pjson& aValue, - const pjson::SerializeOptions& aOpts) { - StringSink sink(aOut, aOpts.maxOutputBytes); - _writeValueTo(sink, aValue, aOpts); -} - -/*static*/ -// Streams one serialized value and returns the resulting stream health. -bool pjsonImpl::_writeValue(std::ostream& aOut, const pjson& aValue, - const pjson::SerializeOptions& aOpts) { - CountingSink count(aOpts.maxOutputBytes); - if (!_writeValueTo(count, aValue, aOpts)) { - aOut.setstate(std::ios::failbit); - return false; - } - // Preflight owns the configured budget; emission itself is unlimited so a - // successful count cannot fail due to double-accounting. - StreamSink sink(aOut, 0); - return _writeValueTo(sink, aValue, aOpts); -} - -// Serializes with compact default options. -std::string pjson::toString() const { - return toString(SerializeOptions()); -} - -// Serializes this complete DOM to a newly allocated string. -std::string pjson::toString(const SerializeOptions& aOpts) const { - CountingSink count(aOpts.maxOutputBytes); - if (!pjsonImpl::_writeValueTo(count, *this, aOpts)) { - if (count.hasInvalidUtf8()) - throw std::invalid_argument("JSON string contains invalid UTF-8"); - throw std::length_error("JSON output exceeds maxOutputBytes or contains invalid data"); - } - std::string result; - result.reserve(count.size()); - pjsonImpl::_appendValue(result, *this, aOpts); - return result; -} - -// Streams with compact default options. -void pjson::write(std::ostream& aOut) const { - write(aOut, SerializeOptions()); -} - -// Writes this complete DOM incrementally; callers inspect the stream state for -// output errors because the public streaming API reports through std::ostream. -void pjson::write(std::ostream& aOut, const SerializeOptions& aOpts) const { - pjsonImpl::_writeValue(aOut, *this, aOpts); -} - -//===----------------------------------------------------------------------===// -// Scalar assignment and vector-backed array mutation -// -// Scalar assignments reuse compatible storage. Array helpers allocate children -// under RAII and publish raw pointers only after container insertion succeeds. -// Multi-element mutation either swaps a complete replacement or rolls back to -// the original size, so allocation failures never leave a partial append. -//===----------------------------------------------------------------------===// - -// Replaces the current value with a copied JSON string. -pjson& pjson::operator=(const std::string& aString) { - resetIfNeeded(jsonType::jsonString); - *_uValue._pValueString = aString; - return *this; -} -// Replaces the current value with the null-terminated string's bytes. -pjson& pjson::operator=(const char* aCString) { - if (aCString == nullptr) - throw std::invalid_argument("pjson string assignment requires non-null input"); - resetIfNeeded(jsonType::jsonString); - *_uValue._pValueString = aCString; - return *this; -} -// Replaces the current value with a JSON boolean. -pjson& pjson::operator=(const bool aBool) { - resetIfNeeded(jsonType::jsonBoolean); - _uValue._valueBool = aBool; - return *this; -} -// Replaces the current value with a JSON integer. -pjson& pjson::operator=(const int64_t aInt) { - resetIfNeeded(jsonType::jsonNumberInt); - _uValue._valueInt = aInt; - return *this; -} -// Replaces the current value with a JSON double. -pjson& pjson::operator=(const double aDouble) { - resetIfNeeded(jsonType::jsonNumberDouble); - _uValue._valueDouble = aDouble; - return *this; -} -namespace { - // Appends one converted child. A non-array target is promoted atomically by - // building and swapping a replacement; an array target changes only after - // both node construction and vector growth have succeeded. - template void appendDomValue(pjson& aTarget, const Value& aValue) { - if (!aTarget.isArray()) { - pjson replacement(aTarget.getAllocator()); - replacement.resetTo(pjson::jsonArray); - pjson::unique_ptr child = pjsonImpl::_makeNode(aTarget.getAllocator()); - *child = aValue; - pjsonImpl::_array(replacement).push_back(nullptr); - pjsonImpl::_array(replacement).back() = child.release(); - aTarget.swap(replacement); - return; - } - - pjson::unique_ptr child = pjsonImpl::_makeNode(aTarget.getAllocator()); - *child = aValue; - pjsonImpl::_array(aTarget).push_back(nullptr); - pjsonImpl::_array(aTarget).back() = child.release(); - } - - // Replaces a target with a fully constructed array, providing a strong guarantee. - template void assignDomArray(pjson& aTarget, const Values& aValues) { - pjson replacement(aTarget.getAllocator()); - replacement.resetTo(pjson::jsonArray); - for (const auto& value : aValues) { - appendDomValue(replacement, value); - } - aTarget.swap(replacement); - } - - // Appends a range atomically: newly attached children are reclaimed in - // reverse order if any later conversion or allocation throws. - template void appendDomArray(pjson& aTarget, const Values& aValues) { - if (!aTarget.isArray()) { - pjson replacement(aTarget.getAllocator()); - replacement.resetTo(pjson::jsonArray); - for (const auto& value : aValues) { - appendDomValue(replacement, value); - } - aTarget.swap(replacement); - return; - } - - PJSONARRAY& array = pjsonImpl::_array(aTarget); - const size_t originalSize = array.size(); - try { - for (const auto& value : aValues) { - appendDomValue(aTarget, value); - } - } catch (...) { - while (array.size() > originalSize) { - pjson::unique_ptr rollback(array.back()); - array.pop_back(); - } - throw; - } - } -} // namespace -// Vector assignment overloads delegate to the same strong-guarantee builder, -// but remain explicit so each supported public type is visible in this source. -pjson& pjson::operator=(const std::vector& aValueArray) { - assignDomArray(*this, aValueArray); - return *this; -} - -pjson& pjson::operator=(const std::vector& aValueArray) { - assignDomArray(*this, aValueArray); - return *this; -} - -pjson& pjson::operator=(const std::vector& aValueArray) { - assignDomArray(*this, aValueArray); - return *this; -} - -pjson& pjson::operator=(const std::vector& aValueArray) { - assignDomArray(*this, aValueArray); - return *this; -} - -// Scalar append overloads promote non-arrays and publish one fully constructed child. -pjson& pjson::operator+=(const std::string& aValue) { - appendDomValue(*this, aValue); - return *this; -} -pjson& pjson::operator+=(const char* aValue) { - if (aValue == nullptr) - throw std::invalid_argument("pjson string append requires non-null input"); - appendDomValue(*this, aValue); - return *this; -} -pjson& pjson::operator+=(const bool aValue) { - appendDomValue(*this, aValue); - return *this; -} -pjson& pjson::operator+=(const int64_t aValue) { - appendDomValue(*this, aValue); - return *this; -} -pjson& pjson::operator+=(const double aValue) { - appendDomValue(*this, aValue); - return *this; -} -// Vector append overloads share the rollback semantics documented by appendDomArray. -pjson& pjson::operator+=(const std::vector& aValueArray) { - appendDomArray(*this, aValueArray); - return *this; -} - -pjson& pjson::operator+=(const std::vector& aValueArray) { - appendDomArray(*this, aValueArray); - return *this; -} - -pjson& pjson::operator+=(const std::vector& aValueArray) { - appendDomArray(*this, aValueArray); - return *this; -} - -pjson& pjson::operator+=(const std::vector& aValueArray) { - appendDomArray(*this, aValueArray); - return *this; -} -//===----------------------------------------------------------------------===// -// Container access and lookup -// -// Mutating operator[] access auto-vivifies missing containers and children; -// find() is non-mutating. Negative array indices count from the end. Mutating -// indices before the beginning clamp to zero, while lookup indices simply miss. -//===----------------------------------------------------------------------===// - -// Returns or creates an object member, atomically promoting non-object values. -pjson& pjson::operator[](const char* aSkey) { - if (aSkey == nullptr) - throw std::invalid_argument("pjson object key requires non-null input"); - if (_eType != jsonType::jsonObject) { - pjson replacement(*_allocator); - replacement.resetTo(jsonType::jsonObject); - unique_ptr child = pjsonImpl::_makeNode(*_allocator); - const std::pair inserted = replacement._uValue._pValueMap->insert( - std::make_pair(std::string(aSkey), static_cast(nullptr))); - pjson* result = child.release(); - inserted.first->second = result; - swap(replacement); - return *result; - } - PJSONMAP::iterator it = _uValue._pValueMap->find(aSkey); - if (it != _uValue._pValueMap->end()) { - return *(it->second); - } - unique_ptr child = pjsonImpl::_makeNode(*_allocator); - const std::pair inserted = _uValue._pValueMap->insert( - std::make_pair(std::string(aSkey), static_cast(nullptr))); - pjson* result = inserted.first->second; - if (inserted.second) { - result = child.release(); - inserted.first->second = result; - } - return *result; -} -// Returns or creates an array element, filling gaps with null nodes. Any failed -// growth destroys every node appended by this call before rethrowing. -pjson& pjson::operator[](int index) { - if (_eType != jsonType::jsonArray) { - pjson replacement(*_allocator); - replacement.resetTo(jsonType::jsonArray); - pjson& result = replacement[index]; - pjson* resultPtr = &result; - swap(replacement); - return *resultPtr; - } - PJSONARRAY& array = *_uValue._pValueArray; - size_t position = 0; - if (index < 0) { - // Negative indexes count from the end: -1 is the last element. - const size_t fromEnd = static_cast(-(index + 1)) + size_t(1); - position = fromEnd > array.size() ? size_t(0) : array.size() - fromEnd; - } else { - position = static_cast(index); - } - - if (position >= array.size()) { - static const size_t kMaxAutoGrowth = size_t(1000000); - const size_t growth = position - array.size() + size_t(1); - if (growth > kMaxAutoGrowth) - throw std::length_error("pjson array auto-growth exceeds safety limit"); - if (position == std::numeric_limits::max() || - position + size_t(1) > array.max_size()) - throw std::length_error("pjson array index exceeds maximum size"); - const size_t requiredSize = position + size_t(1); - const size_t originalSize = array.size(); - try { - array.reserve(requiredSize); - while (array.size() < requiredSize) { - unique_ptr child = pjsonImpl::_makeNode(*_allocator); - array.push_back(nullptr); - array.back() = child.release(); - } - } catch (...) { - while (array.size() > originalSize) { - unique_ptr rollback(array.back()); - array.pop_back(); - } - throw; - } - } - - return *array[position]; -} -pjson& pjson::operator[](const std::string& aString) { - return (*this)[aString.c_str()]; -} -pjson* pjson::find(const std::string& aKey) { - return find(aKey.c_str()); -} -// Finds an object member without inserting or changing the receiver. -pjson* pjson::find(const char* aKey) { - if (aKey != nullptr && _eType == jsonType::jsonObject) { - auto it = _uValue._pValueMap->find(aKey); - if (it != _uValue._pValueMap->end()) { - return it->second; - } - } - return nullptr; -} -const pjson* pjson::find(const std::string& aKey) const { - return find(aKey.c_str()); -} -const pjson* pjson::find(const char* aKey) const { - if (aKey != nullptr && _eType == jsonType::jsonObject) { - auto it = _uValue._pValueMap->find(aKey); - if (it != _uValue._pValueMap->end()) { - return it->second; - } - } - return nullptr; -} -pjson* pjson::find(int aIndex) noexcept { - return const_cast(static_cast(this)->find(aIndex)); -} -// Finds an array element with end-relative negative-index support. -const pjson* pjson::find(int aIndex) const noexcept { - if (_eType != jsonType::jsonArray) - return nullptr; - - const PJSONARRAY& values = *_uValue._pValueArray; - size_t position = 0; - if (aIndex >= 0) { - position = static_cast(aIndex); - if (position >= values.size()) - return nullptr; - } else { - const size_t fromEnd = static_cast(-(aIndex + 1)) + size_t(1); - if (fromEnd > values.size()) - return nullptr; - position = values.size() - fromEnd; - } - return values[position]; -} - -//===----------------------------------------------------------------------===// -// RFC 6901 JSON Pointer decoding and traversal -// -// Pointer syntax is decoded once into unescaped tokens, then traversed without -// mutating the DOM. Array tokens must be canonical unsigned decimals: no sign, -// leading zero, or size_t overflow. The '-' token is reserved for Patch add. -//===----------------------------------------------------------------------===// - -namespace { - // Separates malformed decimal syntax from arithmetic overflow so public - // diagnostics can distinguish invalid and merely out-of-range indices. - enum PointerIndexResult { PointerIndexOk, PointerIndexInvalid, PointerIndexOverflow }; - - // Restores a reusable PointerError to its successful neutral state. - void resetPointerError(pjson::PointerError& aError) { - aError.ok = true; - aError.code = pjson::PointerError::Ok; - aError.pointer.clear(); - aError.tokenIndex = 0; - aError.token.clear(); - aError.message.clear(); - } - - // Records the first-class pointer, token location, and failure category. - bool failPointer(pjson::PointerError& aError, pjson::PointerError::Code aCode, - const std::string& aPointer, size_t aTokenIndex, const std::string& aToken, - const char* aMessage) { - aError.ok = false; - aError.code = aCode; - aError.pointer = aPointer; - aError.tokenIndex = aTokenIndex; - aError.token = aToken; - aError.message = aMessage; - return false; - } - - // Parses RFC 6901's canonical array-index subset without overflowing size_t. - PointerIndexResult parsePointerIndex(const std::string& aToken, size_t& aIndex) { - if (aToken.empty() || (aToken.size() > 1 && aToken[0] == '0')) - return PointerIndexInvalid; - - size_t value = 0; - for (size_t i = 0; i < aToken.size(); ++i) { - const unsigned char ch = static_cast(aToken[i]); - if (ch < static_cast('0') || ch > static_cast('9')) - return PointerIndexInvalid; - const size_t digit = static_cast(ch - static_cast('0')); - if (value > (std::numeric_limits::max() - digit) / size_t(10)) - return PointerIndexOverflow; - value = value * size_t(10) + digit; - } - aIndex = value; - return PointerIndexOk; - } - - // Splits a pointer and decodes ~0/~1 escapes. An empty pointer deliberately - // yields no tokens because it identifies the document root. - bool decodePointer(const std::string& aPointer, std::vector& aTokens, - pjson::PointerError& aError) { - resetPointerError(aError); - aTokens.clear(); - if (aPointer.empty()) - return true; - if (aPointer[0] != '/') - return failPointer(aError, pjson::PointerError::InvalidSyntax, aPointer, 0, - std::string(), "JSON Pointer must be empty or begin with '/'"); - - size_t tokenIndex = 0; - size_t tokenStart = 1; - while (true) { - const size_t slash = aPointer.find('/', tokenStart); - const size_t tokenEnd = slash == std::string::npos ? aPointer.size() : slash; - std::string decoded; - decoded.reserve(tokenEnd - tokenStart); - for (size_t i = tokenStart; i < tokenEnd; ++i) { - const char ch = aPointer[i]; - if (ch != '~') { - decoded += ch; - continue; - } - if (i + 1 >= tokenEnd || (aPointer[i + 1] != '0' && aPointer[i + 1] != '1')) { - return failPointer(aError, pjson::PointerError::InvalidEscape, aPointer, - tokenIndex, - aPointer.substr(tokenStart, tokenEnd - tokenStart), - "JSON Pointer token contains an invalid '~' escape"); - } - decoded += aPointer[i + 1] == '0' ? '~' : '/'; - ++i; - } - aTokens.push_back(std::move(decoded)); - if (slash == std::string::npos) - break; - tokenStart = slash + 1; - ++tokenIndex; - } - return true; - } - - // Traverses the first aCount decoded tokens and reports the exact failing - // token. Patch reuses partial traversal to resolve a destination's parent. - const pjson* resolvePointerTokens(const pjson& aRoot, const std::vector& aTokens, - size_t aCount, const std::string& aPointer, - pjson::PointerError& aError) { - const pjson* current = &aRoot; - for (size_t i = 0; i < aCount; ++i) { - const std::string& token = aTokens[i]; - if (current->isObject()) { - const PJSONMAP* object = &pjsonImpl::_object(*current); - PJSONMAP::const_iterator found = object->find(token); - if (found == object->end()) { - failPointer(aError, pjson::PointerError::MissingTarget, aPointer, i, token, - "JSON Pointer object member does not exist"); - return nullptr; - } - current = found->second; - continue; - } - if (current->isArray()) { - if (token == "-") { - failPointer(aError, pjson::PointerError::AppendTokenNotAllowed, aPointer, i, - token, "the '-' token is only valid for JSON Patch add"); - return nullptr; - } - size_t index = 0; - const PointerIndexResult indexResult = parsePointerIndex(token, index); - if (indexResult == PointerIndexInvalid) { - failPointer(aError, pjson::PointerError::InvalidArrayIndex, aPointer, i, token, - "JSON Pointer array index is not canonical decimal"); - return nullptr; - } - const PJSONARRAY* array = &pjsonImpl::_array(*current); - if (indexResult == PointerIndexOverflow || index >= array->size()) { - failPointer(aError, pjson::PointerError::ArrayIndexOutOfRange, aPointer, i, - token, "JSON Pointer array index is out of range"); - return nullptr; - } - current = (*array)[index]; - continue; - } - failPointer(aError, pjson::PointerError::ExpectedContainer, aPointer, i, token, - "JSON Pointer traversal reached a non-container value"); - return nullptr; - } - return current; - } -} // namespace - -/*static*/ -// Encodes one object-key token for insertion into a JSON Pointer path. -std::string pjson::escapePointerToken(const std::string& aToken) { - std::string escaped; - escaped.reserve(aToken.size()); - for (size_t i = 0; i < aToken.size(); ++i) { - if (aToken[i] == '~') - escaped += "~0"; - else if (aToken[i] == '/') - escaped += "~1"; - else - escaped += aToken[i]; - } - return escaped; -} -// Resolves a string pointer without throwing; exceptional failures are mapped -// to PointerError so nullptr always means a diagnosed failure. -const pjson* pjson::findPointer(const std::string& aPointer, PointerError& aError) const { - try { - std::vector tokens; - if (!decodePointer(aPointer, tokens, aError)) - return nullptr; - return resolvePointerTokens(*this, tokens, tokens.size(), aPointer, aError); - } catch (const std::bad_alloc&) { - try { - failPointer(aError, PointerError::AllocationFailure, std::string(), 0, std::string(), - "JSON Pointer ran out of memory"); - } catch (...) { - aError.ok = false; - aError.code = PointerError::AllocationFailure; - } - return nullptr; - } catch (...) { - try { - failPointer(aError, PointerError::InternalError, std::string(), 0, std::string(), - "JSON Pointer failed with an internal exception"); - } catch (...) { - aError.ok = false; - aError.code = PointerError::InternalError; - } - return nullptr; - } -} -// Mutable forwarding overload; traversal semantics remain non-creating. -pjson* pjson::findPointer(const std::string& aPointer, PointerError& aError) { - return const_cast(static_cast(this)->findPointer(aPointer, aError)); -} -// Convenience overload that intentionally discards pointer diagnostics. -const pjson* pjson::findPointer(const std::string& aPointer) const { - PointerError error; - return findPointer(aPointer, error); -} -// Mutable convenience overload that intentionally discards diagnostics. -pjson* pjson::findPointer(const std::string& aPointer) { - return const_cast(static_cast(this)->findPointer(aPointer)); -} -// Null-safe C-string overload; a null pointer is invalid syntax, not the root. -const pjson* pjson::findPointer(const char* aPointer, PointerError& aError) const { - try { - if (aPointer != nullptr) - return findPointer(std::string(aPointer), aError); - resetPointerError(aError); - failPointer(aError, PointerError::InvalidSyntax, std::string(), 0, std::string(), - "JSON Pointer input is null"); - return nullptr; - } catch (const std::bad_alloc&) { - aError.ok = false; - aError.code = PointerError::AllocationFailure; - return nullptr; - } catch (...) { - aError.ok = false; - aError.code = PointerError::InternalError; - return nullptr; - } -} -// Mutable null-safe C-string forwarding overload. -pjson* pjson::findPointer(const char* aPointer, PointerError& aError) { - return const_cast(static_cast(this)->findPointer(aPointer, aError)); -} -// C-string convenience overload that intentionally discards diagnostics. -const pjson* pjson::findPointer(const char* aPointer) const { - PointerError error; - return findPointer(aPointer, error); -} -// Mutable C-string convenience overload that intentionally discards diagnostics. -pjson* pjson::findPointer(const char* aPointer) { - return const_cast(static_cast(this)->findPointer(aPointer)); -} - -//===----------------------------------------------------------------------===// -// RFC 6902 JSON Patch and RFC 7396 Merge Patch helpers -// -// Helpers accept ownership of values through unique_ptr and release only after -// attachment, so failed insertions cannot leak. Public entry points work on a -// full allocator-local clone and swap it into place only after every operation -// succeeds, giving both patch formats document-level atomicity. -//===----------------------------------------------------------------------===// - -namespace { - typedef pjson::PatchError PatchError; - bool failPatch(PatchError& aError, PatchError::Code aCode, const char* aMessage); - - struct PatchBudget { - size_t operations; - size_t nodes; - size_t bytes; - size_t work; - size_t operationLimit; - size_t nodeLimit; - size_t byteLimit; - size_t workLimit; - - explicit PatchBudget(const pjson::PatchOptions& options) - : operations(0) - , nodes(0) - , bytes(0) - , work(0) - , operationLimit(options.maxOperations == 0 ? size_t(10000) : options.maxOperations) - , nodeLimit(options.maxClonedNodes == 0 ? size_t(1000000) : options.maxClonedNodes) - , byteLimit(options.maxClonedBytes == 0 ? size_t(64) * 1024U * 1024U - : options.maxClonedBytes) - , workLimit(options.maxWork == 0 ? size_t(1000000) : options.maxWork) {} - }; - - bool chargePatch(size_t& used, size_t limit, size_t amount, PatchError& error, - const char* message) { - if (amount > limit - std::min(used, limit)) - return failPatch(error, PatchError::ResourceLimit, message); - used += amount; - return true; - } - - bool measureClone(const pjson& value, PatchBudget& budget, PatchError& error) { - std::vector work; - work.push_back(&value); - while (!work.empty()) { - if (!chargePatch(budget.work, budget.workLimit, 1, error, - "JSON patch work budget exceeded") || - !chargePatch(budget.nodes, budget.nodeLimit, 1, error, - "JSON patch cloned-node budget exceeded") || - !chargePatch(budget.bytes, budget.byteLimit, sizeof(pjson), error, - "JSON patch cloned-byte budget exceeded")) - return false; - const pjson* current = work.back(); - work.pop_back(); - if (current->isString()) { - if (!chargePatch(budget.bytes, budget.byteLimit, - pjsonImpl::_string(*current).size(), error, - "JSON patch cloned-byte budget exceeded")) - return false; - } else if (current->isArray()) { - const PJSONARRAY& array = pjsonImpl::_array(*current); - const size_t remainingWork = - budget.workLimit - std::min(budget.work, budget.workLimit); - if (array.size() > remainingWork) - return failPatch(error, PatchError::ResourceLimit, - "JSON patch work budget exceeded"); - work.insert(work.end(), array.begin(), array.end()); - } else if (current->isObject()) { - const PJSONMAP& object = pjsonImpl::_object(*current); - const size_t remainingWork = - budget.workLimit - std::min(budget.work, budget.workLimit); - if (object.size() > remainingWork) - return failPatch(error, PatchError::ResourceLimit, - "JSON patch work budget exceeded"); - for (PJSONMAP::const_iterator it = object.begin(); it != object.end(); ++it) { - if (!chargePatch(budget.bytes, budget.byteLimit, it->first.size(), error, - "JSON patch cloned-byte budget exceeded")) - return false; - work.push_back(it->second); - } - } - } - return true; - } - - // Patch `test` needs bounded structural equality so an adversarial value - // cannot hide unbounded traversal behind a single operation. - bool patchEqual(const pjson& left, const pjson& right, PatchBudget& budget, PatchError& error, - bool& equal) { - struct Pair { - const pjson* left; - const pjson* right; - }; - std::vector pending; - Pair root = {&left, &right}; - pending.push_back(root); - equal = false; - while (!pending.empty()) { - if (!chargePatch(budget.work, budget.workLimit, 1, error, - "JSON Patch work budget exceeded")) - return false; - const Pair current = pending.back(); - pending.pop_back(); - const pjson& lhs = *current.left; - const pjson& rhs = *current.right; - if (lhs.isNumber() && rhs.isNumber()) { - if (pjsonImpl::_compareNumbers(lhs, rhs) != 0) - return true; - continue; - } - if (lhs.getType() != rhs.getType()) - return true; - if (lhs.isString()) { - const std::string& l = pjsonImpl::_string(lhs); - const std::string& r = pjsonImpl::_string(rhs); - if (!chargePatch(budget.work, budget.workLimit, std::max(l.size(), r.size()), error, - "JSON Patch work budget exceeded")) - return false; - if (l != r) - return true; - } else if (lhs.isBool()) { - if (pjsonImpl::_boolean(lhs) != pjsonImpl::_boolean(rhs)) - return true; - } else if (lhs.isArray()) { - const PJSONARRAY& l = pjsonImpl::_array(lhs); - const PJSONARRAY& r = pjsonImpl::_array(rhs); - if (l.size() != r.size()) - return true; - for (size_t i = 0; i < l.size(); ++i) { - Pair child = {l[i], r[i]}; - pending.push_back(child); - } - } else if (lhs.isObject()) { - const PJSONMAP& l = pjsonImpl::_object(lhs); - const PJSONMAP& r = pjsonImpl::_object(rhs); - if (l.size() != r.size()) - return true; - PJSONMAP::const_iterator li = l.begin(); - PJSONMAP::const_iterator ri = r.begin(); - for (; li != l.end(); ++li, ++ri) { - if (!chargePatch(budget.work, budget.workLimit, - std::max(li->first.size(), ri->first.size()) + size_t(1), - error, "JSON Patch work budget exceeded")) - return false; - if (li->first != ri->first) - return true; - Pair child = {li->second, ri->second}; - pending.push_back(child); - } - } - } - equal = true; - return true; - } - - // Restores a reusable PatchError before processing a new patch document. - void resetPatchError(PatchError& aError) { - aError.ok = true; - aError.code = PatchError::Ok; - aError.opIndex = 0; - aError.op.clear(); - aError.path.clear(); - aError.from.clear(); - aError.tokenIndex = 0; - aError.token.clear(); - aError.message.clear(); - } - - // Records a patch failure while preserving operation metadata set by the caller. - bool failPatch(PatchError& aError, PatchError::Code aCode, const char* aMessage) { - aError.ok = false; - aError.code = aCode; - aError.message = aMessage; - return false; - } - - // Records a patch failure associated with one decoded pointer token. - bool failPatchAtToken(PatchError& aError, PatchError::Code aCode, size_t aTokenIndex, - const std::string& aToken, const char* aMessage) { - aError.tokenIndex = aTokenIndex; - aError.token = aToken; - return failPatch(aError, aCode, aMessage); - } - - // Best-effort noexcept diagnostic used while translating allocation or - // unexpected exceptions out of the public patch API. - void failPatchException(PatchError& aError, PatchError::Code aCode, - const char* aMessage) noexcept { - aError.ok = false; - aError.code = aCode; - try { - aError.message = aMessage; - } catch (...) { - aError.message.clear(); - } - } - - // Maps traversal categories into the smaller PatchError vocabulary. - PatchError::Code pointerCodeForPatch(pjson::PointerError::Code aCode) { - switch (aCode) { - case pjson::PointerError::InvalidArrayIndex: - case pjson::PointerError::AppendTokenNotAllowed: - return PatchError::InvalidArrayIndex; - case pjson::PointerError::ArrayIndexOutOfRange: - return PatchError::ArrayIndexOutOfRange; - case pjson::PointerError::MissingTarget: - case pjson::PointerError::ExpectedContainer: - return PatchError::TargetMissing; - case pjson::PointerError::AllocationFailure: - return PatchError::AllocationFailure; - case pjson::PointerError::InternalError: - return PatchError::InternalError; - default: - return PatchError::TargetMissing; - } - } - - // Copies token context from a pointer failure into the active operation error. - bool failPatchFromPointer(PatchError& aError, const pjson::PointerError& aPointerError) { - aError.tokenIndex = aPointerError.tokenIndex; - aError.token = aPointerError.token; - return failPatch(aError, pointerCodeForPatch(aPointerError.code), - aPointerError.message.c_str()); - } - - // Decodes a Patch pointer and classifies syntax failures as path or from errors. - bool decodePatchPointer(const std::string& aPointer, bool bFrom, - std::vector& aTokens, PatchBudget& aBudget, - PatchError& aError) { - if (!chargePatch(aBudget.work, aBudget.workLimit, aPointer.size() + size_t(1), aError, - "JSON patch work budget exceeded")) - return false; - pjson::PointerError pointerError; - if (decodePointer(aPointer, aTokens, pointerError)) - return true; - aError.tokenIndex = pointerError.tokenIndex; - aError.token = pointerError.token; - return failPatch(aError, bFrom ? PatchError::InvalidFrom : PatchError::InvalidPath, - pointerError.message.c_str()); - } - - // Resolves a mutable prefix and translates PointerError into PatchError. - pjson* resolvePatchTokens(pjson& aRoot, const std::vector& aTokens, size_t aCount, - const std::string& aPointer, PatchBudget& aBudget, - PatchError& aError) { - if (!chargePatch(aBudget.work, aBudget.workLimit, aCount + size_t(1), aError, - "JSON patch work budget exceeded")) - return nullptr; - pjson::PointerError pointerError; - const pjson* result = resolvePointerTokens(aRoot, aTokens, aCount, aPointer, pointerError); - if (result == nullptr) { - failPatchFromPointer(aError, pointerError); - return nullptr; - } - return const_cast(result); - } - - // Validates a destination/source array index. '-' denotes exactly size() and - // is accepted only for add, whose insertion range includes the end position. - bool patchArrayIndex(const pjson& aParent, const std::string& aToken, bool bAllowAppend, - size_t aTokenIndex, size_t& aIndex, bool& bAppend, PatchError& aError) { - bAppend = false; - if (aToken == "-") { - if (bAllowAppend) { - bAppend = true; - aIndex = aParent.size(); - return true; - } - return failPatchAtToken(aError, PatchError::InvalidArrayIndex, aTokenIndex, aToken, - "the '-' token is valid only for add destinations"); - } - - const PointerIndexResult result = parsePointerIndex(aToken, aIndex); - if (result == PointerIndexInvalid) - return failPatchAtToken(aError, PatchError::InvalidArrayIndex, aTokenIndex, aToken, - "array index is not canonical decimal"); - const size_t size = aParent.size(); - if (result == PointerIndexOverflow || (bAllowAppend ? aIndex > size : aIndex >= size)) - return failPatchAtToken(aError, PatchError::ArrayIndexOutOfRange, aTokenIndex, aToken, - "array index is out of range"); - return true; - } - - // Consumes an allocator-compatible value and implements Patch add. Existing - // object members are replaced; array insertion shifts following elements. - bool addOwnedAtPointer(pjson& aRoot, const std::vector& aTokens, - const std::string& aPointer, pjson::unique_ptr aValue, - PatchBudget& aBudget, PatchError& aError) { - if (aTokens.empty()) { - aRoot.swap(*aValue); - return true; - } - - const size_t finalIndex = aTokens.size() - 1; - pjson* parent = resolvePatchTokens(aRoot, aTokens, finalIndex, aPointer, aBudget, aError); - if (parent == nullptr) - return false; - const std::string& token = aTokens.back(); - - if (parent->isObject()) { - PJSONMAP* object = &pjsonImpl::_object(*parent); - PJSONMAP::iterator existing = object->find(token); - if (existing != object->end()) { - existing->second->swap(*aValue); - return true; - } - if (!chargePatch(aBudget.bytes, aBudget.byteLimit, token.size(), aError, - "JSON Patch cloned-byte budget exceeded")) - return false; - const std::pair inserted = - object->insert(std::make_pair(token, static_cast(nullptr))); - if (!inserted.second) - return failPatchAtToken(aError, PatchError::InternalError, finalIndex, token, - "failed to insert object member"); - inserted.first->second = aValue.release(); - return true; - } - - if (parent->isArray()) { - size_t index = 0; - bool append = false; - if (!patchArrayIndex(*parent, token, true, finalIndex, index, append, aError)) - return false; - PJSONARRAY* array = &pjsonImpl::_array(*parent); - if (!chargePatch(aBudget.work, aBudget.workLimit, array->size() - index, aError, - "JSON Patch work budget exceeded")) - return false; - const PJSONARRAY::iterator inserted = - array->insert(array->begin() + static_cast(index), nullptr); - *inserted = aValue.release(); - return true; - } - - return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, - "add destination parent is not a container"); - } - - // Consumes a replacement only after proving the complete target exists. - bool replaceAtPointer(pjson& aRoot, const std::vector& aTokens, - const std::string& aPointer, pjson::unique_ptr aValue, - PatchBudget& aBudget, PatchError& aError) { - if (aTokens.empty()) { - aRoot.swap(*aValue); - return true; - } - - const size_t finalIndex = aTokens.size() - 1; - pjson* parent = resolvePatchTokens(aRoot, aTokens, finalIndex, aPointer, aBudget, aError); - if (parent == nullptr) - return false; - const std::string& token = aTokens.back(); - - if (parent->isObject()) { - PJSONMAP* object = &pjsonImpl::_object(*parent); - PJSONMAP::iterator existing = object->find(token); - if (existing == object->end()) - return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, - "replace target does not exist"); - existing->second->swap(*aValue); - return true; - } - - if (parent->isArray()) { - size_t index = 0; - bool append = false; - if (!patchArrayIndex(*parent, token, false, finalIndex, index, append, aError)) - return false; - pjsonImpl::_array(*parent)[index]->swap(*aValue); - return true; - } - - return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, - "replace target parent is not a container"); - } - - // Detaches a target without destroying it. Removing the document root is - // represented by replacing the still-addressable root value with JSON null. - bool detachAtPointer(pjson& aRoot, const std::vector& aTokens, - const std::string& aPointer, pjson::unique_ptr& aValue, - PatchBudget& aBudget, PatchError& aError) { - if (aTokens.empty()) { - if (!chargePatch(aBudget.nodes, aBudget.nodeLimit, 1, aError, - "JSON Patch cloned-node budget exceeded") || - !chargePatch(aBudget.bytes, aBudget.byteLimit, sizeof(pjson), aError, - "JSON Patch cloned-byte budget exceeded")) - return false; - pjson::unique_ptr replacement = pjsonImpl::_makeNode(aRoot.getAllocator()); - aRoot.swap(*replacement); - aValue = std::move(replacement); - return true; - } - - const size_t finalIndex = aTokens.size() - 1; - pjson* parent = resolvePatchTokens(aRoot, aTokens, finalIndex, aPointer, aBudget, aError); - if (parent == nullptr) - return false; - const std::string& token = aTokens.back(); - - if (parent->isObject()) { - PJSONMAP* object = &pjsonImpl::_object(*parent); - PJSONMAP::iterator existing = object->find(token); - if (existing == object->end()) - return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, - "remove source does not exist"); - aValue.reset(existing->second); - object->erase(existing); - return true; - } - - if (parent->isArray()) { - size_t index = 0; - bool append = false; - if (!patchArrayIndex(*parent, token, false, finalIndex, index, append, aError)) - return false; - PJSONARRAY* array = &pjsonImpl::_array(*parent); - if (!chargePatch(aBudget.work, aBudget.workLimit, array->size() - index - size_t(1), - aError, "JSON Patch work budget exceeded")) - return false; - aValue.reset((*array)[index]); - array->erase(array->begin() + static_cast(index)); - return true; - } - - return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, - "remove source parent is not a container"); - } - - // Compares decoded paths so alternate escape spellings cannot affect identity. - bool samePointerTokens(const std::vector& aLeft, - const std::vector& aRight) { - return aLeft == aRight; - } - - // Detects a move into the source's own descendant, which would invalidate - // the destination during detachment and is forbidden by JSON Patch. - bool isProperPointerAncestor(const std::vector& aAncestor, - const std::vector& aDescendant) { - return aAncestor.size() < aDescendant.size() && - std::equal(aAncestor.begin(), aAncestor.end(), aDescendant.begin()); - } - - // Replaces or adopts an allocator-compatible object child without exposing - // a null map entry if insertion fails. - bool insertObjectChild(pjson& aObject, const std::string& aKey, pjson::unique_ptr aChild) { - PJSONMAP* object = &pjsonImpl::_object(aObject); - PJSONMAP::iterator existing = object->find(aKey); - if (existing != object->end()) { - existing->second->swap(*aChild); - return true; - } - const std::pair inserted = - object->insert(std::make_pair(aKey, static_cast(nullptr))); - if (!inserted.second) - return false; - inserted.first->second = aChild.release(); - return true; - } - - // Applies Merge Patch iteratively to a private working document. Object - // patches recurse, null members delete, and every other value replaces via - // an allocator-local clone. Atomic publication is handled by the caller. - bool applyMergePatchTo(pjson& aTarget, const pjson& aPatch, PatchBudget& aBudget, - PatchError& aError) { - struct MergeItem { - // target and patch describe one pending object/object merge. - pjson* target; - const pjson* patch; - }; - - if (!aPatch.isObject()) { - if (!chargePatch(aBudget.operations, aBudget.operationLimit, 1, aError, - "JSON Merge Patch operation budget exceeded")) - return false; - if (!measureClone(aPatch, aBudget, aError)) - return false; - pjson replacement(aPatch, aTarget.getAllocator()); - aTarget.swap(replacement); - return true; - } - - std::vector work; - MergeItem root = {&aTarget, &aPatch}; - work.push_back(root); - while (!work.empty()) { - const MergeItem item = work.back(); - work.pop_back(); - if (!item.target->isObject()) - item.target->resetTo(pjson::jsonObject); - - const PJSONMAP* patchObject = &pjsonImpl::_object(*item.patch); - for (PJSONMAP::const_iterator it = patchObject->begin(); it != patchObject->end(); - ++it) { - if (!chargePatch(aBudget.operations, aBudget.operationLimit, 1, aError, - "JSON Merge Patch operation budget exceeded") || - !chargePatch(aBudget.work, aBudget.workLimit, 1, aError, - "JSON Merge Patch work budget exceeded")) - return false; - const std::string& key = it->first; - const pjson& patchValue = *it->second; - if (!chargePatch(aBudget.bytes, aBudget.byteLimit, key.size(), aError, - "JSON Merge Patch cloned-byte budget exceeded")) - return false; - if (patchValue.isNull()) { - item.target->erase(key); - continue; - } - - pjson* targetValue = item.target->find(key); - if (patchValue.isObject()) { - if (targetValue == nullptr) { - if (!chargePatch(aBudget.nodes, aBudget.nodeLimit, 1, aError, - "JSON Merge Patch cloned-node budget exceeded") || - !chargePatch(aBudget.bytes, aBudget.byteLimit, sizeof(pjson), aError, - "JSON Merge Patch cloned-byte budget exceeded")) - return false; - pjson::unique_ptr child = pjsonImpl::_makeNode(item.target->getAllocator()); - child->resetTo(pjson::jsonObject); - targetValue = child.get(); - if (!insertObjectChild(*item.target, key, std::move(child))) - return false; - } else if (!targetValue->isObject()) { - targetValue->resetTo(pjson::jsonObject); - } - MergeItem childItem = {targetValue, &patchValue}; - work.push_back(childItem); - continue; - } - - if (!measureClone(patchValue, aBudget, aError)) - return false; - pjson::unique_ptr replacement = - pjsonImpl::_cloneNode(patchValue, item.target->getAllocator()); - if (!insertObjectChild(*item.target, key, std::move(replacement))) - return false; - } - } - return true; - } -} // namespace - -// Key/index extraction overloads combine non-mutating lookup with exact -// tryGet conversion and leave output parameters unchanged on any miss. -bool pjson::tryGet(const std::string& aKey, int64_t& aResult) const { - return tryGet(aKey.c_str(), aResult); -} -bool pjson::tryGet(const std::string& aKey, double& aResult) const { - return tryGet(aKey.c_str(), aResult); -} -bool pjson::tryGet(const std::string& aKey, bool& aResult) const { - return tryGet(aKey.c_str(), aResult); -} -bool pjson::tryGet(const std::string& aKey, std::string& aResult) const { - return tryGet(aKey.c_str(), aResult); -} -bool pjson::tryGet(const std::string& aKey, StringView& aResult) const { - return tryGet(aKey.c_str(), aResult); -} -bool pjson::tryGet(const char* aKey, int64_t& aResult) const { - const pjson* value = find(aKey); - return value != nullptr && value->tryGet(aResult); -} -bool pjson::tryGet(const char* aKey, double& aResult) const { - const pjson* value = find(aKey); - return value != nullptr && value->tryGet(aResult); -} -bool pjson::tryGet(const char* aKey, bool& aResult) const { - const pjson* value = find(aKey); - return value != nullptr && value->tryGet(aResult); -} -bool pjson::tryGet(const char* aKey, std::string& aResult) const { - const pjson* value = find(aKey); - return value != nullptr && value->tryGet(aResult); -} -bool pjson::tryGet(const char* aKey, StringView& aResult) const { - const pjson* value = find(aKey); - return value != nullptr && value->tryGet(aResult); -} -bool pjson::tryGet(int aIndex, int64_t& aResult) const noexcept { - const pjson* value = find(aIndex); - return value != nullptr && value->tryGet(aResult); -} -bool pjson::tryGet(int aIndex, double& aResult) const noexcept { - const pjson* value = find(aIndex); - return value != nullptr && value->tryGet(aResult); -} -bool pjson::tryGet(int aIndex, bool& aResult) const noexcept { - const pjson* value = find(aIndex); - return value != nullptr && value->tryGet(aResult); -} -bool pjson::tryGet(int aIndex, std::string& aResult) const { - const pjson* value = find(aIndex); - return value != nullptr && value->tryGet(aResult); -} -bool pjson::tryGet(int aIndex, StringView& aResult) const noexcept { - const pjson* value = find(aIndex); - return value != nullptr && value->tryGet(aResult); -} - -//===----------------------------------------------------------------------===// -// Public DOM and SAX parse API families -// -// Overloads differ only in input source and diagnostics. Every DOM parse returns -// the origin-aware pjson::unique_ptr, including roots from the default allocator. -//===----------------------------------------------------------------------===// - -/*static*/ -// Parses string-owned bytes with default allocation and omitted diagnostics. -pjson::unique_ptr pjson::parse(const std::string& aStr, const ParseOptions& aOpts) { - return pjsonImpl::_parseTop(aStr.c_str(), aStr.length(), aOpts, nullptr, - pjsonImpl::_defaultAllocator()); -} -/*static*/ -// Parses an explicit byte span with default allocation and omitted diagnostics. -pjson::unique_ptr pjson::parse(const char* aSrc, size_t aSize, const ParseOptions& aOpts) { - return pjsonImpl::_parseTop(aSrc, aSize, aOpts, nullptr, pjsonImpl::_defaultAllocator()); -} -/*static*/ -// Emits SAX events from string-owned bytes and discards detailed diagnostics. -bool pjson::parseSax(const std::string& aStr, SaxHandler& aHandler, const ParseOptions& aOpts) { - return pjsonImpl::_parseSaxTop(aStr.c_str(), aStr.length(), aHandler, aOpts, nullptr); -} -/*static*/ -// Emits SAX events from an explicit byte span and discards detailed diagnostics. -bool pjson::parseSax(const char* aSrc, size_t aSize, SaxHandler& aHandler, - const ParseOptions& aOpts) { - return pjsonImpl::_parseSaxTop(aSrc, aSize, aHandler, aOpts, nullptr); -} -/*static*/ -// Parses string-owned bytes and fills a caller-visible ParseError. -pjson::unique_ptr pjson::parse(const std::string& aStr, ParseError& aError, - const ParseOptions& aOpts) { - return pjsonImpl::_parseTop(aStr.c_str(), aStr.length(), aOpts, &aError, - pjsonImpl::_defaultAllocator()); -} -/*static*/ -// Emits SAX events from a string and fills a caller-visible ParseError. -bool pjson::parseSax(const std::string& aStr, SaxHandler& aHandler, ParseError& aError, - const ParseOptions& aOpts) { - return pjsonImpl::_parseSaxTop(aStr.c_str(), aStr.length(), aHandler, aOpts, &aError); -} -/*static*/ -// Parses an explicit byte span and fills a caller-visible ParseError. -pjson::unique_ptr pjson::parse(const char* aSrc, size_t aSize, ParseError& aError, - const ParseOptions& aOpts) { - return pjsonImpl::_parseTop(aSrc, aSize, aOpts, &aError, pjsonImpl::_defaultAllocator()); -} -/*static*/ -// Emits SAX events from a byte span and fills a caller-visible ParseError. -bool pjson::parseSax(const char* aSrc, size_t aSize, SaxHandler& aHandler, ParseError& aError, - const ParseOptions& aOpts) { - return pjsonImpl::_parseSaxTop(aSrc, aSize, aHandler, aOpts, &aError); -} -/*static*/ -// Parses a stream with default allocation and omitted diagnostics. -pjson::unique_ptr pjson::parseStream(std::istream& aIn, const ParseOptions& aOpts) { - return pjsonImpl::_parseStream(aIn, aOpts, nullptr, pjsonImpl::_defaultAllocator()); -} -/*static*/ -// Parses a stream with default allocation and caller-visible diagnostics. -pjson::unique_ptr pjson::parseStream(std::istream& aIn, ParseError& aError, - const ParseOptions& aOpts) { - return pjsonImpl::_parseStream(aIn, aOpts, &aError, pjsonImpl::_defaultAllocator()); -} -/*static*/ -// Parses string-owned bytes with nodes and wrapper objects from aAlloc. -pjson::unique_ptr pjson::parse(const std::string& aStr, Allocator& aAlloc, - const ParseOptions& aOpts) { - return pjsonImpl::_parseTop(aStr.c_str(), aStr.length(), aOpts, nullptr, aAlloc); -} -/*static*/ -// Parses a byte span with nodes and wrapper objects from aAlloc. -pjson::unique_ptr pjson::parse(const char* aSrc, size_t aSize, Allocator& aAlloc, - const ParseOptions& aOpts) { - return pjsonImpl::_parseTop(aSrc, aSize, aOpts, nullptr, aAlloc); -} -/*static*/ -// Parses string-owned bytes with custom allocation and detailed diagnostics. -pjson::unique_ptr pjson::parse(const std::string& aStr, ParseError& aError, Allocator& aAlloc, - const ParseOptions& aOpts) { - return pjsonImpl::_parseTop(aStr.c_str(), aStr.length(), aOpts, &aError, aAlloc); -} -/*static*/ -// Parses a byte span with custom allocation and detailed diagnostics. -pjson::unique_ptr pjson::parse(const char* aSrc, size_t aSize, ParseError& aError, - Allocator& aAlloc, const ParseOptions& aOpts) { - return pjsonImpl::_parseTop(aSrc, aSize, aOpts, &aError, aAlloc); -} -/*static*/ -// Parses a stream with nodes and wrapper objects from aAlloc. -pjson::unique_ptr pjson::parseStream(std::istream& aIn, Allocator& aAlloc, - const ParseOptions& aOpts) { - return pjsonImpl::_parseStream(aIn, aOpts, nullptr, aAlloc); -} -/*static*/ -// Parses a stream with custom allocation and detailed diagnostics. -pjson::unique_ptr pjson::parseStream(std::istream& aIn, ParseError& aError, Allocator& aAlloc, - const ParseOptions& aOpts) { - return pjsonImpl::_parseStream(aIn, aOpts, &aError, aAlloc); -} -/*static*/ -// Emits SAX events directly from a stream and discards detailed diagnostics. -bool pjson::parseSaxStream(std::istream& aIn, SaxHandler& aHandler, const ParseOptions& aOpts) { - return pjsonImpl::_parseSaxStream(aIn, aHandler, aOpts, nullptr); -} -/*static*/ -// Emits SAX events directly from a stream with caller-visible diagnostics. -bool pjson::parseSaxStream(std::istream& aIn, SaxHandler& aHandler, ParseError& aError, - const ParseOptions& aOpts) { - return pjsonImpl::_parseSaxStream(aIn, aHandler, aOpts, &aError); -} -// Reads incrementally so maxInputBytes bounds memory before the complete stream -// has been materialized. -/*static*/ -pjson::unique_ptr pjsonImpl::_parseStream(std::istream& aIn, const ParseOptions& aOpts, - ParseError* aErr, pjson::Allocator& aAlloc) { - std::string content; - char buffer[8192]; - while (aIn.good()) { - aIn.read(buffer, sizeof(buffer)); - const std::streamsize got = aIn.gcount(); - if (got <= 0) - continue; - const size_t chunk = static_cast(got); - if (aOpts.maxInputBytes != 0 && (content.size() > aOpts.maxInputBytes || - chunk > aOpts.maxInputBytes - content.size())) { - // Include as much of this chunk as fits, allowing line/column to be - // calculated at the exact configured byte boundary. - if (content.size() < aOpts.maxInputBytes) { - content.append(buffer, aOpts.maxInputBytes - content.size()); - } - setParseError(aErr, content.data(), content.size(), aOpts.maxInputBytes, - "input exceeds maxInputBytes"); - return pjson::unique_ptr(); - } - content.append(buffer, chunk); + return false; +} +bool pjson::tryGet(double& aResult) const noexcept { + if (_pImpl->_eType == pjson::jsonType::jsonNumberInt) { + aResult = static_cast(_pImpl->_valueInt); + return true; } - if (aIn.bad()) { - setParseError(aErr, content.data(), content.size(), content.size(), "stream read failed"); - return pjson::unique_ptr(); + if (_pImpl->_eType == pjson::jsonType::jsonNumberUInt) { + aResult = static_cast(_pImpl->_valueUInt); + return true; } - return _parseTop(content.c_str(), content.length(), aOpts, aErr, aAlloc); + if (_pImpl->_eType != pjson::jsonType::jsonNumberDouble) + return false; + aResult = _pImpl->_valueDouble; + return true; } -/*static*/ -bool pjsonImpl::_parseSaxTop(const char* aSrc, size_t aSize, SaxHandler& aHandler, - const ParseOptions& aOpts, ParseError* aErr) { - resetParseError(aErr); - if (aSrc == nullptr) { - setParseError(aErr, "", 0, 0, "null input"); +bool pjson::tryGet(bool& aResult) const noexcept { + if (_pImpl->_eType != pjson::jsonType::jsonBoolean) return false; - } - if (aOpts.maxInputBytes != 0 && aSize > aOpts.maxInputBytes) { - setParseError(aErr, aSrc, aSize, aOpts.maxInputBytes, "input exceeds maxInputBytes"); + aResult = _pImpl->_valueBool; + return true; +} +bool pjson::tryGet(std::string& aResult) const { + if (_pImpl->_eType != pjson::jsonType::jsonString) return false; - } - BufferSaxCursor cursor(aSrc, aSize); - SaxParser parser(cursor, aHandler, aOpts, aErr); - return parser.parseDocument(); + aResult = *_pImpl->_pValueString; + return true; } -/*static*/ -bool pjsonImpl::_parseSaxStream(std::istream& aIn, SaxHandler& aHandler, const ParseOptions& aOpts, - ParseError* aErr) { - resetParseError(aErr); - StreamSaxCursor cursor(aIn); - SaxParser parser(cursor, aHandler, aOpts, aErr); - return parser.parseDocument(); +bool pjson::tryGet(StringView& aResult) const noexcept { + if (_pImpl->_eType != pjson::jsonType::jsonString) + return false; + const std::string& value = *_pImpl->_pValueString; + aResult = StringView(value.data(), value.size()); + return true; } +// Resets to the canonical null state, releasing any owned subtree. +void pjson::reset() { + if (pjsonImpl::_isNullImpl(_pImpl)) + return; -//===----------------------------------------------------------------------===// -// DOM recursive-descent parser -// -// The cursor advances only across validated syntax, every materialized value -// consumes the shared node budget, and local unique_ptr guards retain ownership -// until a child is attached. The first grammar error remains authoritative. -//===----------------------------------------------------------------------===// - -// Shared driver: parse a single top-level value, require only trailing -// whitespace, and report success/failure through the optional ParseError. -/*static*/ -pjson::unique_ptr pjsonImpl::_parseTop(const char* aSrc, size_t aSize, const ParseOptions& aOpts, - ParseError* aErr, pjson::Allocator& aAlloc) { - resetParseError(aErr); - if (aSrc == nullptr) { - setParseError(aErr, "", 0, 0, "null input"); - return pjson::unique_ptr(); + switch (_pImpl->_eType) { + case pjson::jsonType::jsonString: + destroyDomObject(*_allocator, _pImpl->_pValueString, Allocator::StringAllocation); + break; + case pjson::jsonType::jsonArray: + pjsonImpl::_disposeChildren(*this); + destroyDomObject(*_allocator, _pImpl->_pValueArray, Allocator::ArrayAllocation); + break; + case pjson::jsonType::jsonObject: + pjsonImpl::_disposeChildren(*this); + destroyDomObject(*_allocator, _pImpl->_pValueMap, Allocator::ObjectAllocation); + break; + default: + break; } - // Reject an over-large input up front (cheap DoS guard before any work). - if (aOpts.maxInputBytes != 0 && aSize > aOpts.maxInputBytes) { - setParseError(aErr, aSrc, aSize, aOpts.maxInputBytes, "input exceeds maxInputBytes"); - return pjson::unique_ptr(); + pjsonImpl* implementation = _pImpl; + _pImpl = &pjsonImpl::_nullImpl(); + pjsonImpl::_destroyImpl(*_allocator, implementation); +} +// Idempotent reset: rebuild as an empty value of aeType only when the node is +// not already that type, so an existing array/object keeps its contents. +void pjson::resetIfNeeded(jsonType aeType) { + if (_pImpl->_eType != aeType) { + resetTo(aeType); } +} +// Rebuilds storage for aeType and initializes its empty/default value. The new +// container/string allocation occurs before teardown to preserve validity on throw. +void pjson::resetTo(pjson::jsonType aeType) { + // Reject forged enum values before allocation or teardown so the strong + // exception guarantee also covers an invalid requested discriminator. + // jsonNumberUInt is the highest-valued tag (see the header enum). + if (aeType < pjson::jsonType::jsonNull || aeType > pjson::jsonType::jsonNumberUInt) + throw std::invalid_argument("invalid pjson::jsonType"); - ParseCtx c; - c.src = aSrc; - c.pos = 0; - c.end = aSize; - c.duplicateKeys = aOpts.duplicateKeys; - c.depth = 0; - c.maxDepth = aOpts.maxDepth > 0 ? aOpts.maxDepth : 1; - c.nodeCount = 0; - c.maxNodes = aOpts.maxNodes; - c.allocator = &aAlloc; - c.failed = false; - c.errPos = 0; + if (aeType == pjson::jsonNull) { + reset(); + return; + } - pjson::unique_ptr result; + // Build a complete replacement before publishing it. This includes both + // the opaque implementation and any wrapper object required by the type. + pjson replacement(*_allocator); + pjsonImpl* implementation = pjsonImpl::_allocateImpl(*_allocator); try { - pjson* parsed = nullptr; - if (!_parseValue(c, parsed)) { - pjsonImpl::_destroyNode(parsed); - setParseError(aErr, aSrc, aSize, c.errPos, c.errMsg.empty() ? "parse error" : c.errMsg); - return pjson::unique_ptr(); - } - result.reset(parsed); - - // A valid document is a single value; only trailing whitespace may follow. - char trailing; - if (_peek(c, trailing)) { - setParseError(aErr, aSrc, aSize, c.pos, "trailing characters after JSON value"); - return pjson::unique_ptr(); + switch (aeType) { + case pjson::jsonType::jsonString: + implementation->_pValueString = + allocateDomObject(*_allocator, Allocator::StringAllocation); + break; + case pjson::jsonType::jsonArray: + implementation->_pValueArray = allocateDomObject( + *_allocator, Allocator::ArrayAllocation); + break; + case pjson::jsonType::jsonObject: + implementation->_pValueMap = allocateDomObject( + *_allocator, Allocator::ObjectAllocation); + break; + case pjson::jsonType::jsonNumberInt: + implementation->_valueInt = 0; + break; + case pjson::jsonType::jsonNumberUInt: + implementation->_valueUInt = 0; + break; + case pjson::jsonType::jsonNumberDouble: + implementation->_valueDouble = 0.0; + break; + case pjson::jsonType::jsonBoolean: + implementation->_valueBool = false; + break; + default: + break; } - return result; - } catch (const std::bad_alloc&) { - setParseError(aErr, aSrc, aSize, c.pos, "parse ran out of memory"); - } catch (const std::exception& ex) { - setParseError(aErr, aSrc, aSize, c.pos, - std::string("parse failed with exception: ") + ex.what()); } catch (...) { - setParseError(aErr, aSrc, aSize, c.pos, "parse failed with exception"); - } - return pjson::unique_ptr(); -} -// Skips whitespace and reports the next character without consuming it. -/*static*/ -bool pjsonImpl::_peek(ParseCtx& c, char& aOut) { - while (c.pos < c.end) { - aOut = c.src[c.pos]; - if (_isWhitespace(aOut)) { - ++c.pos; - } else { - return true; - } - } - return false; -} -// Consumes the ':' separating an object key from its value (skipping ws). -/*static*/ -bool pjsonImpl::_skipColon(ParseCtx& c) { - while (c.pos < c.end) { - char ch = c.src[c.pos++]; - if (ch == ':') { - return true; - } else if (_isWhitespace(ch)) { - // ignore - } else { - return _fail(c, c.pos - 1, "expected ':' after object key"); - } + pjsonImpl::_destroyImpl(*_allocator, implementation); + throw; } - return _fail(c, c.pos, "expected ':' after object key"); + implementation->_eType = aeType; + replacement._pImpl = implementation; + pjsonImpl::_swapStorage(*this, replacement); } -// Dispatches on the next non-whitespace character to the right sub-parser. -/*static*/ -bool pjsonImpl::_parseValue(ParseCtx& c, pjson*& aOut) { - char ch; - if (!_peek(c, ch)) { - return _fail(c, c.pos, "unexpected end of input; expected a value"); - } - if (ch == '\"') { - return _parseString(c, aOut); - } else if (ch == '{') { - return _parseObject(c, aOut); - } else if (ch == '[') { - return _parseArray(c, aOut); - } else if (ch == '-' || (ch >= '0' && ch <= '9')) { - return _parseNumber(c, aOut); - } else { - // RFC 8259 null / true / false literals. - return _parseKeyword(c, aOut); - } +// Replaces this value with a deep copy allocated in this value's allocator. +// Building the replacement first gives the operation a strong guarantee. +void pjson::copyFrom(const pjson& aFrom) { + if (this == &aFrom) + return; + pjson replacement(aFrom, *_allocator); + pjsonImpl::_swapStorage(*this, replacement); } -// Matches a keyword literal using the exact lowercase RFC spelling. +// Populates aDst from aFrom without recursion. If copying fails, partial +// descendants are reclaimed and aDst is reset to a valid null state. /*static*/ -bool pjsonImpl::_parseKeyword(ParseCtx& c, pjson*& aOut) { - struct KW { - const char* word; - size_t len; - int kind; - }; // kind: 0 null,1 true,2 false - static const KW kws[] = { - {"null", 4, 0}, - {"true", 4, 1}, - {"false", 5, 2}, - }; - for (const KW& kw : kws) { - if (c.pos + kw.len > c.end) - continue; - bool match = true; - for (size_t k = 0; k < kw.len; ++k) { - char a = c.src[c.pos + k]; - char b = kw.word[k]; - if (a != b) { - match = false; - break; +void pjsonImpl::_copyContentsInto(pjson& aDst, const pjson& aFrom) { + // Iterative deep copy. A recursive copy would overflow the stack on very + // deep documents, so we walk with an explicit work-list: each item pairs a + // source node with the destination node to populate from it. Scalars are + // copied immediately; array/map children are queued. + try { + aDst.resetTo(aFrom.getType()); + if (aDst._pImpl->_eType != pjson::jsonType::jsonArray && + aDst._pImpl->_eType != pjson::jsonType::jsonObject) { + switch (aDst._pImpl->_eType) { + case pjson::jsonType::jsonString: + *aDst._pImpl->_pValueString = *(aFrom._pImpl->_pValueString); + break; + case pjson::jsonType::jsonNumberInt: + aDst._pImpl->_valueInt = aFrom._pImpl->_valueInt; + break; + case pjson::jsonType::jsonNumberUInt: + aDst._pImpl->_valueUInt = aFrom._pImpl->_valueUInt; + break; + case pjson::jsonType::jsonNumberDouble: + aDst._pImpl->_valueDouble = aFrom._pImpl->_valueDouble; + break; + case pjson::jsonType::jsonBoolean: + aDst._pImpl->_valueBool = aFrom._pImpl->_valueBool; + break; + default: + break; // null: nothing to copy } + return; } - if (match) { - c.pos += kw.len; - pjson::unique_ptr value(_newNode(c)); - if (!value) - return false; - if (kw.kind == 1) - *value = true; - else if (kw.kind == 2) - *value = false; - // kind 0 leaves it as null - aOut = value.release(); - return true; - } - } - return _fail(c, c.pos, "invalid JSON value"); -} -// Reads a quoted string body starting at the opening '"'. -/*static*/ -bool pjsonImpl::_extractString(ParseCtx& c, std::string& aOut) { - if (c.pos >= c.end || c.src[c.pos] != '\"') { - return _fail(c, c.pos, "expected '\"' to start a string"); - } - ++c.pos; // consume opening quote - return pjsonImpl::_decodeStringBody(c, aOut, /*bStopAtQuote=*/true); -} -/*static*/ -// Parses and allocates one string value after decoding its complete token. -bool pjsonImpl::_parseString(ParseCtx& c, pjson*& aOut) { - std::string s; - if (!_extractString(c, s)) { - return false; - } - pjson::unique_ptr value(_newNode(c)); - if (!value) - return false; - *value = s; - aOut = value.release(); - return true; -} -// Parses a JSON number following the grammar -// -?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)? -// Integers are stored as int64; anything with a fraction/exponent (or an -// integer that overflows int64) is stored as a double. Overflow to a -// non-finite double is rejected. Never throws. -/*static*/ -bool pjsonImpl::_parseNumber(ParseCtx& c, pjson*& aOut) { - const size_t begin = c.pos; - size_t i = c.pos; - bool bFloat = false; - - if (i < c.end && c.src[i] == '-') - ++i; - - // integer part: 0 or [1-9][0-9]* - if (i < c.end && c.src[i] == '0') { - ++i; - } else if (i < c.end && c.src[i] >= '1' && c.src[i] <= '9') { - while (i < c.end && c.src[i] >= '0' && c.src[i] <= '9') - ++i; - } else { - return _fail(c, i, "invalid number: expected digit"); - } - // fractional part - if (i < c.end && c.src[i] == '.') { - bFloat = true; - ++i; - if (!(i < c.end && c.src[i] >= '0' && c.src[i] <= '9')) { - return _fail(c, i, "invalid number: '.' must be followed by a digit"); - } - while (i < c.end && c.src[i] >= '0' && c.src[i] <= '9') - ++i; - } + struct Item { + const pjson* src; + pjson* dst; + }; + std::vector work; + Item start = {&aFrom, &aDst}; + work.push_back(start); - // exponent part - if (i < c.end && (c.src[i] == 'e' || c.src[i] == 'E')) { - bFloat = true; - ++i; - if (i < c.end && (c.src[i] == '+' || c.src[i] == '-')) - ++i; - if (!(i < c.end && c.src[i] >= '0' && c.src[i] <= '9')) { - return _fail(c, i, "invalid number: exponent must have a digit"); - } - while (i < c.end && c.src[i] >= '0' && c.src[i] <= '9') - ++i; - } + while (!work.empty()) { + Item cur = work.back(); + work.pop_back(); + const pjson& src = *cur.src; + pjson& dst = *cur.dst; - std::string sTemp(c.src + begin, i - begin); - if (bFloat) { - double d = 0.0; - if (!_parseDouble(sTemp, d) || !std::isfinite(d)) { - return _fail(c, begin, "number out of range"); - } - pjson::unique_ptr value(_newNode(c)); - if (!value) - return false; - *value = d; - aOut = value.release(); - } else { - errno = 0; - long long llVal = strtoll(sTemp.c_str(), nullptr, 10); - if (errno == ERANGE) { - // Too large for int64: fall back to double to avoid data loss. - double d = 0.0; - if (!_parseDouble(sTemp, d) || !std::isfinite(d)) { - return _fail(c, begin, "number out of range"); + // dst has already been resetTo(src type) by the parent (or caller). + if (src._pImpl->_eType == pjson::jsonType::jsonArray) { + dst._pImpl->_pValueArray->reserve(src._pImpl->_pValueArray->size()); + for (const pjson* elem : *src._pImpl->_pValueArray) { + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(*dst._allocator); + child->resetTo(elem->getType()); + dst._pImpl->_pValueArray->push_back(child.get()); + pjson* attached = child.release(); + if (elem->_pImpl->_eType == pjson::jsonType::jsonArray || + elem->_pImpl->_eType == pjson::jsonType::jsonObject) { + Item it = {elem, attached}; + work.push_back(it); + } else { + pjsonImpl::_copyContentsInto(*attached, *elem); + } + } + } else { // jsonObject + for (const auto& kv : *src._pImpl->_pValueMap) { + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(*dst._allocator); + child->resetTo(kv.second->getType()); + const std::pair inserted = + dst._pImpl->_pValueMap->insert( + std::make_pair(kv.first, static_cast(nullptr))); + if (!inserted.second) + throw std::logic_error("duplicate key while copying pjson object"); + pjson* attached = child.release(); + inserted.first->second = attached; + if (kv.second->_pImpl->_eType == pjson::jsonType::jsonArray || + kv.second->_pImpl->_eType == pjson::jsonType::jsonObject) { + Item it = {kv.second, attached}; + work.push_back(it); + } else { + pjsonImpl::_copyContentsInto(*attached, *kv.second); + } + } } - pjson::unique_ptr value(_newNode(c)); - if (!value) - return false; - *value = d; - aOut = value.release(); - } else { - pjson::unique_ptr value(_newNode(c)); - if (!value) - return false; - *value = static_cast(llVal); - aOut = value.release(); } + } catch (...) { + aDst.reset(); + throw; } - c.pos = i; - return true; } -// Parses one array under a balanced depth charge. A child remains RAII-owned -// until vector growth succeeds, preventing leaks on allocation failure. +// Frees every descendant of node iteratively, so tearing down a very deep +// tree cannot overflow the call stack (as the recursive destructor would). +// Leaves node's own top-level array/map allocated but empty. /*static*/ -bool pjsonImpl::_parseArray(ParseCtx& c, pjson*& aOut) { - if (++c.depth > c.maxDepth) { - --c.depth; - return _fail(c, c.pos, "maximum nesting depth exceeded"); - } - pjson::unique_ptr arr(_newNode(c)); - if (!arr) { - --c.depth; - return false; +void pjsonImpl::_disposeChildren(pjson& node) noexcept { + if (node._pImpl->_eType != pjson::jsonType::jsonArray && + node._pImpl->_eType != pjson::jsonType::jsonObject) { + return; } - arr->resetTo(jsonType::jsonArray); - ++c.pos; // consume '[' - - bool bExpectValue = false; // a comma was seen, a value must follow - bool bAny = false; // at least one value parsed - char ch; - while (_peek(c, ch)) { - if (ch == ']') { - if (bExpectValue) { - --c.depth; - return _fail(c, c.pos, "trailing comma in array"); - } - ++c.pos; - --c.depth; - aOut = arr.release(); - return true; - } else if (ch == ',') { - if (!bAny || bExpectValue) { - --c.depth; - return _fail(c, c.pos, "unexpected ',' in array"); - } - ++c.pos; - bExpectValue = true; - } else { - if (bAny && !bExpectValue) { - --c.depth; - return _fail(c, c.pos, "missing ',' between array elements"); + // Use an intrusive pending list so teardown never allocates and therefore + // remains noexcept even for very deep trees or an exhausted heap. + pjson* pending = nullptr; + if (node._pImpl->_eType == pjson::jsonType::jsonArray) { + for (pjson* c : *node._pImpl->_pValueArray) { + if (pjsonImpl::_isNullImpl(c->_pImpl)) { + pjsonImpl::_destroyNode(c); + } else { + c->_pImpl->_disposeNext = pending; + pending = c; } - pjson* elem = nullptr; - if (!_parseValue(c, elem)) { - pjsonImpl::_destroyNode(elem); - --c.depth; - return false; + } + node._pImpl->_pValueArray->clear(); + } else { + for (const auto& kv : *node._pImpl->_pValueMap) { + if (pjsonImpl::_isNullImpl(kv.second->_pImpl)) { + pjsonImpl::_destroyNode(kv.second); + } else { + kv.second->_pImpl->_disposeNext = pending; + pending = kv.second; } - pjson::unique_ptr ownedElem(elem); - arr->_uValue._pValueArray->push_back(nullptr); - arr->_uValue._pValueArray->back() = ownedElem.release(); - bAny = true; - bExpectValue = false; } + node._pImpl->_pValueMap->clear(); } - --c.depth; - return _fail(c, c.pos, "unterminated array"); -} -// Parses one object under a balanced depth charge and applies duplicate policy -// only after the replacement value is fully parsed and owned. -/*static*/ -bool pjsonImpl::_parseObject(ParseCtx& c, pjson*& aOut) { - if (++c.depth > c.maxDepth) { - --c.depth; - return _fail(c, c.pos, "maximum nesting depth exceeded"); - } - pjson::unique_ptr obj(_newNode(c)); - if (!obj) { - --c.depth; - return false; - } - obj->resetTo(jsonType::jsonObject); - ++c.pos; // consume '{' - bool bExpectMember = false; // a comma was seen, a member must follow - bool bAny = false; // at least one member parsed - char ch; - while (_peek(c, ch)) { - if (ch == '}') { - if (bExpectMember) { - --c.depth; - return _fail(c, c.pos, "trailing comma in object"); - } - ++c.pos; - --c.depth; - aOut = obj.release(); - return true; - } else if (ch == ',') { - if (!bAny || bExpectMember) { - --c.depth; - return _fail(c, c.pos, "unexpected ',' in object"); - } - ++c.pos; - bExpectMember = true; - } else if (ch == '\"') { - if (bAny && !bExpectMember) { - --c.depth; - return _fail(c, c.pos, "missing ',' between object members"); - } - const size_t keyOffset = c.pos; - std::string mkey; - pjson* val = nullptr; - if (!_extractString(c, mkey) || !_skipColon(c) || !_parseValue(c, val)) { - pjsonImpl::_destroyNode(val); - --c.depth; - return false; - } - // Apply the caller's duplicate-key policy: reject the duplicate or - // deterministically keep its first or last value. - auto it = obj->_uValue._pValueMap->find(mkey); - if (it != obj->_uValue._pValueMap->end()) { - if (c.duplicateKeys == ParseOptions::RejectDuplicateKeys) { - pjsonImpl::_destroyNode(val); - --c.depth; - return _fail(c, keyOffset, "duplicate object key"); + while (pending != nullptr) { + pjson* p = pending; + pending = p->_pImpl->_disposeNext; + p->_pImpl->_disposeNext = nullptr; + // Move this node's children into the work-list, then detach so its own + // destructor has nothing left to recurse into. + if (p->_pImpl->_eType == pjson::jsonType::jsonArray) { + for (pjson* c : *p->_pImpl->_pValueArray) { + if (pjsonImpl::_isNullImpl(c->_pImpl)) { + pjsonImpl::_destroyNode(c); + } else { + c->_pImpl->_disposeNext = pending; + pending = c; } - if (c.duplicateKeys == ParseOptions::KeepLastDuplicate) { - pjsonImpl::_destroyNode(it->second); - it->second = val; + } + p->_pImpl->_pValueArray->clear(); + } else if (p->_pImpl->_eType == pjson::jsonType::jsonObject) { + for (const auto& kv : *p->_pImpl->_pValueMap) { + if (pjsonImpl::_isNullImpl(kv.second->_pImpl)) { + pjsonImpl::_destroyNode(kv.second); } else { - pjsonImpl::_destroyNode(val); // KeepFirstDuplicate + kv.second->_pImpl->_disposeNext = pending; + pending = kv.second; } - } else { - pjson::unique_ptr ownedVal(val); - pjson*& slot = (*(obj->_uValue._pValueMap))[mkey]; - slot = ownedVal.release(); } - bAny = true; - bExpectMember = false; - } else { - --c.depth; - return _fail(c, c.pos, "expected '\"' to start an object key"); + p->_pImpl->_pValueMap->clear(); } + pjsonImpl::_destroyNode(p); // now a leaf (or emptied container) + } +} +// Returns the length (1..4) of the valid UTF-8 sequence starting at +// src[pos], or 0 if the bytes there are not valid UTF-8. Requires pos < end. +int pjsonImpl::_utf8Len(const char* src, size_t pos, size_t end) { + unsigned char c0 = static_cast(src[pos]); + int n; + uint32_t cp; + uint32_t lo; + if (c0 < 0x80) + return 1; + else if ((c0 & 0xE0) == 0xC0) { + n = 2; + cp = c0 & 0x1F; + lo = 0x80; + } else if ((c0 & 0xF0) == 0xE0) { + n = 3; + cp = c0 & 0x0F; + lo = 0x800; + } else if ((c0 & 0xF8) == 0xF0) { + n = 4; + cp = c0 & 0x07; + lo = 0x10000; + } else + return 0; // stray continuation / invalid lead + if (pos + static_cast(n) > end) + return 0; + for (size_t k = 1; k < static_cast(n); ++k) { + unsigned char ck = static_cast(src[pos + k]); + if ((ck & 0xC0) != 0x80) + return 0; // not a continuation byte + cp = (cp << 6) | (ck & 0x3F); } - --c.depth; - return _fail(c, c.pos, "unterminated object"); + if (cp < lo) + return 0; // overlong encoding + if (cp > 0x10FFFF) + return 0; // beyond Unicode + if (cp >= 0xD800 && cp <= 0xDFFF) + return 0; // surrogate half in UTF-8 + return n; +} + +/*static*/ +// Allocates a null node under the supplied allocator and wraps origin-aware cleanup. +pjsonImpl::OwnedNode pjsonImpl::_makeNode(pjson::Allocator& aAlloc) { + return pjsonImpl::OwnedNode(pjsonImpl::_allocateNode(aAlloc)); +} +/*static*/ +// Deep-clones a complete subtree into the supplied allocator domain. +pjsonImpl::OwnedNode pjsonImpl::_cloneNode(const pjson& aValue, pjson::Allocator& aAlloc) { + pjsonImpl::OwnedNode result = _makeNode(aAlloc); + _copyContentsInto(*result, aValue); + return result; } //===----------------------------------------------------------------------===// -// Container queries and mutation +// Scalar assignment and vector-backed array mutation +// +// Scalar assignments reuse compatible storage. Array helpers allocate children +// under RAII and publish raw pointers only after container insertion succeeds. +// Multi-element mutation either swaps a complete replacement or rolls back to +// the original size, so allocation failures never leave a partial append. //===----------------------------------------------------------------------===// -bool pjson::hasKey(const std::string& aKey) const { - return hasKey(aKey.c_str()); +// Replaces the current value with a copied JSON string. +pjson& pjson::operator=(const std::string& aString) { + resetIfNeeded(pjson::jsonType::jsonString); + *_pImpl->_pValueString = aString; + return *this; } -// Reports whether an object contains a non-null C-string key. -bool pjson::hasKey(const char* cStr) const { - if (cStr != nullptr && _eType == jsonType::jsonObject) { - auto it = _uValue._pValueMap->find(cStr); - return (it != _uValue._pValueMap->end()); - } - return false; +// Replaces the current value with the null-terminated string's bytes. +pjson& pjson::operator=(const char* aCString) { + if (aCString == nullptr) + throw std::invalid_argument("pjson string assignment requires non-null input"); + resetIfNeeded(pjson::jsonType::jsonString); + *_pImpl->_pValueString = aCString; + return *this; } -// Reports whether an array index resolves under find()'s negative-index rules. -bool pjson::hasIndex(int aIndex) const noexcept { - return find(aIndex) != nullptr; +// Replaces the current value with a JSON boolean. +pjson& pjson::operator=(const bool aBool) { + resetIfNeeded(pjson::jsonType::jsonBoolean); + _pImpl->_valueBool = aBool; + return *this; } -// Returns the member/element count for containers and zero for scalars. -size_t pjson::size() const { - if (_eType == jsonType::jsonArray) { - return _uValue._pValueArray->size(); - } - if (_eType == jsonType::jsonObject) { - return _uValue._pValueMap->size(); - } - return 0; +// Native integer overloads keep ordinary literals unambiguous while routing +// storage through the exact-width numeric implementation. +pjson& pjson::operator=(const int aInt) { + operator=(static_cast(aInt)); + return *this; } -// Reports whether size() is zero, so every scalar is considered empty. -bool pjson::empty() const { - return size() == 0; +pjson& pjson::operator=(const unsigned int aUInt) { + operator=(static_cast(aUInt)); + return *this; } -// Clears container children without changing container type; scalars become null. -void pjson::clear() { - // Arrays and maps become empty containers of the same type; anything else - // resets to null. - switch (_eType) { - case jsonType::jsonArray: { - pjsonImpl::_disposeChildren(*this); - _uValue._pValueArray->clear(); - break; - } - case jsonType::jsonObject: { - pjsonImpl::_disposeChildren(*this); - _uValue._pValueMap->clear(); - break; - } - default: - reset(); - break; - } +pjson& pjson::operator=(const short aInt) { + operator=(static_cast(aInt)); + return *this; } -// Returns object keys in the map's deterministic sorted iteration order. -std::vector pjson::keys() const { - std::vector result; - if (_eType == jsonType::jsonObject) { - result.reserve(_uValue._pValueMap->size()); - for (const auto& kv : *_uValue._pValueMap) { - result.push_back(kv.first); - } - } - return result; +pjson& pjson::operator=(const unsigned short aUInt) { + operator=(static_cast(aUInt)); + return *this; } -bool pjson::erase(const std::string& aKey) { - return erase(aKey.c_str()); +// Wider native integer overloads cover both LP64 and LLP64 without relying on +// the platform-specific fundamental type selected by int64_t/uint64_t. +pjson& pjson::operator=(const long aInt) { + resetIfNeeded(pjson::jsonType::jsonNumberInt); + _pImpl->_valueInt = static_cast(aInt); + return *this; } -// Removes an object member and destroys its owned subtree. -bool pjson::erase(const char* aKey) { - if (aKey != nullptr && _eType == jsonType::jsonObject) { - auto it = _uValue._pValueMap->find(aKey); - if (it != _uValue._pValueMap->end()) { - pjsonImpl::_destroyNode(it->second); - _uValue._pValueMap->erase(it); - return true; - } - } - return false; +pjson& pjson::operator=(const unsigned long aUInt) { + resetIfNeeded(pjson::jsonType::jsonNumberUInt); + _pImpl->_valueUInt = static_cast(aUInt); + return *this; } -// Removes an array element and destroys its owned subtree, shifting later indices. -bool pjson::erase(size_t aIndex) { - if (_eType == jsonType::jsonArray && aIndex < _uValue._pValueArray->size()) { - pjsonImpl::_destroyNode((*_uValue._pValueArray)[aIndex]); - _uValue._pValueArray->erase(_uValue._pValueArray->begin() + - static_cast(aIndex)); - return true; - } - return false; +pjson& pjson::operator=(const long long aInt) { + resetIfNeeded(pjson::jsonType::jsonNumberInt); + _pImpl->_valueInt = static_cast(aInt); + return *this; +} +pjson& pjson::operator=(const unsigned long long aUInt) { + resetIfNeeded(pjson::jsonType::jsonNumberUInt); + _pImpl->_valueUInt = static_cast(aUInt); + return *this; } -// Applies JSON Patch while intentionally discarding detailed diagnostics. -bool pjson::applyPatch(const pjson& aPatch, const PatchOptions& aOpts) noexcept { - PatchError error; - return applyPatch(aPatch, error, aOpts); +// Float input is widened exactly to pjson's binary64 representation. +pjson& pjson::operator=(const float aFloat) { + operator=(static_cast(aFloat)); + return *this; } -// Applies an RFC 6902 operation sequence atomically. Validation and mutation -// happen on scratch; only a completely successful sequence is published by swap. -bool pjson::applyPatch(const pjson& aPatch, PatchError& aError, - const PatchOptions& aOpts) noexcept { - resetPatchError(aError); - try { - if (!aPatch.isArray()) - return failPatch(aError, PatchError::InvalidPatchDocument, - "JSON Patch document must be an array"); +// Replaces the current value with a JSON double. +pjson& pjson::operator=(const double aDouble) { + resetIfNeeded(pjson::jsonType::jsonNumberDouble); + _pImpl->_valueDouble = aDouble; + return *this; +} +pjson& pjson::operator=(const long double aDouble) { + operator=(static_cast(aDouble)); + return *this; +} +namespace { + // Appends one converted child. A non-array target is promoted atomically by + // building and swapping a replacement; an array target changes only after + // both node construction and vector growth have succeeded. + template void appendDomValue(pjson& aTarget, const Value& aValue) { + if (!aTarget.isArray()) { + pjson replacement(aTarget.getAllocator()); + replacement.resetTo(pjson::jsonArray); + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(aTarget.getAllocator()); + *child = aValue; + pjsonImpl::_array(replacement).push_back(nullptr); + pjsonImpl::_array(replacement).back() = child.release(); + pjsonImpl::_swapStorage(aTarget, replacement); + return; + } - PatchBudget budget(aOpts); - const PJSONARRAY& operations = pjsonImpl::_array(aPatch); - if (!chargePatch(budget.operations, budget.operationLimit, operations.size(), aError, - "JSON Patch operation budget exceeded") || - !measureClone(*this, budget, aError)) - return false; + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(aTarget.getAllocator()); + *child = aValue; + pjsonImpl::_array(aTarget).push_back(nullptr); + pjsonImpl::_array(aTarget).back() = child.release(); + } - // The scratch copy is both the rollback boundary and the allocator domain - // into which every add/copy/replace value must be cloned. - pjson scratch(*this, *_allocator); - for (size_t operationIndex = 0; operationIndex < operations.size(); ++operationIndex) { - const pjson& operation = *operations[operationIndex]; - aError.opIndex = operationIndex; - aError.op.clear(); - aError.path.clear(); - aError.from.clear(); - aError.tokenIndex = 0; - aError.token.clear(); - aError.message.clear(); + // Replaces a target with a fully constructed array, providing a strong guarantee. + template void assignDomArray(pjson& aTarget, const Values& aValues) { + pjson replacement(aTarget.getAllocator()); + replacement.resetTo(pjson::jsonArray); + for (const auto& value : aValues) { + appendDomValue(replacement, value); + } + pjsonImpl::_swapStorage(aTarget, replacement); + } - if (!operation.isObject()) - return failPatch(aError, PatchError::OperationNotObject, - "JSON Patch operation must be an object"); + // Appends a range atomically: newly attached children are reclaimed in + // reverse order if any later conversion or allocation throws. + template void appendDomArray(pjson& aTarget, const Values& aValues) { + if (!aTarget.isArray()) { + pjson replacement(aTarget.getAllocator()); + replacement.resetTo(pjson::jsonArray); + for (const auto& value : aValues) { + appendDomValue(replacement, value); + } + pjsonImpl::_swapStorage(aTarget, replacement); + return; + } - const pjson* opNode = operation.find("op"); - if (opNode == nullptr || !opNode->isString()) - return failPatch(aError, PatchError::MissingOp, - "JSON Patch operation requires string member 'op'"); - aError.op = pjsonImpl::_string(*opNode); + pjsonImpl::ArrayStorage& array = pjsonImpl::_array(aTarget); + const size_t originalSize = array.size(); + try { + for (const auto& value : aValues) { + appendDomValue(aTarget, value); + } + } catch (...) { + while (array.size() > originalSize) { + pjsonImpl::OwnedNode rollback(array.back()); + array.pop_back(); + } + throw; + } + } +} // namespace +// Vector assignment overloads delegate to the same strong-guarantee builder, +// but remain explicit so each supported public type is visible in this source. +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - const bool knownOperation = aError.op == "add" || aError.op == "remove" || - aError.op == "replace" || aError.op == "move" || - aError.op == "copy" || aError.op == "test"; - if (!knownOperation) - return failPatch(aError, PatchError::InvalidOp, - "JSON Patch operation name is not supported"); +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - const pjson* pathNode = operation.find("path"); - if (pathNode == nullptr || !pathNode->isString()) - return failPatch(aError, PatchError::MissingPath, - "JSON Patch operation requires string member 'path'"); - aError.path = pjsonImpl::_string(*pathNode); - std::vector pathTokens; - if (!decodePatchPointer(aError.path, false, pathTokens, budget, aError)) - return false; +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - // Validate and decode this operation's metadata before mutating the - // private scratch tree. Later operations are processed only after - // earlier ones succeed; the public target is still untouched. - const bool needsFrom = aError.op == "move" || aError.op == "copy"; - std::vector fromTokens; - if (needsFrom) { - const pjson* fromNode = operation.find("from"); - if (fromNode == nullptr || !fromNode->isString()) - return failPatch(aError, PatchError::MissingFrom, - "move and copy require string member 'from'"); - aError.from = pjsonImpl::_string(*fromNode); - if (!decodePatchPointer(aError.from, true, fromTokens, budget, aError)) - return false; - } +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - const bool needsValue = - aError.op == "add" || aError.op == "replace" || aError.op == "test"; - const pjson* valueNode = operation.find("value"); - if (needsValue && valueNode == nullptr) - return failPatch(aError, PatchError::MissingValue, - "add, replace, and test require member 'value'"); +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - if (aError.op == "add") { - if (!measureClone(*valueNode, budget, aError)) - return false; - unique_ptr value = pjsonImpl::_cloneNode(*valueNode, scratch.getAllocator()); - if (!addOwnedAtPointer(scratch, pathTokens, aError.path, std::move(value), budget, - aError)) - return false; - continue; - } +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - if (aError.op == "remove") { - unique_ptr removed; - if (!detachAtPointer(scratch, pathTokens, aError.path, removed, budget, aError)) - return false; - continue; - } +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - if (aError.op == "replace") { - if (!measureClone(*valueNode, budget, aError)) - return false; - unique_ptr value = pjsonImpl::_cloneNode(*valueNode, scratch.getAllocator()); - if (!replaceAtPointer(scratch, pathTokens, aError.path, std::move(value), budget, - aError)) - return false; - continue; - } +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - if (aError.op == "test") { - const pjson* target = resolvePatchTokens(scratch, pathTokens, pathTokens.size(), - aError.path, budget, aError); - if (target == nullptr) - return false; - bool equal = false; - if (!patchEqual(*target, *valueNode, budget, aError, equal)) - return false; - if (!equal) - return failPatch(aError, PatchError::TestFailed, - "JSON Patch test value does not match target"); - continue; - } +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - const pjson* source = resolvePatchTokens(scratch, fromTokens, fromTokens.size(), - aError.from, budget, aError); - if (source == nullptr) - return false; +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - if (aError.op == "copy") { - if (!measureClone(*source, budget, aError)) - return false; - unique_ptr value = pjsonImpl::_cloneNode(*source, scratch.getAllocator()); - if (!addOwnedAtPointer(scratch, pathTokens, aError.path, std::move(value), budget, - aError)) - return false; - continue; - } +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - if (samePointerTokens(fromTokens, pathTokens)) - continue; - if (fromTokens.empty()) { - // Moving the root can only succeed when the destination is - // also root (handled above); every other path is a descendant. - return failPatch(aError, PatchError::MoveRootNotAllowed, - "cannot move the document root below itself"); - } - if (isProperPointerAncestor(fromTokens, pathTokens)) - return failPatch(aError, PatchError::MoveIntoDescendant, - "cannot move a value into one of its descendants"); +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - unique_ptr moved; - if (!detachAtPointer(scratch, fromTokens, aError.from, moved, budget, aError)) - return false; - if (!addOwnedAtPointer(scratch, pathTokens, aError.path, std::move(moved), budget, - aError)) - return false; - } +pjson& pjson::operator=(const std::vector& aValueArray) { + assignDomArray(*this, aValueArray); + return *this; +} - // This is the sole publication point; all earlier exits leave *this intact. - swap(scratch); - resetPatchError(aError); - return true; - } catch (const std::bad_alloc&) { - failPatchException(aError, PatchError::AllocationFailure, "JSON Patch ran out of memory"); - return false; - } catch (const std::exception&) { - failPatchException(aError, PatchError::InternalError, - "JSON Patch failed with an internal exception"); - return false; - } catch (...) { - failPatchException(aError, PatchError::InternalError, - "JSON Patch failed with an unknown exception"); - return false; - } +// Scalar append overloads promote non-arrays and publish one fully constructed child. +pjson& pjson::operator+=(const std::string& aValue) { + appendDomValue(*this, aValue); + return *this; } -// Applies Merge Patch while intentionally discarding detailed diagnostics. -bool pjson::applyMergePatch(const pjson& aPatch, const PatchOptions& aOpts) noexcept { - PatchError error; - return applyMergePatch(aPatch, error, aOpts); +pjson& pjson::operator+=(const char* aValue) { + if (aValue == nullptr) + throw std::invalid_argument("pjson string append requires non-null input"); + appendDomValue(*this, aValue); + return *this; } -// Applies RFC 7396 atomically by mutating a private deep copy and publishing it -// only after the iterative merge has completed. -bool pjson::applyMergePatch(const pjson& aPatch, PatchError& aError, - const PatchOptions& aOpts) noexcept { - resetPatchError(aError); - try { - PatchBudget budget(aOpts); - if (!measureClone(*this, budget, aError)) - return false; - pjson scratch(*this, *_allocator); - if (!applyMergePatchTo(scratch, aPatch, budget, aError)) { - if (!aError.ok) - return false; - return failPatch(aError, PatchError::InternalError, - "JSON Merge Patch could not update an object member"); - } - swap(scratch); - resetPatchError(aError); - return true; - } catch (const std::bad_alloc&) { - failPatchException(aError, PatchError::AllocationFailure, - "JSON Merge Patch ran out of memory"); - return false; - } catch (const std::exception&) { - failPatchException(aError, PatchError::InternalError, - "JSON Merge Patch failed with an internal exception"); - return false; - } catch (...) { - failPatchException(aError, PatchError::InternalError, - "JSON Merge Patch failed with an unknown exception"); - return false; - } +pjson& pjson::operator+=(const bool aValue) { + appendDomValue(*this, aValue); + return *this; +} +pjson& pjson::operator+=(const int aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +pjson& pjson::operator+=(const unsigned int aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +pjson& pjson::operator+=(const short aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +pjson& pjson::operator+=(const unsigned short aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +pjson& pjson::operator+=(const long aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +pjson& pjson::operator+=(const unsigned long aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +pjson& pjson::operator+=(const long long aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +pjson& pjson::operator+=(const unsigned long long aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +pjson& pjson::operator+=(const float aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +pjson& pjson::operator+=(const double aValue) { + appendDomValue(*this, aValue); + return *this; +} +pjson& pjson::operator+=(const long double aValue) { + appendDomValue(*this, static_cast(aValue)); + return *this; +} +// Vector append overloads share the rollback semantics documented by appendDomArray. +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; +} + +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; } -/*static*/ -// Compares stored JSON numbers without rounding an int64_t through binary64. -// The result is -1/0/1, or 2 when a NaN makes the ordering unordered. -int pjsonImpl::_compareNumbers(const pjson& aLeft, const pjson& aRight) { - if (aLeft._eType == pjson::jsonNumberInt && aRight._eType == pjson::jsonNumberInt) { - if (aLeft._uValue._valueInt < aRight._uValue._valueInt) - return -1; - return aLeft._uValue._valueInt > aRight._uValue._valueInt ? 1 : 0; - } - if (aLeft._eType == pjson::jsonNumberDouble && aRight._eType == pjson::jsonNumberDouble) { - const double left = aLeft._uValue._valueDouble; - const double right = aRight._uValue._valueDouble; - if (std::isnan(left) || std::isnan(right)) - return 2; - if (left < right) - return -1; - return left > right ? 1 : 0; - } - const bool intOnLeft = aLeft._eType == pjson::jsonNumberInt; - const int64_t integer = intOnLeft ? aLeft._uValue._valueInt : aRight._uValue._valueInt; - const double floating = intOnLeft ? aRight._uValue._valueDouble : aLeft._uValue._valueDouble; - int intVsDouble = 0; - if (std::isnan(floating)) { - return 2; - } else if (floating >= 9223372036854775808.0) { // exact 2^63 - intVsDouble = -1; - } else if (floating < -9223372036854775808.0) { - intVsDouble = 1; - } else { - const int64_t truncated = static_cast(floating); - if (integer != truncated) { - intVsDouble = integer < truncated ? -1 : 1; - } else { - const double integralPart = static_cast(truncated); - if (floating != integralPart) - intVsDouble = floating > integralPart ? -1 : 1; - } - } - return intOnLeft ? intVsDouble : -intVsDouble; +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; } -// Deep structural equality. Numbers compare by value across int/double -// (1 == 1.0); arrays element-wise in order; objects by key/value. The walk is -// iterative (an explicit pair work-list) so it never overflows the call stack -// on deeply nested documents. -bool pjson::operator==(const pjson& aOther) const { - struct Pair { - const pjson* a; - const pjson* b; - }; - std::vector work; - Pair root = {this, &aOther}; - work.push_back(root); - while (!work.empty()) { - Pair cur = work.back(); - work.pop_back(); - const pjson& lhs = *cur.a; - const pjson& rhs = *cur.b; +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; +} - // Numbers compare across int/double as one family. - bool lNum = (lhs._eType == jsonNumberInt || lhs._eType == jsonNumberDouble); - bool rNum = (rhs._eType == jsonNumberInt || rhs._eType == jsonNumberDouble); - if (lNum && rNum) { - if (pjsonImpl::_compareNumbers(lhs, rhs) != 0) { - return false; - } - continue; - } +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; +} - if (lhs._eType != rhs._eType) { - return false; - } +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; +} - switch (lhs._eType) { - case jsonType::jsonNull: - break; - case jsonType::jsonString: - if (*lhs._uValue._pValueString != *rhs._uValue._pValueString) - return false; - break; - case jsonType::jsonBoolean: - if (lhs._uValue._valueBool != rhs._uValue._valueBool) - return false; - break; - case jsonType::jsonNumberInt: - if (lhs._uValue._valueInt != rhs._uValue._valueInt) - return false; - break; - case jsonType::jsonNumberDouble: - if (lhs._uValue._valueDouble != rhs._uValue._valueDouble) - return false; - break; - case jsonType::jsonArray: { - if (lhs._uValue._pValueArray->size() != rhs._uValue._pValueArray->size()) { - return false; - } - for (size_t i = 0; i < lhs._uValue._pValueArray->size(); ++i) { - Pair p = {(*lhs._uValue._pValueArray)[i], (*rhs._uValue._pValueArray)[i]}; - work.push_back(p); - } - break; - } - case jsonType::jsonObject: { - if (lhs._uValue._pValueMap->size() != rhs._uValue._pValueMap->size()) { - return false; - } - auto a = lhs._uValue._pValueMap->begin(); - auto b = rhs._uValue._pValueMap->begin(); - for (; a != lhs._uValue._pValueMap->end(); ++a, ++b) { - if (a->first != b->first) { - return false; // keys (sorted) differ - } - Pair p = {a->second, b->second}; - work.push_back(p); - } - break; - } - } - } - return true; +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; } -// Implements inequality as the exact complement of structural equality. -bool pjson::operator!=(const pjson& aOther) const { - return !(*this == aOther); + +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; } -//===----------------------------------------------------------------------===// -// JSON Schema draft-07 subset validation -// -// Validation accumulates ordinary keyword failures but aborts on configured -// depth/reference limits. Combinators evaluate branches into temporary error -// vectors, committing diagnostics only according to the combinator's outcome so -// failed exploratory branches do not leak spurious public errors. -//===----------------------------------------------------------------------===// -// The JSON Schema type name for a value. -/*static*/ -std::string pjsonImpl::_typeName(const pjson& aNode) { - switch (aNode._eType) { - case jsonType::jsonNull: - return "null"; - case jsonType::jsonString: - return "string"; - case jsonType::jsonNumberInt: - return "integer"; - case jsonType::jsonNumberDouble: - return "number"; - case jsonType::jsonBoolean: - return "boolean"; - case jsonType::jsonArray: - return "array"; - case jsonType::jsonObject: - return "object"; - } - return "unknown"; +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; } -// Implements the "type" keyword. "number" accepts integers too; "integer" -// accepts a whole-valued double (e.g. 2.0) as JSON Schema does. -/*static*/ -bool pjsonImpl::_typeMatches(const pjson& aNode, const std::string& aTypeName) { - if (aTypeName == "null") - return aNode.isNull(); - if (aTypeName == "string") - return aNode.isString(); - if (aTypeName == "boolean") - return aNode.isBool(); - if (aTypeName == "array") - return aNode.isArray(); - if (aTypeName == "object") - return aNode.isObject(); - if (aTypeName == "number") - return aNode.isNumber(); - if (aTypeName == "integer") { - if (aNode.isInt()) - return true; - // A double with no fractional part counts as an integer. - if (aNode.isDouble()) { - double d = pjsonImpl::_floating(aNode); - return std::floor(d) == d && std::isfinite(d); - } - return false; - } - return false; // unknown type name never matches -} -// Appends "/token" to a JSON Pointer path, escaping '~' and '/' per RFC 6901. -std::string pjsonImpl::_pointerAppend(const std::string& aBase, const std::string& aToken) { - std::string escaped; - escaped.reserve(aToken.size()); - for (char c : aToken) { - if (c == '~') - escaped += "~0"; - else if (c == '/') - escaped += "~1"; - else - escaped += c; - } - return aBase + "/" + escaped; -} -// Conservative single-pass screen for constructs that are especially prone to -// catastrophic backtracking in std::regex. This is intentionally fail-closed: -// unrestricted ECMAScript regex remains available through trustedRegex(). -bool pjsonImpl::_isSafeRegex(const std::string& aPattern) { - bool escaped = false; - bool inClass = false; - int groups = 0; - int quantifiers = 0; - struct Group { - bool hasQuantifier; - bool hasAlternation; - }; - std::vector stack; - for (size_t i = 0; i < aPattern.size(); ++i) { - const char c = aPattern[i]; - if (escaped) { - if (c >= '1' && c <= '9') - return false; // backreference - escaped = false; - continue; - } - if (c == '\\') { - escaped = true; - continue; - } - if (c == '[') { - inClass = true; - continue; - } - if (c == ']' && inClass) { - inClass = false; - continue; - } - if (inClass) - continue; +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; +} - if (c == '(') { - if (++groups > 16) - return false; - Group g = {false, false}; - stack.push_back(g); - } else if (c == '|') { - // Even apparently simple alternation can become ambiguous when - // combined with repetition, so the safe subset excludes it. - return false; - } else if (c == '*' || c == '+' || c == '?' || c == '{') { - if (++quantifiers > 1) - return false; - if (c == '{') { - // Keep counted repetitions bounded. Scan only the numeric - // bounds; malformed syntax is still diagnosed by std::regex. - size_t j = i + 1; - size_t first = 0; - size_t second = 0; - bool haveFirst = false; - bool haveSecond = false; - while (j < aPattern.size() && aPattern[j] >= '0' && aPattern[j] <= '9') { - haveFirst = true; - if (first > 1000) - return false; - first = first * 10 + static_cast(aPattern[j] - '0'); - ++j; - } - if (j < aPattern.size() && aPattern[j] == ',') { - ++j; - while (j < aPattern.size() && aPattern[j] >= '0' && aPattern[j] <= '9') { - haveSecond = true; - if (second > 1000) - return false; - second = second * 10 + static_cast(aPattern[j] - '0'); - ++j; - } - } - if ((haveFirst && first > 1000) || (haveSecond && second > 1000)) - return false; - } - if (!stack.empty()) - stack.back().hasQuantifier = true; - } else if (c == ')' && !stack.empty()) { - Group closed = stack.back(); - stack.pop_back(); - size_t next = i + 1; - bool quantified = - next < aPattern.size() && (aPattern[next] == '*' || aPattern[next] == '+' || - aPattern[next] == '?' || aPattern[next] == '{'); - if (quantified && (closed.hasQuantifier || closed.hasAlternation)) - return false; - if (!stack.empty()) { - stack.back().hasQuantifier = - stack.back().hasQuantifier || quantified || closed.hasQuantifier; - stack.back().hasAlternation = stack.back().hasAlternation || closed.hasAlternation; - } - } - } - return true; +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; } -namespace { - //===------------------------------------------------------------------===// - // Exact numeric constraints and format validators - //===------------------------------------------------------------------===// - // Normalized decimal magnitude: coefficient * 10^exponent10. Trailing - // decimal zeroes are folded into the exponent so divisibility can be tested - // with integer arithmetic rather than floating-point tolerance. - struct ExactDecimal { - uint64_t coefficient; - int exponent10; - }; +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; +} - // Computes an int64 magnitude without overflowing on INT64_MIN. - uint64_t magnitudeOf(int64_t value) { - return value < 0 ? uint64_t(-(value + 1)) + uint64_t(1) : uint64_t(value); - } +pjson& pjson::operator+=(const std::vector& aValueArray) { + appendDomArray(*this, aValueArray); + return *this; +} - // Parses the serializer's finite decimal notation into normalized form; - // returns false if its bounded coefficient/exponent representation overflows. - bool decimalFromText(const std::string& text, ExactDecimal& result) { - size_t pos = 0; - if (pos < text.size() && (text[pos] == '+' || text[pos] == '-')) - ++pos; - uint64_t coefficient = 0; - int fractionDigits = 0; - bool seenDigit = false; - bool afterPoint = false; - while (pos < text.size() && text[pos] != 'e' && text[pos] != 'E') { - const char ch = text[pos++]; - if (ch == '.' && !afterPoint) { - afterPoint = true; - continue; - } - if (ch < '0' || ch > '9') - return false; - const uint64_t digit = static_cast(ch - '0'); - if (coefficient > (std::numeric_limits::max() - digit) / uint64_t(10)) - return false; - coefficient = coefficient * uint64_t(10) + digit; - if (afterPoint) - ++fractionDigits; - seenDigit = true; - } - int explicitExponent = 0; - if (pos < text.size()) { - ++pos; - bool negative = false; - if (pos < text.size() && (text[pos] == '+' || text[pos] == '-')) { - negative = text[pos] == '-'; - ++pos; - } - if (pos == text.size()) - return false; - while (pos < text.size()) { - const char ch = text[pos++]; - if (ch < '0' || ch > '9') - return false; - if (explicitExponent > 10000) - return false; - explicitExponent = explicitExponent * 10 + (ch - '0'); - } - if (negative) - explicitExponent = -explicitExponent; - } - if (!seenDigit) - return false; - if (coefficient == 0) { - result.coefficient = 0; - result.exponent10 = 0; - return true; - } - int exponent = explicitExponent - fractionDigits; - while (coefficient % uint64_t(10) == 0) { - coefficient /= uint64_t(10); - ++exponent; - } - result.coefficient = coefficient; - result.exponent10 = exponent; - return true; - } +//===----------------------------------------------------------------------===// +// Factories, checked access, and generic child insertion +//===----------------------------------------------------------------------===// - // Converts either internal numeric representation to normalized decimal form. - bool decimalFromNumber(const pjson& value, ExactDecimal& result) { - if (value.isInt()) { - result.coefficient = magnitudeOf(pjsonImpl::_integer(value)); - result.exponent10 = 0; - if (result.coefficient == 0) - return true; - while (result.coefficient % uint64_t(10) == 0) { - result.coefficient /= uint64_t(10); - ++result.exponent10; - } - return true; - } - if (!value.isDouble() || !std::isfinite(pjsonImpl::_floating(value))) - return false; - return decimalFromText(pjsonImpl::_formatDouble(pjsonImpl::_floating(value)), result); - } +// Explicit typed factories. Each returns a default-allocator value of the +// requested kind so callers never depend on default construction's type. +pjson pjson::null() { + return pjson(); +} +pjson pjson::object() { + pjson value; + value.resetTo(pjson::jsonType::jsonObject); + return value; +} +pjson pjson::array() { + pjson value; + value.resetTo(pjson::jsonType::jsonArray); + return value; +} +// Assigning nullptr resets to JSON null, matching null(). +pjson& pjson::operator=(std::nullptr_t) { + reset(); + return *this; +} - // Produces a diagnostic representation without losing integer precision. - std::string formatNumber(const pjson& value) { - return value.isInt() ? std::to_string(pjsonImpl::_integer(value)) - : pjsonImpl::_formatDouble(pjsonImpl::_floating(value)); +// Checked, non-vivifying object access. Throws std::out_of_range on a missing +// key or a non-object receiver, distinguishing it from vivifying operator[]. +pjson& pjson::at(const std::string& aKey) { + pjson* child = find(aKey); + if (child == nullptr) + throw std::out_of_range("pjson::at: object key not found"); + return *child; +} +const pjson& pjson::at(const std::string& aKey) const { + const pjson* child = find(aKey); + if (child == nullptr) + throw std::out_of_range("pjson::at: object key not found"); + return *child; +} +// Checked, non-vivifying array access using a non-negative index. Throws +// std::out_of_range for a non-array receiver or an out-of-range index. +pjson& pjson::at(size_t aIndex) { + if (_pImpl->_eType != pjson::jsonType::jsonArray || aIndex >= _pImpl->_pValueArray->size()) + throw std::out_of_range("pjson::at: array index out of range"); + return *(*_pImpl->_pValueArray)[aIndex]; +} +const pjson& pjson::at(size_t aIndex) const { + if (_pImpl->_eType != pjson::jsonType::jsonArray || aIndex >= _pImpl->_pValueArray->size()) + throw std::out_of_range("pjson::at: array index out of range"); + return *(*_pImpl->_pValueArray)[aIndex]; +} + +// Generic child append. Promotes a non-array target to an array, then attaches +// a deep copy (copy overload) or a moved/cross-allocator-copied value. +pjson& pjson::pushBack(const pjson& aValue) { + // When converting a container that owns aValue, build the complete + // replacement before destroying the old tree. This also gives all + // non-array promotions a strong exception guarantee. + if (_pImpl->_eType != pjson::jsonType::jsonArray) { + pjson replacement(*_allocator); + replacement.resetTo(pjson::jsonType::jsonArray); + replacement.pushBack(aValue); + pjsonImpl::_swapStorage(*this, replacement); + return *this; } - - // Decodes nonnegative integral size keywords without truncation. When the - // mathematical value exceeds size_t, aboveRange distinguishes it from an - // invalid keyword shape so min constraints can still be evaluated exactly. - bool schemaSize(const pjson& value, size_t& result, bool& aboveRange) { - aboveRange = false; - if (value.isInt()) { - const int64_t integer = pjsonImpl::_integer(value); - if (integer < 0) - return false; - const uint64_t magnitude = static_cast(integer); - if (magnitude > static_cast(std::numeric_limits::max())) { - aboveRange = true; - return true; - } - result = static_cast(magnitude); - return true; - } - if (!value.isDouble()) - return false; - const double number = pjsonImpl::_floating(value); - if (!std::isfinite(number) || number < 0.0 || std::floor(number) != number) - return false; - const double exclusiveUpper = std::ldexp(1.0, std::numeric_limits::digits); - if (number >= exclusiveUpper) { - aboveRange = true; - return true; - } - result = static_cast(number); - return true; + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(*_allocator); + pjsonImpl::_copyContentsInto(*child, aValue); + _pImpl->_pValueArray->push_back(nullptr); + _pImpl->_pValueArray->back() = child.release(); + return *this; +} +pjson& pjson::pushBack(pjson&& aValue) { + // Moving an ancestor into its descendant cannot consume the source without + // invalidating the destination. Snapshot it instead; moved-from values are + // allowed to retain their value. Self-append therefore appends a finite + // snapshot rather than creating an ownership cycle. + if (pjsonImpl::_containsNode(aValue, this)) + return pushBack(static_cast(aValue)); + if (_pImpl->_eType != pjson::jsonType::jsonArray) { + pjson replacement(*_allocator); + replacement.resetTo(pjson::jsonType::jsonArray); + replacement.pushBack(std::move(aValue)); + pjsonImpl::_swapStorage(*this, replacement); + return *this; } - - // Implements multipleOf from integers or canonical decimal text generated - // for doubles. Powers of ten are reduced through their prime factors, - // avoiding fixed-epsilon comparisons. - bool isExactMultiple(const pjson& value, const pjson& divisor) { - // JSON Schema requires a strictly positive divisor. Consistent with - // the library's tolerant handling of malformed schemas, non-positive - // values are ignored instead of being treated as assertions. - if (pjsonImpl::_numberAsDouble(divisor) <= 0.0) - return true; - if (value.isInt() && divisor.isInt()) { - const uint64_t d = magnitudeOf(pjsonImpl::_integer(divisor)); - return magnitudeOf(pjsonImpl::_integer(value)) % d == 0; - } - ExactDecimal v = {0, 0}; - ExactDecimal d = {0, 0}; - if (!decimalFromNumber(divisor, d) || d.coefficient == 0) - return true; - if (!decimalFromNumber(value, v)) - return false; - if (v.coefficient == 0) - return true; - const int shift = v.exponent10 - d.exponent10; - if (shift >= 0) { - uint64_t reduced = d.coefficient; - int remainingTwos = shift; - int remainingFives = shift; - while (remainingTwos > 0 && reduced % uint64_t(2) == 0) { - reduced /= uint64_t(2); - --remainingTwos; - } - while (remainingFives > 0 && reduced % uint64_t(5) == 0) { - reduced /= uint64_t(5); - --remainingFives; - } - return v.coefficient % reduced == 0; - } - if (v.coefficient % d.coefficient != 0) - return false; - uint64_t quotient = v.coefficient / d.coefficient; - int decimalPlaces = -shift; - while (decimalPlaces > 0 && quotient % uint64_t(10) == 0) { - quotient /= uint64_t(10); - --decimalPlaces; - } - return decimalPlaces == 0; + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(*_allocator); + // Reserve before consuming aValue so allocation failure leaves both values + // logically unchanged. Child nodes are separately allocated, so a vector + // reallocation cannot invalidate an aliased descendant source. + if (_pImpl->_pValueArray->size() == _pImpl->_pValueArray->max_size()) + throw std::length_error("pjson array exceeds maximum size"); + _pImpl->_pValueArray->reserve(_pImpl->_pValueArray->size() + 1); + if (child->_allocator == aValue._allocator) { + pjsonImpl::_swapStorage(*child, aValue); + aValue.reset(); + } else { + pjsonImpl::_copyContentsInto(*child, aValue); + aValue.reset(); } - - // Locale-independent character predicates used by schema format parsers. - bool isAsciiDigit(char ch) { - return ch >= '0' && ch <= '9'; + _pImpl->_pValueArray->push_back(nullptr); + _pImpl->_pValueArray->back() = child.release(); + return *this; +} +// Insert-or-assign an object member from an arbitrary pjson value. +pjson& pjson::insertOrAssign(const std::string& aKey, const pjson& aValue) { + // Snapshot an ancestor (including *this) before adding/replacing its child, + // otherwise insertion could change the very source being copied. + if (pjsonImpl::_containsNode(aValue, this)) { + pjson sourceCopy(aValue, *_allocator); + return insertOrAssign(aKey, std::move(sourceCopy)); } - bool isAsciiHex(char ch) { - return isAsciiDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + if (_pImpl->_eType != pjson::jsonType::jsonObject) { + pjson replacement(*_allocator); + replacement.resetTo(pjson::jsonType::jsonObject); + replacement.insertOrAssign(aKey, aValue); + pjsonImpl::_swapStorage(*this, replacement); + return *this; } - - // Parses exactly count decimal digits at offset into a small integer. - bool parseFixedDigits(const std::string& value, size_t offset, size_t count, int& result) { - if (offset > value.size() || count > value.size() - offset) - return false; - result = 0; - for (size_t i = 0; i < count; ++i) { - if (!isAsciiDigit(value[offset + i])) - return false; - result = result * 10 + (value[offset + i] - '0'); - } - return true; + pjsonImpl::ObjectStorage& object = *_pImpl->_pValueMap; + pjsonImpl::ObjectStorage::iterator existing = object.find(aKey); + if (existing != object.end()) { + existing->second->copyFrom(aValue); + return *this; } - - // Applies Gregorian leap-year rules. - bool isLeapYear(int year) { - return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + pjsonImpl::OwnedNode child = pjsonImpl::_cloneNode(aValue, *_allocator); + const std::pair inserted = + object.insert(std::make_pair(aKey, static_cast(nullptr))); + if (!inserted.second) { + inserted.first->second->copyFrom(aValue); + return *this; } - - // Validates an RFC 3339 full-date, including month-specific day limits. - bool validDate(const std::string& value) { - if (value.size() != 10 || value[4] != '-' || value[7] != '-') - return false; - int year = 0, month = 0, day = 0; - if (!parseFixedDigits(value, 0, 4, year) || !parseFixedDigits(value, 5, 2, month) || - !parseFixedDigits(value, 8, 2, day) || month < 1 || month > 12 || day < 1) - return false; - static const int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - int maxDay = days[month - 1]; - if (month == 2 && isLeapYear(year)) - maxDay = 29; - return day <= maxDay; + inserted.first->second = child.release(); + return *this; +} +pjson& pjson::insertOrAssign(const std::string& aKey, pjson&& aValue) { + if (pjsonImpl::_containsNode(aValue, this)) { + pjson sourceCopy(aValue, *_allocator); + return insertOrAssign(aKey, std::move(sourceCopy)); } - - // Validates an RFC 3339 full-time and permits leap second 60 only when the - // represented UTC minute is 23:59. - bool validTime(const std::string& value) { - if (value.size() < 9 || value[2] != ':' || value[5] != ':') - return false; - int hour = 0, minute = 0, second = 0; - if (!parseFixedDigits(value, 0, 2, hour) || !parseFixedDigits(value, 3, 2, minute) || - !parseFixedDigits(value, 6, 2, second) || hour > 23 || minute > 59 || second > 60) - return false; - size_t pos = 8; - if (pos < value.size() && value[pos] == '.') { - ++pos; - const size_t fractionStart = pos; - while (pos < value.size() && isAsciiDigit(value[pos])) - ++pos; - if (pos == fractionStart) - return false; - } - int offsetMinutes = 0; - if (pos < value.size() && (value[pos] == 'Z' || value[pos] == 'z')) { - ++pos; - } else { - if (pos + 6 != value.size() || (value[pos] != '+' && value[pos] != '-') || - value[pos + 3] != ':') - return false; - int offsetHour = 0, offsetMinute = 0; - if (!parseFixedDigits(value, pos + 1, 2, offsetHour) || - !parseFixedDigits(value, pos + 4, 2, offsetMinute) || offsetHour > 23 || - offsetMinute > 59) - return false; - offsetMinutes = offsetHour * 60 + offsetMinute; - if (value[pos] == '-') - offsetMinutes = -offsetMinutes; - pos += 6; - } - if (pos != value.size()) - return false; - if (second == 60) { - int utcMinute = (hour * 60 + minute - offsetMinutes) % (24 * 60); - if (utcMinute < 0) - utcMinute += 24 * 60; - if (utcMinute != 23 * 60 + 59) - return false; - } - return true; + if (_pImpl->_eType != pjson::jsonType::jsonObject) { + pjson replacement(*_allocator); + replacement.resetTo(pjson::jsonType::jsonObject); + replacement.insertOrAssign(aKey, std::move(aValue)); + pjsonImpl::_swapStorage(*this, replacement); + return *this; } - - // Validates an RFC 3339 date-time joined by T/t. - bool validDateTime(const std::string& value) { - return value.size() > 11 && (value[10] == 'T' || value[10] == 't') && - validDate(value.substr(0, 10)) && validTime(value.substr(11)); + pjsonImpl::ObjectStorage& object = *_pImpl->_pValueMap; + pjsonImpl::ObjectStorage::iterator existing = object.find(aKey); + if (existing != object.end()) { + *existing->second = std::move(aValue); + return *this; } - - // Validates four canonical decimal IPv4 octets with no leading zeroes. - bool validIPv4(const std::string& value) { - size_t pos = 0; - for (int part = 0; part < 4; ++part) { - const size_t begin = pos; - int octet = 0; - while (pos < value.size() && isAsciiDigit(value[pos])) { - octet = octet * 10 + (value[pos] - '0'); - if (octet > 255) - return false; - ++pos; - } - const size_t digits = pos - begin; - if (digits == 0 || digits > 3 || (digits > 1 && value[begin] == '0')) - return false; - if (part != 3) { - if (pos >= value.size() || value[pos] != '.') - return false; - ++pos; - } - } - return pos == value.size(); + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(*_allocator); + const std::pair inserted = + object.insert(std::make_pair(aKey, child.get())); + if (!inserted.second) { + *inserted.first->second = std::move(aValue); + return *this; } - - // Counts 16-bit units on one side of ::, optionally accepting a final IPv4 - // address as two units. Empty sides are valid only as compression operands. - bool parseIPv6Side(const std::string& side, bool mayContainIPv4, int& units) { - if (side.empty()) - return true; - size_t start = 0; - while (start <= side.size()) { - const size_t colon = side.find(':', start); - const size_t end = colon == std::string::npos ? side.size() : colon; - if (end == start) - return false; - const std::string token = side.substr(start, end - start); - if (token.find('.') != std::string::npos) { - if (!mayContainIPv4 || end != side.size() || !validIPv4(token)) - return false; - units += 2; - } else { - if (token.size() > 4) - return false; - for (size_t i = 0; i < token.size(); ++i) { - if (!isAsciiHex(token[i])) - return false; - } - ++units; - } - if (colon == std::string::npos) - break; - start = colon + 1; - if (start == side.size()) - return false; + pjson* attached = child.release(); + if (attached->_allocator == aValue._allocator) { + pjsonImpl::_swapStorage(*attached, aValue); + } else { + // Clone before consuming a cross-allocator source. If copying throws, + // erase the newly attached null node and preserve both input values. + try { + pjsonImpl::_copyContentsInto(*attached, aValue); + } catch (...) { + pjsonImpl::_destroyNode(attached); + object.erase(inserted.first); + throw; } - return true; + aValue.reset(); } + return *this; +} +// Reserves array capacity. Promotes a non-array to an empty array first so the +// reservation is always meaningful; a no-op count of zero still normalizes type. +pjson& pjson::reserve(size_t aCount) { + if (_pImpl->_eType != pjson::jsonType::jsonArray) + resetTo(pjson::jsonType::jsonArray); + _pImpl->_pValueArray->reserve(aCount); + return *this; +} +// contains() is a readable alias for hasKey(). +bool pjson::contains(const std::string& aKey) const { + return hasKey(aKey); +} +bool pjson::contains(const char* aKey) const { + return hasKey(aKey); +} - // Validates an IPv6 address with at most one compression marker and exactly - // eight units after expanding it. - bool validIPv6(const std::string& value) { - if (value.empty()) - return false; - const size_t compression = value.find("::"); - if (compression != std::string::npos && - value.find("::", compression + 2) != std::string::npos) - return false; - int units = 0; - if (compression == std::string::npos) - return parseIPv6Side(value, true, units) && units == 8; - const std::string left = value.substr(0, compression); - const std::string right = value.substr(compression + 2); - // An embedded IPv4 address may appear only as the final component, - // which is necessarily on the right side when :: compression is used. - if (!parseIPv6Side(left, false, units) || !parseIPv6Side(right, true, units)) +//===----------------------------------------------------------------------===// +// Non-allocating traversal (PJSON-API-001) +// +// Callback-style visitors keep the public header declaration-only and ABI +// stable. Each visits borrowed children directly with no key copy and no second +// lookup; aContext carries caller state because a plain function pointer cannot +// capture. Returning false stops early and propagates as the call's result. +//===----------------------------------------------------------------------===// +bool pjson::forEachMember(ConstMemberVisitor aVisitor, void* aContext) const { + if (_pImpl->_eType != pjson::jsonType::jsonObject || aVisitor == nullptr) + return true; + for (pjsonImpl::ObjectStorage::const_iterator it = _pImpl->_pValueMap->begin(); + it != _pImpl->_pValueMap->end(); ++it) { + StringView keyView(it->first.data(), it->first.size()); + if (!aVisitor(keyView, static_cast(*it->second), aContext)) return false; - return units < 8; } - - // Validates the canonical 8-4-4-4-12 hexadecimal UUID text shape. - bool validUuid(const std::string& value) { - if (value.size() != 36 || value[8] != '-' || value[13] != '-' || value[18] != '-' || - value[23] != '-') - return false; - for (size_t i = 0; i < value.size(); ++i) { - if (i == 8 || i == 13 || i == 18 || i == 23) - continue; - if (!isAsciiHex(value[i])) - return false; - } + return true; +} +bool pjson::forEachMember(MemberVisitor aVisitor, void* aContext) { + if (_pImpl->_eType != pjson::jsonType::jsonObject || aVisitor == nullptr) return true; + for (pjsonImpl::ObjectStorage::iterator it = _pImpl->_pValueMap->begin(); + it != _pImpl->_pValueMap->end(); ++it) { + StringView keyView(it->first.data(), it->first.size()); + if (!aVisitor(keyView, *it->second, aContext)) + return false; } - - // Dispatches supported format assertions. Unknown names are annotations and - // therefore succeed with known=false, as required by JSON Schema. - bool knownFormatValid(const std::string& format, const std::string& value, bool& known) { - known = true; - if (format == "date") - return validDate(value); - if (format == "time") - return validTime(value); - if (format == "date-time") - return validDateTime(value); - if (format == "ipv4") - return validIPv4(value); - if (format == "ipv6") - return validIPv6(value); - if (format == "uuid") - return validUuid(value); - known = false; + return true; +} +bool pjson::forEachElement(ConstElementVisitor aVisitor, void* aContext) const { + if (_pImpl->_eType != pjson::jsonType::jsonArray || aVisitor == nullptr) return true; + const pjsonImpl::ArrayStorage& arr = *_pImpl->_pValueArray; + for (size_t i = 0; i < arr.size(); ++i) { + if (!aVisitor(static_cast(*arr[i]), aContext)) + return false; } - - // Percent-decodes a same-document URI fragment and accepts it only when the - // result is empty or has JSON Pointer syntax. Token unescaping happens later. - bool decodeSchemaFragment(const std::string& fragment, std::string& pointer) { - pointer.clear(); - for (size_t i = 0; i < fragment.size(); ++i) { - if (fragment[i] != '%') { - pointer += fragment[i]; - continue; - } - if (i + 2 >= fragment.size() || !isAsciiHex(fragment[i + 1]) || - !isAsciiHex(fragment[i + 2])) - return false; - const char hi = fragment[i + 1]; - const char lo = fragment[i + 2]; - const int high = - isAsciiDigit(hi) ? hi - '0' : (hi >= 'a' ? hi - 'a' + 10 : hi - 'A' + 10); - const int low = - isAsciiDigit(lo) ? lo - '0' : (lo >= 'a' ? lo - 'a' + 10 : lo - 'A' + 10); - pointer += static_cast((high << 4) | low); - i += 2; - } - return pointer.empty() || pointer[0] == '/'; - } - - // Adds a diagnostic without allowing allocation failure to escape a noexcept API. - void bestEffortSchemaError(std::vector& errors, const std::string& path, - const std::string& message) noexcept { - try { - errors.push_back(SchemaError(path, message)); - } catch (...) { // Best effort: this path must remain noexcept. - return; - } + return true; +} +bool pjson::forEachElement(ElementVisitor aVisitor, void* aContext) { + if (_pImpl->_eType != pjson::jsonType::jsonArray || aVisitor == nullptr) + return true; + pjsonImpl::ArrayStorage& arr = *_pImpl->_pValueArray; + for (size_t i = 0; i < arr.size(); ++i) { + if (!aVisitor(*arr[i], aContext)) + return false; } + return true; +} +//===----------------------------------------------------------------------===// +// Container access and lookup +// +// Mutating operator[] access auto-vivifies missing containers and children; +// find() is non-mutating. Negative array indices count from the end. Mutating +// indices before the beginning throw without changing the receiver, while lookup +// indices simply miss. +//===----------------------------------------------------------------------===// - // Literal-string overload for exception paths that should avoid extra temporaries. - void bestEffortSchemaError(std::vector& errors, const char* path, - const char* message) noexcept { - try { - errors.push_back(SchemaError(path, message)); - } catch (...) { // Best effort: this path must remain noexcept. - return; - } +// Returns or creates an object member, atomically promoting non-object values. +// The std::string overload is the length-aware primary implementation so keys +// containing embedded U+0000 are preserved byte-for-byte; the const char* +// overload deliberately keeps conventional NUL-terminated semantics. +pjson& pjson::operator[](const std::string& aString) { + if (_pImpl->_eType != pjson::jsonType::jsonObject) { + pjson replacement(*_allocator); + replacement.resetTo(pjson::jsonType::jsonObject); + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(*_allocator); + const std::pair inserted = + replacement._pImpl->_pValueMap->insert( + std::make_pair(aString, static_cast(nullptr))); + pjson* result = child.release(); + inserted.first->second = result; + pjsonImpl::_swapStorage(*this, replacement); + return *result; } - - // Balances the shared recursion counter across every return and exception. - struct SchemaDepthGuard { - pjsonImpl::SchemaValidationCtx& ctx; - size_t levels; - explicit SchemaDepthGuard(pjsonImpl::SchemaValidationCtx& aCtx) - : ctx(aCtx) - , levels(1) { - ++ctx.depth; - } - void enterResolvedReference() { - ++ctx.depth; - ++levels; - } - ~SchemaDepthGuard() { ctx.depth -= levels; } - }; - - // Keeps every iteratively resolved (instance, schema) pair active until the - // terminal schema has been evaluated, matching nested-call cycle semantics. - struct ActiveRefGuard { - std::vector>& refs; - const size_t initialSize; - explicit ActiveRefGuard(std::vector>& aRefs) - : refs(aRefs) - , initialSize(aRefs.size()) {} - void push(const pjson* node, const pjson* schema) { - refs.push_back(std::make_pair(node, schema)); - } - ~ActiveRefGuard() { - while (refs.size() > initialSize) - refs.pop_back(); - } - }; - - // Aborts all remaining branches and ensures a budget failure reaches the - // public error vector even when discovered inside combinator scratch errors. - void failValidationBudget(pjsonImpl::SchemaValidationCtx& ctx, - pjsonImpl::SchemaErrorSink& errors, const std::string& path, - const std::string& message) { - if (ctx.aborted) - return; - ctx.aborted = true; - const size_t errorLimit = ctx.options.maxErrors == 0 ? size_t(100) : ctx.options.maxErrors; - if (ctx.errorsUsed >= errorLimit) - return; - std::vector& destination = - ctx.publicErrors != nullptr ? *ctx.publicErrors : errors.values; - const size_t before = destination.size(); - bestEffortSchemaError(destination, path, message); - if (destination.size() != before) - ++ctx.errorsUsed; + pjsonImpl::ObjectStorage::iterator it = _pImpl->_pValueMap->find(aString); + if (it != _pImpl->_pValueMap->end()) { + return *(it->second); } - - size_t validationDepthLimit(const SchemaOptions& options) { - const size_t requested = options.maxValidationDepth == 0 ? kSchemaValidationDepthHardLimit - : options.maxValidationDepth; - return std::min(requested, kSchemaValidationDepthHardLimit); + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(*_allocator); + const std::pair inserted = + _pImpl->_pValueMap->insert(std::make_pair(aString, static_cast(nullptr))); + pjson* result = inserted.first->second; + if (inserted.second) { + result = child.release(); + inserted.first->second = result; } - - size_t validationRefLimit(const SchemaOptions& options) { - return options.maxRefResolutions == 0 ? size_t(1024) : options.maxRefResolutions; + return *result; +} +pjson& pjson::operator[](const char* aSkey) { + if (aSkey == nullptr) + throw std::invalid_argument("pjson object key requires non-null input"); + return (*this)[std::string(aSkey)]; +} +// Returns or creates an array element, filling gaps with null nodes. Any failed +// growth destroys every node appended by this call before rethrowing. +pjson& pjson::operator[](int index) { + if (index < 0) { + if (_pImpl->_eType != pjson::jsonType::jsonArray) + throw std::out_of_range("pjson array negative index requires an existing array"); + pjsonImpl::ArrayStorage& array = *_pImpl->_pValueArray; + const size_t fromEnd = static_cast(-(index + 1)) + size_t(1); + if (fromEnd > array.size()) + throw std::out_of_range("pjson array negative index out of range"); + return *array[array.size() - fromEnd]; } + return (*this)[static_cast(index)]; +} - size_t validationWorkLimit(const SchemaOptions& options) { - return options.maxValidationWork == 0 ? size_t(1000000) : options.maxValidationWork; +// Returns or creates an array element at a non-negative index, filling gaps +// with null nodes. Any failed growth destroys nodes appended by this call. +pjson& pjson::operator[](size_t index) { + if (_pImpl->_eType != pjson::jsonType::jsonArray) { + pjson replacement(*_allocator); + replacement.resetTo(pjson::jsonType::jsonArray); + pjson& result = replacement[index]; + pjson* resultPtr = &result; + pjsonImpl::_swapStorage(*this, replacement); + return *resultPtr; } + pjsonImpl::ArrayStorage& array = *_pImpl->_pValueArray; + const size_t position = index; - // Charges bounded validation work before potentially expensive traversal. - bool chargeValidationWork(pjsonImpl::SchemaValidationCtx& ctx, - pjsonImpl::SchemaErrorSink& errors, const std::string& path, - size_t amount = 1) { - const size_t limit = validationWorkLimit(ctx.options); - if (amount > limit - std::min(ctx.workUsed, limit)) { - failValidationBudget(ctx, errors, path, "schema validation work budget exceeded"); - return false; + if (position >= array.size()) { + static const size_t kMaxAutoGrowth = size_t(1000000); + const size_t growth = position - array.size() + size_t(1); + if (growth > kMaxAutoGrowth) + throw std::length_error("pjson array auto-growth exceeds safety limit"); + if (position == std::numeric_limits::max() || + position + size_t(1) > array.max_size()) + throw std::length_error("pjson array index exceeds maximum size"); + const size_t requiredSize = position + size_t(1); + const size_t originalSize = array.size(); + try { + array.reserve(requiredSize); + while (array.size() < requiredSize) { + pjsonImpl::OwnedNode child = pjsonImpl::_makeNode(*_allocator); + array.push_back(nullptr); + array.back() = child.release(); + } + } catch (...) { + while (array.size() > originalSize) { + pjsonImpl::OwnedNode rollback(array.back()); + array.pop_back(); + } + throw; } - ctx.workUsed += amount; - return true; - } - - // Loop-heavy keywords charge separately from recursive schema evaluations. - bool chargeLoopWork(pjsonImpl::SchemaValidationCtx& ctx, pjsonImpl::SchemaErrorSink& errors, - const std::string& path, size_t amount = 1) { - return chargeValidationWork(ctx, errors, path, amount); } - // Counts Unicode code points, charging the bytes examined. Parsed strings - // are valid UTF-8; malformed programmatic bytes count individually here. - bool unicodeLength(const std::string& value, pjsonImpl::SchemaValidationCtx& ctx, - pjsonImpl::SchemaErrorSink& errors, const std::string& path, size_t& count) { - count = 0; - for (size_t offset = 0; offset < value.size(); ++count) { - const int bytes = pjsonImpl::_utf8Len(value.data(), offset, value.size()); - const size_t consumed = bytes > 0 ? static_cast(bytes) : size_t(1); - if (!chargeLoopWork(ctx, errors, path, consumed)) - return false; - offset += consumed; + return *array[position]; +} +// Length-aware object lookup: the std::string overload is the primary form so +// keys containing embedded U+0000 resolve on their full byte sequence. The +// const char* overloads keep conventional NUL-terminated behavior. +pjson* pjson::find(const std::string& aKey) { + if (_pImpl->_eType == pjson::jsonType::jsonObject) { + auto it = _pImpl->_pValueMap->find(aKey); + if (it != _pImpl->_pValueMap->end()) { + return it->second; } - return true; } + return nullptr; +} +// Finds an object member without inserting or changing the receiver. +pjson* pjson::find(const char* aKey) { + if (aKey != nullptr) + return find(std::string(aKey)); + return nullptr; +} +const pjson* pjson::find(const std::string& aKey) const { + return const_cast(this)->find(aKey); +} +const pjson* pjson::find(const char* aKey) const { + if (aKey != nullptr) + return find(std::string(aKey)); + return nullptr; +} +pjson* pjson::find(int aIndex) noexcept { + return const_cast(static_cast(this)->find(aIndex)); +} +// Finds an array element with end-relative negative-index support. +const pjson* pjson::find(int aIndex) const noexcept { + if (_pImpl->_eType != pjson::jsonType::jsonArray) + return nullptr; - // Records ordinary keyword failures through one shared per-run quota. The - // terminal budget diagnostic bypasses this quota via failValidationBudget. - bool addSchemaError(pjsonImpl::SchemaValidationCtx&, pjsonImpl::SchemaErrorSink& errors, - const std::string& path, const std::string& message) { - errors.push_back(SchemaError(path, message)); - return !errors.ctx.aborted; + const pjsonImpl::ArrayStorage& values = *_pImpl->_pValueArray; + size_t position = 0; + if (aIndex >= 0) { + position = static_cast(aIndex); + if (position >= values.size()) + return nullptr; + } else { + const size_t fromEnd = static_cast(-(aIndex + 1)) + size_t(1); + if (fromEnd > values.size()) + return nullptr; + position = values.size() - fromEnd; } + return values[position]; +} +pjson* pjson::findIndex(size_t aIndex) noexcept { + return const_cast(static_cast(this)->findIndex(aIndex)); +} +// Finds a non-negative array index without narrowing to int. +const pjson* pjson::findIndex(size_t aIndex) const noexcept { + if (_pImpl->_eType != pjson::jsonType::jsonArray || aIndex >= _pImpl->_pValueArray->size()) + return nullptr; + return (*_pImpl->_pValueArray)[aIndex]; +} - // Applies configured size/complexity gates before ECMAScript regex_search. - // Policy or syntax failures are validation errors, distinct from no match. - bool evaluateRegex(const std::string& subject, const std::string& pattern, - const std::string& path, pjsonImpl::SchemaErrorSink& errors, - pjsonImpl::SchemaValidationCtx& ctx, bool& matches) { - matches = false; - if (ctx.options.maxRegexSubjectBytes != 0 && - subject.size() > ctx.options.maxRegexSubjectBytes) { - errors.push_back( - SchemaError(path, "string exceeds regex safety limit (" + - std::to_string(subject.size()) + " bytes, limit " + - std::to_string(ctx.options.maxRegexSubjectBytes) + ")")); - return false; - } - - pjsonImpl::RegexCacheEntry& cached = ctx.regexCache[pattern]; - if (cached.state == pjsonImpl::RegexCacheEntry::Uninitialized) { - if (!chargeLoopWork(ctx, errors, path, pattern.size() + size_t(1))) - return false; - if (ctx.options.maxRegexPatternBytes != 0 && - pattern.size() > ctx.options.maxRegexPatternBytes) { - cached.state = pjsonImpl::RegexCacheEntry::PatternTooLarge; - } else if (!ctx.options.allowUnsafeRegex && !pjsonImpl::_isSafeRegex(pattern)) { - cached.state = pjsonImpl::RegexCacheEntry::UnsafePattern; - } else { - try { - cached.expression.assign(pattern, std::regex::ECMAScript); - cached.state = pjsonImpl::RegexCacheEntry::Ready; - } catch (const std::regex_error&) { - cached.state = pjsonImpl::RegexCacheEntry::InvalidPattern; - } - } - } +// Key/index extraction overloads combine non-mutating lookup with exact +// tryGet conversion and leave output parameters unchanged on any miss. The +// std::string forms are length-aware so embedded-NUL keys resolve correctly. +bool pjson::tryGet(const std::string& aKey, int64_t& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const std::string& aKey, uint64_t& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const std::string& aKey, double& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const std::string& aKey, bool& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const std::string& aKey, std::string& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const std::string& aKey, StringView& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const char* aKey, int64_t& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const char* aKey, uint64_t& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const char* aKey, double& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const char* aKey, bool& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const char* aKey, std::string& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(const char* aKey, StringView& aResult) const { + const pjson* value = find(aKey); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(int aIndex, int64_t& aResult) const noexcept { + const pjson* value = find(aIndex); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(int aIndex, uint64_t& aResult) const noexcept { + const pjson* value = find(aIndex); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(int aIndex, double& aResult) const noexcept { + const pjson* value = find(aIndex); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(int aIndex, bool& aResult) const noexcept { + const pjson* value = find(aIndex); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(int aIndex, std::string& aResult) const { + const pjson* value = find(aIndex); + return value != nullptr && value->tryGet(aResult); +} +bool pjson::tryGet(int aIndex, StringView& aResult) const noexcept { + const pjson* value = find(aIndex); + return value != nullptr && value->tryGet(aResult); +} - if (cached.state == pjsonImpl::RegexCacheEntry::PatternTooLarge) { - errors.push_back(SchemaError(path, "schema regex pattern exceeds safety limit")); - return false; - } - if (cached.state == pjsonImpl::RegexCacheEntry::UnsafePattern) { - errors.push_back(SchemaError(path, "schema regex pattern rejected by safety policy")); - return false; - } - if (cached.state == pjsonImpl::RegexCacheEntry::InvalidPattern) { - errors.push_back(SchemaError(path, "schema has an invalid regex pattern")); - return false; - } - if (!chargeLoopWork(ctx, errors, path, subject.size() + size_t(1))) - return false; - matches = std::regex_search(subject, cached.expression); - return true; - } -} // namespace -/*static*/ -// Schema equality mirrors public structural equality while charging every -// visited value and compared string/key against the validation work budget. -bool pjsonImpl::_equalWithBudget(const pjson& aLeft, const pjson& aRight, SchemaValidationCtx& aCtx, - SchemaErrorSink& aErrors, const std::string& aPath, bool& aEqual) { - struct Pair { - const pjson* left; - const pjson* right; - }; - std::vector work; - Pair root = {&aLeft, &aRight}; - work.push_back(root); - aEqual = false; +//===----------------------------------------------------------------------===// - while (!work.empty()) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - const Pair current = work.back(); - work.pop_back(); - const pjson& left = *current.left; - const pjson& right = *current.right; - const bool leftNumber = left.isNumber(); - const bool rightNumber = right.isNumber(); - if (leftNumber && rightNumber) { - if (_compareNumbers(left, right) != 0) - return true; - continue; - } - if (left._eType != right._eType) - return true; +// Container queries and mutation +//===----------------------------------------------------------------------===// - switch (left._eType) { - case jsonType::jsonNull: - break; - case jsonType::jsonString: { - const size_t bytes = std::max(left._uValue._pValueString->size(), - right._uValue._pValueString->size()); - if (!chargeLoopWork(aCtx, aErrors, aPath, bytes)) - return false; - if (*left._uValue._pValueString != *right._uValue._pValueString) - return true; - break; - } - case jsonType::jsonNumberInt: - case jsonType::jsonNumberDouble: - break; // numeric pairs were handled above - case jsonType::jsonBoolean: - if (left._uValue._valueBool != right._uValue._valueBool) - return true; - break; - case jsonType::jsonArray: - if (left._uValue._pValueArray->size() != right._uValue._pValueArray->size()) - return true; - for (size_t i = 0; i < left._uValue._pValueArray->size(); ++i) { - Pair child = {(*left._uValue._pValueArray)[i], - (*right._uValue._pValueArray)[i]}; - work.push_back(child); - } - break; - case jsonType::jsonObject: { - if (left._uValue._pValueMap->size() != right._uValue._pValueMap->size()) - return true; - ObjectStorage::const_iterator l = left._uValue._pValueMap->begin(); - ObjectStorage::const_iterator r = right._uValue._pValueMap->begin(); - for (; l != left._uValue._pValueMap->end(); ++l, ++r) { - if (!chargeLoopWork(aCtx, aErrors, aPath, - std::max(l->first.size(), r->first.size()) + size_t(1))) - return false; - if (l->first != r->first) - return true; - Pair child = {l->second, r->second}; - work.push_back(child); - } - break; - } - } +bool pjson::hasKey(const std::string& aKey) const { + if (_pImpl->_eType == pjson::jsonType::jsonObject) { + auto it = _pImpl->_pValueMap->find(aKey); + return (it != _pImpl->_pValueMap->end()); } - aEqual = true; - return true; + return false; } -// Validates aNode against aSchema while sharing reference, recursion, and -// failure-budget state across every recursive branch. -/*static*/ -bool pjsonImpl::_validateCtx(const pjson& aNode, const pjson& aSchema, const std::string& aPath, - SchemaErrorSink& aErrors, SchemaValidationCtx& aCtx) { - if (aCtx.aborted) - return false; - if (!chargeValidationWork(aCtx, aErrors, aPath)) - return false; - if (aCtx.depth >= validationDepthLimit(aCtx.options)) { - failValidationBudget(aCtx, aErrors, aPath, "schema validation depth budget exceeded"); - return false; +// Reports whether an object contains a non-null C-string key. +bool pjson::hasKey(const char* cStr) const { + if (cStr != nullptr) + return hasKey(std::string(cStr)); + return false; +} +// Reports whether an array index resolves under find()'s negative-index rules. +bool pjson::hasIndex(int aIndex) const noexcept { + return find(aIndex) != nullptr; +} +// Returns the member/element count for containers and zero for scalars. +size_t pjson::size() const { + if (_pImpl->_eType == pjson::jsonType::jsonArray) { + return _pImpl->_pValueArray->size(); } - SchemaDepthGuard depthGuard(aCtx); - ActiveRefGuard activeRefGuard(aCtx.activeRefs); - const pjson* currentSchema = &aSchema; - - // Resolve consecutive local references without consuming native stack. - // Each hop still behaves like a logical _validateCtx invocation: it charges - // work, enters the depth budget, and keeps its active pair until the final - // target has been evaluated. Draft-07 reference objects ignore siblings. - for (;;) { - // A boolean schema accepts (true) or rejects (false) everything. - if (currentSchema->isBool()) { - if (!pjsonImpl::_boolean(*currentSchema)) { - aErrors.push_back(SchemaError(aPath, "schema is false; no value is valid here")); - return false; - } - return true; - } - // Only object schemas carry keywords; anything else is treated as "accept". - if (!currentSchema->isObject()) - return true; - - // Draft-07 treats an object containing a string $ref as a reference - // object: all sibling keywords are ignored. Only same-document fragment - // references are supported; percent-decoding precedes RFC 6901 decoding. - const pjson* ref = currentSchema->find("$ref"); - if (ref == nullptr || !ref->isString()) + if (_pImpl->_eType == pjson::jsonType::jsonObject) { + return _pImpl->_pValueMap->size(); + } + return 0; +} +// Reports whether size() is zero, so every scalar is considered empty. +bool pjson::empty() const { + return size() == 0; +} +// Clears container children without changing container type; scalars become null. +void pjson::clear() { + // Arrays and maps become empty containers of the same type; anything else + // resets to null. + switch (_pImpl->_eType) { + case pjson::jsonType::jsonArray: { + pjsonImpl::_disposeChildren(*this); + _pImpl->_pValueArray->clear(); break; - - const std::string refText = pjsonImpl::_string(*ref); - if (!refText.empty() && refText[0] != '#') { - aErrors.push_back(SchemaError(aPath, "non-local $ref is not supported: " + refText)); - return false; - } - if (aCtx.refResolutions >= validationRefLimit(aCtx.options)) { - failValidationBudget(aCtx, aErrors, aPath, "schema $ref resolution budget exceeded"); - return false; - } - ++aCtx.refResolutions; - - std::string pointer; - const std::string fragment = refText.empty() ? std::string() : refText.substr(1); - if (!decodeSchemaFragment(fragment, pointer)) { - aErrors.push_back(SchemaError(aPath, "malformed local $ref fragment: " + refText)); - return false; - } - - pjson::PointerError pointerError; - const pjson* target = aCtx.rootSchema.findPointer(pointer, pointerError); - if (target == nullptr) { - const bool malformed = pointerError.code == pjson::PointerError::InvalidSyntax || - pointerError.code == pjson::PointerError::InvalidEscape || - pointerError.code == pjson::PointerError::InvalidArrayIndex || - pointerError.code == pjson::PointerError::AppendTokenNotAllowed; - aErrors.push_back( - SchemaError(aPath, std::string(malformed ? "malformed" : "unresolved") + - " local $ref: " + refText)); - return false; - } - - const std::pair active(&aNode, target); - if (std::find(aCtx.activeRefs.begin(), aCtx.activeRefs.end(), active) != - aCtx.activeRefs.end()) { - aErrors.push_back(SchemaError(aPath, "local $ref cycle detected: " + refText)); - return false; } - activeRefGuard.push(&aNode, target); - - if (!chargeValidationWork(aCtx, aErrors, aPath)) - return false; - if (aCtx.depth >= validationDepthLimit(aCtx.options)) { - failValidationBudget(aCtx, aErrors, aPath, "schema validation depth budget exceeded"); - return false; + case pjson::jsonType::jsonObject: { + pjsonImpl::_disposeChildren(*this); + _pImpl->_pValueMap->clear(); + break; } - depthGuard.enterResolvedReference(); - currentSchema = target; + default: + reset(); + break; } - - const pjson& schema = *currentSchema; - const size_t before = aErrors.size(); - - // ---- type ---- - if (const pjson* t = schema.find("type")) { - if (t->isString()) { - if (!_typeMatches(aNode, pjsonImpl::_string(*t))) { - aErrors.push_back(SchemaError(aPath, "expected type " + pjsonImpl::_string(*t) + - ", got " + _typeName(aNode))); - } - } else if (t->isArray()) { - bool matched = false; - std::string names; - for (const pjson* e : pjsonImpl::_array(*t)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - if (e->isString()) { - if (!names.empty()) - names += ", "; - names += pjsonImpl::_string(*e); - if (_typeMatches(aNode, pjsonImpl::_string(*e))) { - matched = true; - break; - } - } - } - if (!matched) { - aErrors.push_back(SchemaError(aPath, "expected one of type [" + names + "], got " + - _typeName(aNode))); - } +} +// Returns copied object keys in the private container's unspecified native order. +std::vector pjson::keys() const { + std::vector result; + if (_pImpl->_eType == pjson::jsonType::jsonObject) { + result.reserve(_pImpl->_pValueMap->size()); + for (const auto& kv : *_pImpl->_pValueMap) { + result.push_back(kv.first); } - // A malformed "type" (neither string nor array) is ignored. } - - // ---- const ---- - if (const pjson* cst = schema.find("const")) { - bool equal = false; - if (!_equalWithBudget(aNode, *cst, aCtx, aErrors, aPath, equal)) - return false; - if (!equal) { - aErrors.push_back(SchemaError(aPath, "value does not equal the required const")); + return result; +} +bool pjson::erase(const std::string& aKey) { + if (_pImpl->_eType == pjson::jsonType::jsonObject) { + auto it = _pImpl->_pValueMap->find(aKey); + if (it != _pImpl->_pValueMap->end()) { + pjsonImpl::_destroyNode(it->second); + _pImpl->_pValueMap->erase(it); + return true; } } - - // ---- enum ---- - if (const pjson* en = schema.find("enum")) { - if (en->isArray()) { - bool found = false; - for (const pjson* opt : pjsonImpl::_array(*en)) { - bool equal = false; - if (!_equalWithBudget(aNode, *opt, aCtx, aErrors, aPath, equal)) - return false; - if (equal) { - found = true; - break; - } - } - if (!found) { - aErrors.push_back(SchemaError(aPath, "value is not in the allowed enum")); - } - } + return false; +} +// Removes an object member and destroys its owned subtree. +bool pjson::erase(const char* aKey) { + if (aKey != nullptr) + return erase(std::string(aKey)); + return false; +} +// Removes an array element and destroys its owned subtree, shifting later indices. +bool pjson::erase(size_t aIndex) { + if (_pImpl->_eType == pjson::jsonType::jsonArray && aIndex < _pImpl->_pValueArray->size()) { + pjsonImpl::_destroyNode((*_pImpl->_pValueArray)[aIndex]); + _pImpl->_pValueArray->erase(_pImpl->_pValueArray->begin() + + static_cast(aIndex)); + return true; } - - // ---- numeric constraints ---- - if (aNode.isNumber()) { - if (const pjson* m = schema.find("minimum")) { - if (m->isNumber() && _compareNumbers(aNode, *m) < 0) { - addSchemaError(aCtx, aErrors, aPath, - "value " + formatNumber(aNode) + " is below minimum " + - formatNumber(*m)); - } - } - if (const pjson* m = schema.find("maximum")) { - const int comparison = m->isNumber() ? _compareNumbers(aNode, *m) : 2; - if (comparison != 2 && comparison > 0) { - addSchemaError(aCtx, aErrors, aPath, - "value " + formatNumber(aNode) + " is above maximum " + - formatNumber(*m)); - } - } - if (const pjson* m = schema.find("exclusiveMinimum")) { - const int comparison = m->isNumber() ? _compareNumbers(aNode, *m) : 2; - if (comparison <= 0) { - addSchemaError(aCtx, aErrors, aPath, - "value " + formatNumber(aNode) + - " is not greater than exclusiveMinimum " + formatNumber(*m)); - } - } - if (const pjson* m = schema.find("exclusiveMaximum")) { - const int comparison = m->isNumber() ? _compareNumbers(aNode, *m) : 2; - if (comparison != 2 && comparison >= 0) { - addSchemaError(aCtx, aErrors, aPath, - "value " + formatNumber(aNode) + - " is not less than exclusiveMaximum " + formatNumber(*m)); - } - } - if (const pjson* m = schema.find("multipleOf")) { - if (m->isNumber() && !isExactMultiple(aNode, *m)) { - addSchemaError(aCtx, aErrors, aPath, - "value " + formatNumber(aNode) + " is not a multiple of " + - formatNumber(*m)); - } + return false; +} +// Compares stored JSON numbers exactly across signed, unsigned, and double +// representations without rounding an integer through binary64. The result is +// -1/0/1, or 2 when a NaN makes the ordering unordered. +int pjsonImpl::_compareNumbers(const pjson& aLeft, const pjson& aRight) { + const pjson::jsonType lt = aLeft._pImpl->_eType; + const pjson::jsonType rt = aRight._pImpl->_eType; + + // ---- integer vs integer (any signedness) ---- + if (lt != pjson::jsonNumberDouble && rt != pjson::jsonNumberDouble) { + const bool lu = lt == pjson::jsonNumberUInt; + const bool ru = rt == pjson::jsonNumberUInt; + if (!lu && !ru) { + const int64_t l = aLeft._pImpl->_valueInt; + const int64_t r = aRight._pImpl->_valueInt; + return l < r ? -1 : (l > r ? 1 : 0); + } + if (lu && ru) { + const uint64_t l = aLeft._pImpl->_valueUInt; + const uint64_t r = aRight._pImpl->_valueUInt; + return l < r ? -1 : (l > r ? 1 : 0); + } + // One signed, one unsigned. A negative signed value is always smaller. + const int64_t s = lu ? aRight._pImpl->_valueInt : aLeft._pImpl->_valueInt; + const uint64_t u = lu ? aLeft._pImpl->_valueUInt : aRight._pImpl->_valueUInt; + int cmp; + if (s < 0) { + cmp = -1; // signed < unsigned + } else { + const uint64_t su = static_cast(s); + cmp = su < u ? -1 : (su > u ? 1 : 0); } + // cmp expresses (signed operand) vs (unsigned operand); flip if the + // unsigned operand was on the left. + return lu ? -cmp : cmp; } - // ---- string constraints ---- - if (aNode.isString()) { - const std::string& s = *aNode._uValue._pValueString; - size_t length = 0; - if (!unicodeLength(s, aCtx, aErrors, aPath, length)) - return false; - if (const pjson* m = schema.find("minLength")) { - size_t bound = 0; - bool aboveRange = false; - if (schemaSize(*m, bound, aboveRange) && (aboveRange || length < bound)) - addSchemaError(aCtx, aErrors, aPath, - "string length " + std::to_string(length) + " is below minLength " + - formatNumber(*m)); - } - if (const pjson* m = schema.find("maxLength")) { - size_t bound = 0; - bool aboveRange = false; - if (schemaSize(*m, bound, aboveRange) && !aboveRange && length > bound) - addSchemaError(aCtx, aErrors, aPath, - "string length " + std::to_string(length) + " is above maxLength " + - formatNumber(*m)); - } - if (const pjson* p = schema.find("pattern")) { - if (p->isString()) { - const std::string pattern = pjsonImpl::_string(*p); - bool matches = false; - if (evaluateRegex(s, pattern, aPath, aErrors, aCtx, matches) && !matches) - aErrors.push_back( - SchemaError(aPath, "string does not match pattern /" + pattern + "/")); - } - } - if (aCtx.options.validateFormats) { - if (const pjson* format = schema.find("format")) { - if (format->isString()) { - bool known = false; - if (!knownFormatValid(pjsonImpl::_string(*format), s, known) && known) - aErrors.push_back(SchemaError(aPath, "string is not a valid " + - pjsonImpl::_string(*format) + - " format")); - } - } - } + // ---- double vs double ---- + if (lt == pjson::jsonNumberDouble && rt == pjson::jsonNumberDouble) { + const double left = aLeft._pImpl->_valueDouble; + const double right = aRight._pImpl->_valueDouble; + if (std::isnan(left) || std::isnan(right)) + return 2; + if (left < right) + return -1; + return left > right ? 1 : 0; } - // ---- array constraints ---- - if (aNode.isArray()) { - const PJSONARRAY& arr = *aNode._uValue._pValueArray; - if (const pjson* m = schema.find("minItems")) { - size_t bound = 0; - bool aboveRange = false; - if (schemaSize(*m, bound, aboveRange) && (aboveRange || arr.size() < bound)) - addSchemaError(aCtx, aErrors, aPath, - "array has " + std::to_string(arr.size()) + - " items, below minItems " + formatNumber(*m)); - } - if (const pjson* m = schema.find("maxItems")) { - size_t bound = 0; - bool aboveRange = false; - if (schemaSize(*m, bound, aboveRange) && !aboveRange && arr.size() > bound) - addSchemaError(aCtx, aErrors, aPath, - "array has " + std::to_string(arr.size()) + - " items, above maxItems " + formatNumber(*m)); - } - if (const pjson* u = schema.find("uniqueItems")) { - if (u->isBool() && pjsonImpl::_boolean(*u)) { - bool dup = false; - for (size_t i = 0; i < arr.size() && !dup; ++i) { - for (size_t j = i + 1; j < arr.size(); ++j) { - bool equal = false; - if (!_equalWithBudget(*arr[i], *arr[j], aCtx, aErrors, aPath, equal)) - return false; - if (equal) { - dup = true; - break; - } - } - } - if (dup) { - aErrors.push_back(SchemaError(aPath, "array items are not unique")); - } + // ---- integer vs double ---- + const bool intOnLeft = lt != pjson::jsonNumberDouble; + const pjson& intNode = intOnLeft ? aLeft : aRight; + const double floating = intOnLeft ? aRight._pImpl->_valueDouble : aLeft._pImpl->_valueDouble; + if (std::isnan(floating)) + return 2; + + // Compare the integer against the double exactly. Represent the integer's + // value and compare via a double truncation plus fractional tiebreak. + int intVsDouble = 0; + if (intNode._pImpl->_eType == pjson::jsonNumberUInt) { + const uint64_t integer = intNode._pImpl->_valueUInt; + if (floating >= 18446744073709551616.0) { // 2^64 + intVsDouble = -1; + } else if (floating < 0.0) { + intVsDouble = 1; + } else { + const uint64_t truncated = static_cast(floating); + if (integer != truncated) { + intVsDouble = integer < truncated ? -1 : 1; + } else { + const double integralPart = static_cast(truncated); + if (floating != integralPart) + intVsDouble = floating > integralPart ? -1 : 1; } } - if (const pjson* items = schema.find("items")) { - if (items->isArray()) { - const PJSONARRAY& tuple = pjsonImpl::_array(*items); - const size_t count = std::min(arr.size(), tuple.size()); - for (size_t i = 0; i < count && !aCtx.aborted; ++i) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - _validateCtx(*arr[i], *tuple[i], _pointerAppend(aPath, std::to_string(i)), - aErrors, aCtx); - } + } else { + const int64_t integer = intNode._pImpl->_valueInt; + if (floating >= 9223372036854775808.0) { // exact 2^63 + intVsDouble = -1; + } else if (floating < -9223372036854775808.0) { + intVsDouble = 1; + } else { + const int64_t truncated = static_cast(floating); + if (integer != truncated) { + intVsDouble = integer < truncated ? -1 : 1; } else { - for (size_t i = 0; i < arr.size() && !aCtx.aborted; ++i) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - _validateCtx(*arr[i], *items, _pointerAppend(aPath, std::to_string(i)), aErrors, - aCtx); - } + const double integralPart = static_cast(truncated); + if (floating != integralPart) + intVsDouble = floating > integralPart ? -1 : 1; } } } + return intOnLeft ? intVsDouble : -intVsDouble; +} +// Public exact numeric comparison. Delegates to the internal comparator, which +// returns 2 for a NaN-involved (unordered) comparison; that and any non-numeric +// operand are reported as "no ordering" so callers never see a rounded result. +bool pjson::tryCompareNumber(const pjson& aOther, int& aOrder) const noexcept { + if (!isNumber() || !aOther.isNumber()) + return false; + const int order = pjsonImpl::_compareNumbers(*this, aOther); + if (order == 2) // unordered (NaN) + return false; + aOrder = order; + return true; +} +// Deep structural equality. Numbers compare by value across int/double +// (1 == 1.0); arrays element-wise in order; objects by key/value. The walk is +// iterative (an explicit pair work-list) so it never overflows the call stack +// on deeply nested documents. +bool pjson::operator==(const pjson& aOther) const { + struct Pair { + const pjson* a; + const pjson* b; + }; + std::vector work; + Pair root = {this, &aOther}; + work.push_back(root); - // ---- object constraints ---- - if (aNode.isObject()) { - const PJSONMAP& obj = *aNode._uValue._pValueMap; + while (!work.empty()) { + Pair cur = work.back(); + work.pop_back(); + const pjson& lhs = *cur.a; + const pjson& rhs = *cur.b; - if (const pjson* req = schema.find("required")) { - if (req->isArray()) { - for (const pjson* k : pjsonImpl::_array(*req)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - if (k->isString() && obj.find(pjsonImpl::_string(*k)) == obj.end()) { - aErrors.push_back(SchemaError(aPath, "missing required property \"" + - pjsonImpl::_string(*k) + "\"")); - } - } + // Numbers compare across int/uint/double as one family. + bool lNum = lhs.isNumber(); + bool rNum = rhs.isNumber(); + if (lNum && rNum) { + if (pjsonImpl::_compareNumbers(lhs, rhs) != 0) { + return false; } + continue; } - if (const pjson* m = schema.find("minProperties")) { - size_t bound = 0; - bool aboveRange = false; - if (schemaSize(*m, bound, aboveRange) && (aboveRange || obj.size() < bound)) - addSchemaError(aCtx, aErrors, aPath, - "object has " + std::to_string(obj.size()) + - " properties, below minProperties " + formatNumber(*m)); - } - if (const pjson* m = schema.find("maxProperties")) { - size_t bound = 0; - bool aboveRange = false; - if (schemaSize(*m, bound, aboveRange) && !aboveRange && obj.size() > bound) - addSchemaError(aCtx, aErrors, aPath, - "object has " + std::to_string(obj.size()) + - " properties, above maxProperties " + formatNumber(*m)); + + if (lhs._pImpl->_eType != rhs._pImpl->_eType) { + return false; } - const pjson* props = schema.find("properties"); - if (props && props->isObject()) { - for (const auto& kv : pjsonImpl::_object(*props)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) + switch (lhs._pImpl->_eType) { + case pjson::jsonType::jsonNull: + break; + case pjson::jsonType::jsonString: + if (*lhs._pImpl->_pValueString != *rhs._pImpl->_pValueString) return false; - auto it = obj.find(kv.first); - if (it != obj.end()) { - _validateCtx(*it->second, *kv.second, _pointerAppend(aPath, kv.first), aErrors, - aCtx); - } - if (aCtx.aborted) + break; + case pjson::jsonType::jsonBoolean: + if (lhs._pImpl->_valueBool != rhs._pImpl->_valueBool) return false; - } - } - - const pjson* patternProps = schema.find("patternProperties"); - // A set avoids the prior O(properties * matches) membership scan when - // additionalProperties is evaluated after patternProperties. - std::set patternMatched; - if (patternProps && patternProps->isObject()) { - for (const auto& patternSchema : pjsonImpl::_object(*patternProps)) { - for (const auto& kv : obj) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - bool matches = false; - if (evaluateRegex(kv.first, patternSchema.first, - _pointerAppend(aPath, kv.first), aErrors, aCtx, matches) && - matches) { - patternMatched.insert(kv.first); - _validateCtx(*kv.second, *patternSchema.second, - _pointerAppend(aPath, kv.first), aErrors, aCtx); - } - if (aCtx.aborted) - return false; - } - } - } - - if (const pjson* propertyNames = schema.find("propertyNames")) { - for (const auto& kv : obj) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) + break; + case pjson::jsonType::jsonNumberInt: + if (lhs._pImpl->_valueInt != rhs._pImpl->_valueInt) return false; - pjson propertyName; - propertyName = kv.first; - _validateCtx(propertyName, *propertyNames, _pointerAppend(aPath, kv.first), aErrors, - aCtx); - if (aCtx.aborted) + break; + case pjson::jsonType::jsonNumberUInt: + if (lhs._pImpl->_valueUInt != rhs._pImpl->_valueUInt) return false; - } - } - - const pjson* dependentRequired = schema.find("dependentRequired"); - if (dependentRequired && dependentRequired->isObject()) { - for (const auto& dependency : pjsonImpl::_object(*dependentRequired)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) + break; + case pjson::jsonType::jsonNumberDouble: + if (lhs._pImpl->_valueDouble != rhs._pImpl->_valueDouble) return false; - if (obj.find(dependency.first) == obj.end() || !dependency.second->isArray()) - continue; - for (const pjson* required : pjsonImpl::_array(*dependency.second)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - if (required->isString() && - obj.find(pjsonImpl::_string(*required)) == obj.end()) { - aErrors.push_back(SchemaError( - aPath, "property \"" + dependency.first + "\" requires property \"" + - pjsonImpl::_string(*required) + "\"")); - } - } - } - } - - const pjson* dependencies = schema.find("dependencies"); - if (dependencies && dependencies->isObject()) { - for (const auto& dependency : pjsonImpl::_object(*dependencies)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) + break; + case pjson::jsonType::jsonArray: { + if (lhs._pImpl->_pValueArray->size() != rhs._pImpl->_pValueArray->size()) { return false; - if (obj.find(dependency.first) == obj.end()) - continue; - if (dependency.second->isArray()) { - for (const pjson* required : pjsonImpl::_array(*dependency.second)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - if (required->isString() && - obj.find(pjsonImpl::_string(*required)) == obj.end()) { - aErrors.push_back(SchemaError(aPath, "property \"" + dependency.first + - "\" requires property \"" + - pjsonImpl::_string(*required) + - "\"")); - } - } - } else { - _validateCtx(aNode, *dependency.second, aPath, aErrors, aCtx); - if (aCtx.aborted) - return false; } - } - } - - if (const pjson* addl = schema.find("additionalProperties")) { - for (const auto& kv : obj) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - const bool declared = - props && props->isObject() && - pjsonImpl::_object(*props).find(kv.first) != pjsonImpl::_object(*props).end(); - const bool matched = patternMatched.find(kv.first) != patternMatched.end(); - if (declared || matched) - continue; - if (addl->isBool()) { - if (!pjsonImpl::_boolean(*addl)) { - aErrors.push_back( - SchemaError(_pointerAppend(aPath, kv.first), - "additional property \"" + kv.first + "\" is not allowed")); - } - } else { - _validateCtx(*kv.second, *addl, _pointerAppend(aPath, kv.first), aErrors, aCtx); + for (size_t i = 0; i < lhs._pImpl->_pValueArray->size(); ++i) { + Pair p = {(*lhs._pImpl->_pValueArray)[i], (*rhs._pImpl->_pValueArray)[i]}; + work.push_back(p); } - if (aCtx.aborted) - return false; - } - } - } - - // ---- logical combinators ---- - // allOf contributes each branch's concrete errors. anyOf, oneOf, and not - // are speculative: branches validate into scratch vectors so only the - // combinator-level outcome is exposed to callers. Budget aborts bypass that - // isolation through failValidationBudget and stop all remaining work. - if (const pjson* allOf = schema.find("allOf")) { - if (allOf->isArray()) { - for (const pjson* sub : pjsonImpl::_array(*allOf)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - _validateCtx(aNode, *sub, aPath, aErrors, aCtx); - if (aCtx.aborted) - return false; + break; } - } - } - if (const pjson* anyOf = schema.find("anyOf")) { - if (anyOf->isArray()) { - bool any = false; - for (const pjson* sub : pjsonImpl::_array(*anyOf)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) + case pjson::jsonType::jsonObject: { + if (lhs._pImpl->_pValueMap->size() != rhs._pImpl->_pValueMap->size()) { return false; - std::vector scratch; - SchemaErrorSink scratchSink(scratch, aCtx, false); - if (_validateCtx(aNode, *sub, aPath, scratchSink, aCtx)) { - any = true; - break; } - if (aCtx.aborted) - return false; - } - if (!any) { - aErrors.push_back(SchemaError(aPath, "value does not match any schema in anyOf")); - } - } - } - if (const pjson* oneOf = schema.find("oneOf")) { - if (oneOf->isArray()) { - int matches = 0; - for (const pjson* sub : pjsonImpl::_array(*oneOf)) { - if (!chargeLoopWork(aCtx, aErrors, aPath)) - return false; - std::vector scratch; - SchemaErrorSink scratchSink(scratch, aCtx, false); - if (_validateCtx(aNode, *sub, aPath, scratchSink, aCtx)) - ++matches; - if (aCtx.aborted) - return false; - } - if (matches != 1) { - aErrors.push_back(SchemaError(aPath, "value matched " + std::to_string(matches) + - " schemas in oneOf (exactly 1 required)")); + for (pjsonImpl::ObjectStorage::const_iterator it = lhs._pImpl->_pValueMap->begin(); + it != lhs._pImpl->_pValueMap->end(); ++it) { + pjsonImpl::ObjectStorage::const_iterator matching = + rhs._pImpl->_pValueMap->find(it->first); + if (matching == rhs._pImpl->_pValueMap->end()) + return false; + Pair p = {it->second, matching->second}; + work.push_back(p); + } + break; } } } - const pjson* nots = schema.find("not"); - if (nots != nullptr && (nots->isBool() || nots->isObject())) { - std::vector scratch; - SchemaErrorSink scratchSink(scratch, aCtx, false); - if (_validateCtx(aNode, *nots, aPath, scratchSink, aCtx)) { - aErrors.push_back(SchemaError(aPath, "value must not match the \"not\" schema")); - } - if (aCtx.aborted) - return false; - } - - return !aCtx.aborted && aErrors.size() == before; -} -/*static*/ -// Runs one noexcept validation session. Unexpected failures become best-effort -// diagnostics rather than escaping across the public API boundary. -bool pjsonImpl::_validate(const pjson& aNode, const pjson& aSchema, const std::string& aPath, - std::vector& aErrors, const SchemaOptions& aOpts) noexcept { - try { - SchemaValidationCtx ctx(aSchema, aOpts, &aErrors); - SchemaErrorSink sink(aErrors, ctx); - return _validateCtx(aNode, aSchema, aPath, sink, ctx); - } catch (const SchemaBudgetExceeded&) { - return false; - } catch (const std::bad_alloc&) { - bestEffortSchemaError(aErrors, aPath.c_str(), "schema validation ran out of memory"); - } catch (const std::exception&) { - bestEffortSchemaError(aErrors, aPath.c_str(), - "schema validation failed with an internal exception"); - } catch (...) { - bestEffortSchemaError(aErrors, aPath.c_str(), - "schema validation failed with an unknown exception"); - } - return false; -} -// Validates and returns only the aggregate result, discarding diagnostics. -bool pjson::validate(const pjson& aSchema, const SchemaOptions& aOpts) const noexcept { - std::vector errors; - return pjsonImpl::_validate(*this, aSchema, "", errors, aOpts); + return true; } -// Validates from the root path and appends diagnostics to aErrors. -bool pjson::validate(const pjson& aSchema, std::vector& aErrors, - const SchemaOptions& aOpts) const noexcept { - return pjsonImpl::_validate(*this, aSchema, "", aErrors, aOpts); +// Implements inequality as the exact complement of structural equality. +bool pjson::operator!=(const pjson& aOther) const { + return !(*this == aOther); } diff --git a/pjsonlib/src/pjson_internal.h b/pjsonlib/src/pjson_internal.h new file mode 100644 index 0000000..4f9673d --- /dev/null +++ b/pjsonlib/src/pjson_internal.h @@ -0,0 +1,164 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// pjson_internal.h — private, library-only header. +// +// This header is NOT installed and is not part of the public API. It defines +// the pjsonImpl friend struct and shared internal aliases so the library +// implementation can span the DOM, serialization, Pointer, and Patch +// translation units while keeping the public pjson.h declaration-focused. +// Parser implementation details live in pjson_parser_internal.h and depend on +// this core header; this header never depends on the parser. +//===----------------------------------------------------------------------===// +#ifndef PRAVEENJSON_INTERNAL_H +#define PRAVEENJSON_INTERNAL_H + +#include "pjson.h" + +#include +#include +#include +#include +#include + +//===----------------------------------------------------------------------===// +// pjsonImpl — private DOM, ownership, and serialization helpers. +// +// Keeping implementation-only DOM operations in one friend struct leaves +// pjson.h declaration-focused while allowing these helpers to maintain DOM +// invariants. pJsonSchemaValidator does not include this header. +//===----------------------------------------------------------------------===// +struct ByteDance::pjsonImpl { + typedef std::vector ArrayStorage; + struct ObjectHash { + size_t operator()(const std::string& aKey) const noexcept; + }; + typedef std::unordered_map ObjectStorage; + + // One suspended container in the iterative serializer. Exactly one of + // array/object is active according to isObject; the associated cursor + // always denotes the next child to emit. + struct SerializeFrame { + bool isObject; + size_t depth; + bool first; + const ArrayStorage* array; + size_t arrayIndex; + const ObjectStorage* object; + ObjectStorage::const_iterator objectIt; + }; + + static int _utf8Len(const char* src, size_t pos, size_t end); + static std::string _formatDouble(double aValue); + template + static bool _writeEscapedTo(Sink& aOut, const std::string& aIn, bool bEscapeNonAscii); + template + static bool _openOrEmit(Sink& aOut, const pjson* aValue, size_t aDepth, + const pjson::SerializeOptions& aOpts, + std::vector& aFrames); + template + static bool _writeValueTo(Sink& aOut, const pjson& aValue, + const pjson::SerializeOptions& aOpts); + static void _appendValue(std::string& aOut, const pjson& aValue, + const pjson::SerializeOptions& aOpts); + static bool _writeValue(std::ostream& aOut, const pjson& aValue, + const pjson::SerializeOptions& aOpts); + + // Internal typed/storage access keeps representation and permissive + // conversion helpers out of the public API. Callers first establish type. + static ArrayStorage& _array(pjson& aValue) { return *aValue._pImpl->_pValueArray; } + static const ArrayStorage& _array(const pjson& aValue) { return *aValue._pImpl->_pValueArray; } + static ObjectStorage& _object(pjson& aValue) { return *aValue._pImpl->_pValueMap; } + static const ObjectStorage& _object(const pjson& aValue) { return *aValue._pImpl->_pValueMap; } + static int64_t _integer(const pjson& aValue) { return aValue._pImpl->_valueInt; } + static uint64_t _unsigned(const pjson& aValue) { return aValue._pImpl->_valueUInt; } + static double _floating(const pjson& aValue) { return aValue._pImpl->_valueDouble; } + static double _numberAsDouble(const pjson& aValue) { + if (aValue._pImpl->_eType == pjson::jsonNumberInt) + return static_cast(aValue._pImpl->_valueInt); + if (aValue._pImpl->_eType == pjson::jsonNumberUInt) + return static_cast(aValue._pImpl->_valueUInt); + return aValue._pImpl->_valueDouble; + } + static bool _boolean(const pjson& aValue) { return aValue._pImpl->_valueBool; } + static const std::string& _string(const pjson& aValue) { return *aValue._pImpl->_pValueString; } + // Returns -1, 0, or 1, and 2 when either floating operand is NaN. + static int _compareNumbers(const pjson& aLeft, const pjson& aRight); + + // Iteratively frees every descendant pjson of node's array/map, leaving the + // node's own top-level container allocated but empty (a no-op for scalars). + // Using an explicit work-list instead of the recursive destructor keeps + // teardown safe on arbitrarily deep documents. Marked noexcept: it is + // reached from ~pjson, so an allocation failure here terminates rather than + // escaping a destructor. + static void _disposeChildren(pjson& node) noexcept; + static pjson::Allocator& _defaultAllocator() noexcept; + static pjsonImpl& _nullImpl() noexcept; + static bool _isNullImpl(const pjsonImpl* aImpl) noexcept; + static pjsonImpl* _allocateImpl(pjson::Allocator& aAlloc); + static void _destroyImpl(pjson::Allocator& aAlloc, pjsonImpl* aImpl) noexcept; + static pjson* _allocateNode(pjson::Allocator& aAlloc); + static void _destroyNode(pjson* aValue) noexcept; + + // Internal origin-aware owning pointer. Replaces the former public + // pjsonImpl::OwnedNode/ValueDeleter: parser and mutation helpers still get + // RAII cleanup during construction, but no smart pointer leaks into the + // public API. Destruction routes through _destroyNode for library-created, + // allocator-backed child nodes. Caller-created roots use normal delete. + struct NodeDeleter { + void operator()(pjson* aValue) const noexcept { pjsonImpl::_destroyNode(aValue); } + }; + typedef std::unique_ptr OwnedNode; + + static OwnedNode _makeNode(pjson::Allocator& aAlloc); + static OwnedNode _cloneNode(const pjson& aValue, pjson::Allocator& aAlloc); + + // Instance behavior that must touch pjson's private storage lives here rather + // than as private methods on pjson, so the public header carries no instance + // helper declarations. pjsonImpl is a friend, so these reach private state + // directly. Keeping them static and .cpp-local means a future data-member + // change is contained to this file. + // + // Iteratively deep-copies aFrom's contents into aDst using aDst's allocator. + static void _copyContentsInto(pjson& aDst, const pjson& aFrom); + // O(1) storage exchange for two same-allocator nodes the caller has already + // proven are not aliased (e.g. a target and a freshly cloned/detached value). + // Bypasses the public swap()'s ancestor/descendant guard. + static void _swapStorage(pjson& aLeft, pjson& aRight) noexcept; + // Returns whether aNode is aRoot or lies within aRoot's subtree. + static bool _containsNode(const pjson& aRoot, const pjson* aNode) noexcept; + + //== Data =============================================================== + // Intrusive scratch link used only by allocation-free iterative tree + // destruction. It is null during normal object lifetime. + pjson* _disposeNext; + pjson::jsonType _eType; + union { + ObjectStorage* _pValueMap; + ArrayStorage* _pValueArray; + int64_t _valueInt; + uint64_t _valueUInt; + double _valueDouble; + bool _valueBool; + std::string* _pValueString; + }; + + pjsonImpl() noexcept + : _disposeNext(nullptr) + , _eType(pjson::jsonNull) + , _valueUInt(0) {} +}; + +#endif /* !PRAVEENJSON_INTERNAL_H */ diff --git a/pjsonlib/src/pjson_parser.cpp b/pjsonlib/src/pjson_parser.cpp new file mode 100644 index 0000000..84cf32d --- /dev/null +++ b/pjsonlib/src/pjson_parser.cpp @@ -0,0 +1,1826 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 + +#include "pjson_parser_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ByteDance; + +namespace { + int clampParseDepth(int configured) { + if (configured <= 0) + return 1; + return configured < pJsonParserImpl::DepthHardLimit ? configured + : pJsonParserImpl::DepthHardLimit; + } + + const pJsonParser::Options& effectiveOptions(const pJsonParserImpl* implementation) noexcept { + static const pJsonParser::Options defaults; + return implementation == nullptr ? defaults : implementation->options; + } + + pjson::Allocator& effectiveAllocator(const pJsonParserImpl* implementation) noexcept { + return implementation == nullptr ? pjsonImpl::_defaultAllocator() + : *implementation->allocator; + } +} // namespace + +namespace ByteDance { + pJsonParser::Options::Options() + : maxDepth(512) + , maxNodes(1000000) + , maxInputBytes(size_t(64) * 1024 * 1024) + , duplicateKeys(RejectDuplicateKeys) + , numberPolicy(RejectUnrepresentableNumbers) {} + + pJsonParser::Error::Error() + : ok(true) + , code(None) + , offset(0) + , line(1) + , column(1) {} + + pJsonParser::SaxHandler::~SaxHandler() {} + bool pJsonParser::SaxHandler::onNull() { + return true; + } + bool pJsonParser::SaxHandler::onBool(bool) { + return true; + } + bool pJsonParser::SaxHandler::onInt(int64_t) { + return true; + } + bool pJsonParser::SaxHandler::onUInt(uint64_t) { + return true; + } + bool pJsonParser::SaxHandler::onDouble(double) { + return true; + } + bool pJsonParser::SaxHandler::onString(const std::string&) { + return true; + } + bool pJsonParser::SaxHandler::onStartArray() { + return true; + } + bool pJsonParser::SaxHandler::onEndArray() { + return true; + } + bool pJsonParser::SaxHandler::onStartObject() { + return true; + } + bool pJsonParser::SaxHandler::onKey(const std::string&) { + return true; + } + bool pJsonParser::SaxHandler::onEndObject() { + return true; + } + + pJsonParser::pJsonParser(const Options& options) + : _impl(new pJsonParserImpl(pjsonImpl::_defaultAllocator(), options)) {} + + pJsonParser::pJsonParser(pjson::Allocator& allocator, const Options& options) + : _impl(new pJsonParserImpl(allocator, options)) {} + + pJsonParser::~pJsonParser() { + delete _impl; + } + + pJsonParser::pJsonParser(const pJsonParser& other) + : _impl(new pJsonParserImpl(effectiveAllocator(other._impl), + effectiveOptions(other._impl))) {} + + pJsonParser::pJsonParser(pJsonParser&& other) noexcept + : _impl(other._impl) { + other._impl = nullptr; + } + + pJsonParser& pJsonParser::operator=(const pJsonParser& other) { + if (this == &other) + return *this; + pJsonParserImpl* replacement = + new pJsonParserImpl(effectiveAllocator(other._impl), effectiveOptions(other._impl)); + delete _impl; + _impl = replacement; + return *this; + } + + pJsonParser& pJsonParser::operator=(pJsonParser&& other) noexcept { + if (this == &other) + return *this; + delete _impl; + _impl = other._impl; + other._impl = nullptr; + return *this; + } + + const pJsonParser::Options& pJsonParser::options() const noexcept { + return effectiveOptions(_impl); + } + pjson::Allocator& pJsonParser::allocator() const noexcept { + return effectiveAllocator(_impl); + } + + pjson pJsonParser::parse(const std::string& input) const { + return pJsonParserImpl::parseTop(input.c_str(), input.size(), effectiveOptions(_impl), + nullptr, effectiveAllocator(_impl)); + } + pjson pJsonParser::parse(const char* input, size_t size) const { + return pJsonParserImpl::parseTop(input, size, effectiveOptions(_impl), nullptr, + effectiveAllocator(_impl)); + } + pjson pJsonParser::parse(const std::string& input, Error& error) const { + return pJsonParserImpl::parseTop(input.c_str(), input.size(), effectiveOptions(_impl), + &error, effectiveAllocator(_impl)); + } + pjson pJsonParser::parse(const char* input, size_t size, Error& error) const { + return pJsonParserImpl::parseTop(input, size, effectiveOptions(_impl), &error, + effectiveAllocator(_impl)); + } + pjson pJsonParser::parseStream(std::istream& input) const { + return pJsonParserImpl::parseStream(input, effectiveOptions(_impl), nullptr, + effectiveAllocator(_impl)); + } + pjson pJsonParser::parseStream(std::istream& input, Error& error) const { + return pJsonParserImpl::parseStream(input, effectiveOptions(_impl), &error, + effectiveAllocator(_impl)); + } + bool pJsonParser::parseSax(const std::string& input, pJsonParser::SaxHandler& handler) const { + return pJsonParserImpl::parseSaxTop(input.c_str(), input.size(), handler, + effectiveOptions(_impl), nullptr); + } + bool pJsonParser::parseSax(const char* input, size_t size, + pJsonParser::SaxHandler& handler) const { + return pJsonParserImpl::parseSaxTop(input, size, handler, effectiveOptions(_impl), nullptr); + } + bool pJsonParser::parseSax(const std::string& input, pJsonParser::SaxHandler& handler, + Error& error) const { + return pJsonParserImpl::parseSaxTop(input.c_str(), input.size(), handler, + effectiveOptions(_impl), &error); + } + bool pJsonParser::parseSax(const char* input, size_t size, pJsonParser::SaxHandler& handler, + Error& error) const { + return pJsonParserImpl::parseSaxTop(input, size, handler, effectiveOptions(_impl), &error); + } + bool pJsonParser::parseSaxStream(std::istream& input, pJsonParser::SaxHandler& handler) const { + return pJsonParserImpl::parseSaxStream(input, handler, effectiveOptions(_impl), nullptr); + } + bool pJsonParser::parseSaxStream(std::istream& input, pJsonParser::SaxHandler& handler, + Error& error) const { + return pJsonParserImpl::parseSaxStream(input, handler, effectiveOptions(_impl), &error); + } +} // namespace ByteDance +bool pJsonParserImpl::isWhitespace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} +// Encodes a Unicode code point as UTF-8 and appends it to aOut. +/*static*/ +void pJsonParserImpl::appendUtf8(uint32_t aCodePoint, std::string& aOut) { + if (aCodePoint <= 0x7F) { + aOut += static_cast(aCodePoint); + } else if (aCodePoint <= 0x7FF) { + aOut += static_cast(0xC0 | (aCodePoint >> 6)); + aOut += static_cast(0x80 | (aCodePoint & 0x3F)); + } else if (aCodePoint <= 0xFFFF) { + aOut += static_cast(0xE0 | (aCodePoint >> 12)); + aOut += static_cast(0x80 | ((aCodePoint >> 6) & 0x3F)); + aOut += static_cast(0x80 | (aCodePoint & 0x3F)); + } else { + aOut += static_cast(0xF0 | (aCodePoint >> 18)); + aOut += static_cast(0x80 | ((aCodePoint >> 12) & 0x3F)); + aOut += static_cast(0x80 | ((aCodePoint >> 6) & 0x3F)); + aOut += static_cast(0x80 | (aCodePoint & 0x3F)); + } +} +// Decodes exactly four hexadecimal bytes at aStart into one UTF-16 code unit. +bool pJsonParserImpl::hex4(const char* aSrc, size_t aStart, uint32_t& aOut) { + aOut = 0; + for (size_t k = 0; k < size_t(4); ++k) { + char h = aSrc[aStart + k]; + aOut <<= 4; + if (h >= '0' && h <= '9') + aOut |= static_cast(h - '0'); + else if (h >= 'a' && h <= 'f') + aOut |= static_cast(h - 'a' + 10); + else if (h >= 'A' && h <= 'F') + aOut |= static_cast(h - 'A' + 10); + else + return false; + } + return true; +} +// Returns the length (1..4) of the valid UTF-8 sequence starting at +// src[pos], or 0 if the bytes there are not valid UTF-8. Requires pos < end. +// Formats a finite double with Ryu's proven shortest-round-trip conversion. +// A '.0' suffix is appended when the result would otherwise look like an +// integer, so the value re-parses into the double representation (type-stable). +/*static*/ +bool pJsonParserImpl::parseDouble(const std::string& aText, double& aValue, + bool* aUnderflowToZero) { + if (aUnderflowToZero != nullptr) + *aUnderflowToZero = false; + std::istringstream in(aText); + in.imbue(std::locale::classic()); + in >> std::noskipws >> aValue; + const bool cleanParse = !in.fail() && in.peek() == std::char_traits::eof(); + // libstdc++/libc++ set failbit as well as eofbit for both underflow and + // overflow. Classify the range direction from the decimal exponent instead + // of trusting the implementation-specific saturated result. A negative + // effective decimal exponent cannot overflow binary64, so its finite zero or + // subnormal result is valid; nonnegative range failures are overflow. + if (!cleanParse && (!in.eof() || !std::isfinite(aValue))) + return false; + const size_t signOffset = !aText.empty() && aText[0] == '-' ? 1 : 0; + const size_t exponentMark = aText.find_first_of("eE"); + const size_t significandEnd = exponentMark == std::string::npos ? aText.size() : exponentMark; + const size_t point = aText.find('.', signOffset); + const size_t digitsBeforePoint = point != std::string::npos && point < significandEnd + ? point - signOffset + : significandEnd - signOffset; + size_t digitOrdinal = 0; + size_t firstNonzero = std::string::npos; + for (size_t i = signOffset; i < significandEnd; ++i) { + if (aText[i] == '.') + continue; + if (firstNonzero == std::string::npos && aText[i] != '0') + firstNonzero = digitOrdinal; + ++digitOrdinal; + } + if (firstNonzero == std::string::npos) + return true; // exact zero cannot overflow + if (cleanParse) { + if (aUnderflowToZero != nullptr && aValue == 0.0) + *aUnderflowToZero = true; + return true; + } + + const int64_t kExponentCap = INT64_C(1000000000); + int64_t explicitExponent = 0; + if (exponentMark != std::string::npos) { + size_t i = exponentMark + 1; + bool negative = false; + if (i < aText.size() && (aText[i] == '+' || aText[i] == '-')) { + negative = aText[i] == '-'; + ++i; + } + for (; i < aText.size(); ++i) { + const int digit = aText[i] - '0'; + if (explicitExponent > (kExponentCap - digit) / 10) { + explicitExponent = kExponentCap; + break; + } + explicitExponent = explicitExponent * 10 + digit; + } + if (negative) + explicitExponent = -explicitExponent; + } + const int64_t baseExponent = + static_cast(digitsBeforePoint) - static_cast(firstNonzero) - 1; + const int64_t effectiveExponent = explicitExponent > kExponentCap - baseExponent ? kExponentCap + : explicitExponent < -kExponentCap - baseExponent + ? -kExponentCap + : explicitExponent + baseExponent; + if (effectiveExponent >= 0) + return false; + if (aUnderflowToZero != nullptr && aValue == 0.0) + *aUnderflowToZero = true; + return true; +} + +// Converts one already grammar-validated number token. Both DOM and SAX use +// this routine so storage classification and lossy-number policy cannot drift. +bool pJsonParserImpl::convertNumberToken(const std::string& aText, bool aIsFloat, + pJsonParser::Options::NumberPolicy aPolicy, + pJsonParserImpl::ParsedNumber& aResult, + const char*& aErrorMessage) { + aErrorMessage = nullptr; + const bool allowLossy = aPolicy == pJsonParser::Options::AllowLossyNumbers; + if (!aIsFloat) { + errno = 0; + const long long signedValue = strtoll(aText.c_str(), nullptr, 10); + if (errno != ERANGE) { + aResult.kind = pJsonParserImpl::ParsedNumber::SignedInteger; + aResult.signedValue = static_cast(signedValue); + return true; + } + if (aText.empty() || aText[0] != '-') { + errno = 0; + const unsigned long long unsignedValue = strtoull(aText.c_str(), nullptr, 10); + if (errno != ERANGE) { + aResult.kind = pJsonParserImpl::ParsedNumber::UnsignedInteger; + aResult.unsignedValue = static_cast(unsignedValue); + return true; + } + } + if (!allowLossy) { + aErrorMessage = "integer out of range; enable AllowLossyNumbers to store as double"; + return false; + } + } + + double floatingValue = 0.0; + bool underflowToZero = false; + if (!parseDouble(aText, floatingValue, &underflowToZero) || !std::isfinite(floatingValue)) { + aErrorMessage = "number out of range"; + return false; + } + if (underflowToZero && !allowLossy) { + aErrorMessage = "number underflows to zero; enable AllowLossyNumbers to permit rounding"; + return false; + } + aResult.kind = pJsonParserImpl::ParsedNumber::FloatingPoint; + aResult.floatingValue = floatingValue; + return true; +} +namespace { + //===------------------------------------------------------------------===// + // Parse diagnostics and SAX cursor adapters + //===------------------------------------------------------------------===// + + // Converts a zero-based byte offset into one-based source coordinates. CRLF + // counts as one line ending; a lone CR or LF also starts a new line. + void lineAndColumn(const char* src, size_t size, size_t offset, size_t& line, size_t& column) { + line = 1; + column = 1; + const size_t end = offset < size ? offset : size; + for (size_t i = 0; i < end; ++i) { + if (src[i] == '\r') { + if (i + 1 < end && src[i + 1] == '\n') + ++i; + ++line; + column = 1; + } else if (src[i] == '\n') { + ++line; + column = 1; + } else { + ++column; + } + } + } + + // Maps a parser diagnostic message to a stable pJsonParser::Error::Code. The exact + // message wording may evolve; this keeps the machine-facing category stable + // by classifying on the well-known phrases the parser emits. + pJsonParser::Error::Code classifyParseMessage(const std::string& message) { + if (message.find("UTF-8") != std::string::npos || + message.find("surrogate") != std::string::npos || + message.find("escape") != std::string::npos || message.find("\\u") != std::string::npos) + return pJsonParser::Error::InvalidEncoding; + if (message.find("duplicate object key") != std::string::npos) + return pJsonParser::Error::DuplicateKey; + if (message.find("out of range") != std::string::npos || + message.find("number") != std::string::npos) + return pJsonParser::Error::NumberRange; + if (message.find("nesting depth") != std::string::npos) + return pJsonParser::Error::DepthLimit; + if (message.find("maxInputBytes") != std::string::npos) + return pJsonParser::Error::InputLimit; + if (message.find("maxNodes") != std::string::npos || + message.find("node budget") != std::string::npos) + return pJsonParser::Error::NodeLimit; + if (message.find("out of memory") != std::string::npos) + return pJsonParser::Error::AllocationFailure; + if (message.find("stream read") != std::string::npos) + return pJsonParser::Error::StreamError; + return pJsonParser::Error::Syntax; + } + + pJsonParser::Error::Code classifyParseMessage(const char* message) noexcept { + if (std::strstr(message, "UTF-8") != nullptr || + std::strstr(message, "surrogate") != nullptr || + std::strstr(message, "escape") != nullptr || std::strstr(message, "\\u") != nullptr) + return pJsonParser::Error::InvalidEncoding; + if (std::strstr(message, "duplicate object key") != nullptr) + return pJsonParser::Error::DuplicateKey; + if (std::strstr(message, "out of range") != nullptr || + std::strstr(message, "number") != nullptr) + return pJsonParser::Error::NumberRange; + if (std::strstr(message, "nesting depth") != nullptr) + return pJsonParser::Error::DepthLimit; + if (std::strstr(message, "maxInputBytes") != nullptr) + return pJsonParser::Error::InputLimit; + if (std::strstr(message, "maxNodes") != nullptr || + std::strstr(message, "node budget") != nullptr) + return pJsonParser::Error::NodeLimit; + if (std::strstr(message, "out of memory") != nullptr) + return pJsonParser::Error::AllocationFailure; + if (std::strstr(message, "stream read") != nullptr) + return pJsonParser::Error::StreamError; + return pJsonParser::Error::Syntax; + } + + // Publishes a buffer-parser failure, deriving source coordinates from the + // authoritative byte offset. A null destination intentionally discards it. + // The code is classified from the message unless an explicit one is given. + void setParseError(pJsonParser::Error* err, const char* src, size_t size, size_t offset, + const std::string& message, + pJsonParser::Error::Code code = pJsonParser::Error::None) noexcept { + if (!err) + return; + err->ok = false; + err->code = code == pJsonParser::Error::None ? classifyParseMessage(message) : code; + err->offset = offset; + lineAndColumn(src, size, offset, err->line, err->column); + try { + err->message = message; + } catch (...) { + err->message.clear(); + } + } + + void setParseError(pJsonParser::Error* err, const char* src, size_t size, size_t offset, + const char* message, + pJsonParser::Error::Code code = pJsonParser::Error::None) noexcept { + if (!err) + return; + err->ok = false; + err->code = code == pJsonParser::Error::None ? classifyParseMessage(message) : code; + err->offset = offset; + lineAndColumn(src, size, offset, err->line, err->column); + try { + err->message = message; + } catch (...) { + err->message.clear(); + } + } + + // Restores the public error object to its successful, start-of-input state. + void resetParseError(pJsonParser::Error* err) { + if (!err) + return; + err->ok = true; + err->code = pJsonParser::Error::None; + err->offset = 0; + err->line = 1; + err->column = 1; + err->message.clear(); + } + + // Internal control-flow exception used to unwind immediately when a SAX + // callback returns false; parseDocument converts it back into pJsonParser::Error. + class SaxParseCancelled : public std::exception { + public: + // Supplies a stable diagnostic if cancellation escapes an internal frame. + const char* what() const noexcept override { return "SAX parse aborted"; } + }; + + template + bool scanJsonNumber(Cursor& cursor, std::string& text, bool& isFloat, + const char*& errorMessage) { + text.clear(); + isFloat = false; + errorMessage = nullptr; + char ch = 0; + if (!cursor.peek(ch)) { + errorMessage = "unexpected end of input; expected a value"; + return false; + } + if (ch == '-') { + if (!cursor.take(ch)) + return false; + text.push_back(ch); + if (!cursor.peek(ch)) { + errorMessage = "invalid number: expected digit"; + return false; + } + } + if (ch == '0') { + if (!cursor.take(ch)) + return false; + text.push_back(ch); + } else if (ch >= '1' && ch <= '9') { + do { + if (!cursor.take(ch)) + return false; + text.push_back(ch); + } while (cursor.peek(ch) && ch >= '0' && ch <= '9'); + } else { + errorMessage = "invalid number: expected digit"; + return false; + } + if (cursor.peek(ch) && ch == '.') { + isFloat = true; + if (!cursor.take(ch)) + return false; + text.push_back(ch); + if (!cursor.peek(ch) || ch < '0' || ch > '9') { + errorMessage = "invalid number: '.' must be followed by a digit"; + return false; + } + do { + if (!cursor.take(ch)) + return false; + text.push_back(ch); + } while (cursor.peek(ch) && ch >= '0' && ch <= '9'); + } + if (cursor.peek(ch) && (ch == 'e' || ch == 'E')) { + isFloat = true; + if (!cursor.take(ch)) + return false; + text.push_back(ch); + if (cursor.peek(ch) && (ch == '+' || ch == '-')) { + if (!cursor.take(ch)) + return false; + text.push_back(ch); + } + if (!cursor.peek(ch) || ch < '0' || ch > '9') { + errorMessage = "invalid number: exponent must have a digit"; + return false; + } + do { + if (!cursor.take(ch)) + return false; + text.push_back(ch); + } while (cursor.peek(ch) && ch >= '0' && ch <= '9'); + } + return true; + } + + // Non-owning cursor over a contiguous input buffer. Positions are byte + // offsets, while line/column values are maintained incrementally. + class BufferSaxCursor { + public: + // Binds the cursor to caller-owned bytes, which must outlive parsing. + BufferSaxCursor(const char* src, size_t size) + : _src(src) + , _size(size) + , _pos(0) + , _line(1) + , _column(1) + , _prevWasCR(false) {} + + // Observes the next byte without advancing source coordinates. + bool peek(char& ch) { + if (_pos >= _size) + return false; + ch = _src[_pos]; + return true; + } + + // Consumes one byte and advances CR/LF-aware source coordinates. + bool get(char& ch) { + if (!peek(ch)) + return false; + advance(ch); + ++_pos; + return true; + } + + // Reports whether every byte in the fixed buffer has been consumed. + bool eof() const { return _pos >= _size; } + // A memory cursor cannot suffer an I/O failure. + bool failed() const { return false; } + // Returns the zero-based byte offset of the next input byte. + size_t position() const { return _pos; } + // Returns the one-based line containing the next input byte. + size_t line() const { return _line; } + // Returns the one-based column containing the next input byte. + size_t column() const { return _column; } + + private: + // Counts CRLF as one newline even though its bytes arrive separately. + void advance(char ch) { + if (ch == '\r') { + ++_line; + _column = 1; + _prevWasCR = true; + } else if (ch == '\n') { + if (_prevWasCR) { + _prevWasCR = false; + } else { + ++_line; + _column = 1; + } + } else { + ++_column; + _prevWasCR = false; + } + } + + const char* _src; + size_t _size; + size_t _pos; + size_t _line; + size_t _column; + bool _prevWasCR; + }; + + // Buffered cursor that gives the SAX parser the same interface for streams + // without first materializing the complete input. + class StreamSaxCursor { + public: + // Binds to a caller-owned stream and delays reads until bytes are needed. + explicit StreamSaxCursor(std::istream& in) + : _in(in) + , _used(0) + , _posInBuf(0) + , _pos(0) + , _line(1) + , _column(1) + , _prevWasCR(false) + , _failed(false) + , _eof(false) {} + + // Observes the next buffered byte, refilling on demand. + bool peek(char& ch) { + if (!ensure()) + return false; + ch = _buffer[_posInBuf]; + return true; + } + + // Consumes one byte while maintaining absolute and source positions. + bool get(char& ch) { + if (!ensure()) + return false; + ch = _buffer[_posInBuf++]; + if (ch == '\r') { + ++_line; + _column = 1; + _prevWasCR = true; + } else if (ch == '\n') { + if (_prevWasCR) { + _prevWasCR = false; + } else { + ++_line; + _column = 1; + } + } else { + ++_column; + _prevWasCR = false; + } + ++_pos; + return true; + } + + // Reports EOF only after both the stream and the refill buffer are empty. + bool eof() const { return _eof && _posInBuf >= _used; } + // Distinguishes an I/O failure from an ordinary end of stream. + bool failed() const { return _failed; } + // Returns the number of bytes consumed across all refills. + size_t position() const { return _pos; } + // Returns the one-based line containing the next input byte. + size_t line() const { return _line; } + // Returns the one-based column containing the next input byte. + size_t column() const { return _column; } + + private: + // Makes one byte available unless EOF or an unrecoverable read failure + // has already been observed. Short reads with data are still usable. + bool ensure() { + if (_posInBuf < _used) + return true; + if (_eof || _failed) + return false; + // Pull directly from streambuf so a source that intentionally + // exposes one short chunk at a time is not mistaken for EOF by + // istream::read's exact-count semantics. One byte is sufficient for + // the parser; the streambuf retains any remaining get-area bytes. + std::streambuf* buffer = _in.rdbuf(); + if (buffer == nullptr || _in.bad()) { + _failed = true; + return false; + } + std::streambuf::int_type next; + try { + next = buffer->sbumpc(); + } catch (const std::bad_alloc&) { + throw; + } catch (...) { + _failed = true; + return false; + } + if (!std::streambuf::traits_type::eq_int_type(next, + std::streambuf::traits_type::eof())) { + _buffer[0] = std::streambuf::traits_type::to_char_type(next); + _used = 1; + _posInBuf = 0; + return true; + } + if (_in.bad()) { + _failed = true; + return false; + } + _eof = true; + return false; + } + + std::istream& _in; + char _buffer[8192]; + size_t _used; + size_t _posInBuf; + size_t _pos; + size_t _line; + size_t _column; + bool _prevWasCR; + bool _failed; + bool _eof; + }; + + // Recursive-descent event parser shared by buffer and stream cursors. It + // applies the same grammar, resource budgets, and duplicate-key policy as + // DOM parsing, but can suppress callbacks for KeepFirstDuplicate values. + template struct SaxParser { + Cursor& cur; + pJsonParser::SaxHandler& handler; + const pJsonParser::Options& opts; + pJsonParser::Error* err; + size_t nodeCount; + + // Couples a cursor and event sink for one parse, with fresh node accounting. + SaxParser(Cursor& aCur, pJsonParser::SaxHandler& aHandler, + const pJsonParser::Options& aOpts, pJsonParser::Error* aErr) + : cur(aCur) + , handler(aHandler) + , opts(aOpts) + , err(aErr) + , nodeCount(0) {} + + // Parses exactly one complete document, translating parser, handler, + // allocation, and stream failures into a stable non-throwing result. + bool parseDocument() noexcept { + try { + resetParseError(err); + if (!parseValue(0, true)) + return false; + if (!skipWhitespace()) + return false; + char ch = 0; + if (opts.maxInputBytes != 0 && cur.position() >= opts.maxInputBytes) { + if (cur.peek(ch)) + return failAt(opts.maxInputBytes, cur.line(), cur.column(), + "input exceeds maxInputBytes"); + } else if (cur.peek(ch)) { + return fail("trailing characters after JSON value"); + } + if (cur.failed()) + return fail("stream read failed"); + return true; + } catch (const SaxParseCancelled&) { + return failNoThrow("SAX parse aborted", pJsonParser::Error::CallbackError); + } catch (const std::bad_alloc&) { + return failNoThrow("SAX parse ran out of memory", + pJsonParser::Error::AllocationFailure); + } catch (const std::exception&) { + return failNoThrow("SAX parse or handler exception", + pJsonParser::Error::CallbackError); + } catch (...) { + return failNoThrow("SAX parse or handler exception", + pJsonParser::Error::CallbackError); + } + } + + // Dispatches one value at the current nesting depth. emit=false still + // validates and counts the subtree but deliberately skips callbacks. + bool parseValue(size_t depth, bool emit) { + if (!skipWhitespace()) + return false; + + char ch = 0; + if (!cur.peek(ch)) { + if (cur.failed()) + return fail("stream read failed"); + return fail("unexpected end of input; expected a value"); + } + + if (ch == '"') + return parseStringValue(emit); + if (ch == '{') + return parseObject(depth + 1, emit); + if (ch == '[') + return parseArray(depth + 1, emit); + if (ch == '-' || (ch >= '0' && ch <= '9')) + return parseNumberValue(emit); + return parseKeywordValue(emit); + } + + // Consumes only the four whitespace bytes admitted by JSON. + bool skipWhitespace() { + char ch = 0; + while (cur.peek(ch) && pJsonParserImpl::isWhitespace(ch)) { + if (!getChar(ch)) + return false; + } + if (cur.failed()) + return fail("stream read failed"); + return true; + } + + // Parses a string value and emits it after it has consumed one node from + // the configured budget. Object keys are handled separately. + bool parseStringValue(bool emit) { + if (!reserveNode()) + return false; + std::string value; + if (!parseStringRaw(value)) + return false; + if (!emit) + return true; + return dispatch(handler.onString(value)); + } + + // Recognizes the lowercase null/boolean literals required by RFC 8259. + bool parseKeywordValue(bool emit) { + char ch = 0; + if (!cur.peek(ch)) + return fail("unexpected end of input; expected a value"); + + if (ch == 'n') { + if (!matchLiteral("null")) + return false; + if (!reserveNode()) + return false; + return !emit || dispatch(handler.onNull()); + } + if (ch == 't') { + if (!matchLiteral("true")) + return false; + if (!reserveNode()) + return false; + return !emit || dispatch(handler.onBool(true)); + } + if (ch == 'f') { + if (!matchLiteral("false")) + return false; + if (!reserveNode()) + return false; + return !emit || dispatch(handler.onBool(false)); + } + return fail("invalid JSON value"); + } + + // Scans the JSON number grammar before conversion. Integral tokens that + // overflow int64 are preserved as finite doubles rather than truncated. + bool parseNumberValue(bool emit) { + std::string text; + bool isFloat = false; + const char* scanError = nullptr; + struct Adapter { + SaxParser& parser; + bool peek(char& ch) { return parser.cur.peek(ch); } + bool take(char& ch) { return parser.getChar(ch); } + } adapter = {*this}; + if (!scanJsonNumber(adapter, text, isFloat, scanError)) + return scanError == nullptr ? false : fail(scanError); + + if (!reserveNode()) + return false; + + pJsonParserImpl::ParsedNumber number; + const char* message = nullptr; + if (!pJsonParserImpl::convertNumberToken(text, isFloat, opts.numberPolicy, number, + message)) + return fail(message); + if (!emit) + return true; + if (number.kind == pJsonParserImpl::ParsedNumber::SignedInteger) + return dispatch(handler.onInt(number.signedValue)); + if (number.kind == pJsonParserImpl::ParsedNumber::UnsignedInteger) + return dispatch(handler.onUInt(number.unsignedValue)); + return dispatch(handler.onDouble(number.floatingValue)); + } + + // Parses an array while explicitly tracking comma state so leading, + // repeated, missing, and trailing commas receive deterministic errors. + bool parseArray(size_t depth, bool emit) { + const size_t maxDepth = static_cast(clampParseDepth(opts.maxDepth)); + if (depth > maxDepth) + return fail("maximum nesting depth exceeded"); + if (!reserveNode()) + return false; + + char ch = 0; + if (!getChar(ch) || ch != '[') + return fail("unexpected end of input; expected a value"); + if (emit && !dispatch(handler.onStartArray())) + return false; + + bool expectValue = false; + bool any = false; + while (true) { + if (!skipWhitespace()) + return false; + if (!cur.peek(ch)) { + if (cur.failed()) + return fail("stream read failed"); + return fail("unterminated array"); + } + if (ch == ']') { + if (expectValue) + return fail("trailing comma in array"); + if (!getChar(ch)) + return false; + return !emit || dispatch(handler.onEndArray()); + } + if (ch == ',') { + if (!any || expectValue) + return fail("unexpected ',' in array"); + if (!getChar(ch)) + return false; + expectValue = true; + continue; + } + if (any && !expectValue) + return fail("missing ',' between array elements"); + if (!parseValue(depth, emit)) + return false; + any = true; + expectValue = false; + } + } + + // Parses an object and implements duplicate-key policy at event time. + // KeepFirst parses duplicate values with emit=false so malformed input + // and resource-limit violations cannot hide inside discarded members. + bool parseObject(size_t depth, bool emit) { + const size_t maxDepth = static_cast(clampParseDepth(opts.maxDepth)); + if (depth > maxDepth) + return fail("maximum nesting depth exceeded"); + if (!reserveNode()) + return false; + + char ch = 0; + if (!getChar(ch) || ch != '{') + return fail("unexpected end of input; expected a value"); + if (emit && !dispatch(handler.onStartObject())) + return false; + + bool expectMember = false; + bool any = false; + std::unordered_set seenKeys; + while (true) { + if (!skipWhitespace()) + return false; + if (!cur.peek(ch)) { + if (cur.failed()) + return fail("stream read failed"); + return fail("unterminated object"); + } + if (ch == '}') { + if (expectMember) + return fail("trailing comma in object"); + if (!getChar(ch)) + return false; + return !emit || dispatch(handler.onEndObject()); + } + if (ch == ',') { + if (!any || expectMember) + return fail("unexpected ',' in object"); + if (!getChar(ch)) + return false; + expectMember = true; + continue; + } + if (ch != '"') + return fail("expected '\"' to start an object key"); + if (any && !expectMember) + return fail("missing ',' between object members"); + + const size_t keyOffset = cur.position(); + const size_t keyLine = cur.line(); + const size_t keyColumn = cur.column(); + std::string key; + if (!parseStringRaw(key)) + return false; + if (!skipWhitespace()) + return false; + if (!getChar(ch) || ch != ':') + return fail("expected ':' after object key"); + + bool duplicate = false; + if (opts.duplicateKeys != pJsonParser::Options::KeepLastDuplicate) { + duplicate = seenKeys.find(key) != seenKeys.end(); + } + if (duplicate && opts.duplicateKeys == pJsonParser::Options::RejectDuplicateKeys) { + return failAt(keyOffset, keyLine, keyColumn, "duplicate object key"); + } + if (!duplicate && opts.duplicateKeys != pJsonParser::Options::KeepLastDuplicate) + seenKeys.insert(key); + + const bool emitValue = + emit && + !(duplicate && opts.duplicateKeys == pJsonParser::Options::KeepFirstDuplicate); + if (emitValue && !dispatch(handler.onKey(key))) + return false; + if (!parseValue(depth, emitValue)) + return false; + any = true; + expectMember = false; + } + } + + // Decodes a quoted JSON string and rejects invalid Unicode/control bytes. + bool parseStringRaw(std::string& out) { + char ch = 0; + if (!getChar(ch) || ch != '"') + return fail("expected '\"' to start a string"); + + out.clear(); + while (true) { + if (!getChar(ch)) { + if (cur.failed()) + return fail("stream read failed"); + return fail("unterminated string"); + } + const unsigned char uch = static_cast(ch); + if (ch == '"') + return true; + if (ch == '\\') { + if (!getChar(ch)) + return fail("dangling escape at end of input"); + switch (ch) { + case '"': + out += '"'; + break; + case '\\': + out += '\\'; + break; + case '/': + out += '/'; + break; + case 'b': + out += '\b'; + break; + case 'f': + out += '\f'; + break; + case 'n': + out += '\n'; + break; + case 'r': + out += '\r'; + break; + case 't': + out += '\t'; + break; + case 'u': { + uint32_t cp = 0; + if (!readHex4(cp)) + return false; + if (cp >= 0xD800 && cp <= 0xDBFF) { + char slash = 0; + if (cur.peek(slash) && slash == '\\') { + if (!getChar(slash)) + return fail("invalid \\u escape"); + char u = 0; + if (!getChar(u)) + return fail("invalid \\u escape"); + if (u == 'u') { + std::string hex; + hex.reserve(4); + bool complete = true; + for (int i = 0; i < 4; ++i) { + char hx = 0; + if (!getChar(hx)) { + complete = false; + break; + } + hex.push_back(hx); + } + uint32_t low = 0; + const bool validLow = + complete && hex.size() == 4 && + pJsonParserImpl::hex4(hex.c_str(), 0, low) && + low >= 0xDC00 && low <= 0xDFFF; + if (validLow) { + cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); + } else { + return fail("unpaired high surrogate"); + } + } else { + return fail("unpaired high surrogate"); + } + } else { + return fail("unpaired high surrogate"); + } + } else if (cp >= 0xDC00 && cp <= 0xDFFF) { + return fail("unpaired low surrogate"); + } + pJsonParserImpl::appendUtf8(cp, out); + break; + } + default: + return fail("invalid escape sequence"); + } + continue; + } + if (uch < 0x20) { + return fail("unescaped control character in string"); + } + if (uch >= 0x80) { + out += static_cast(uch); + if (!consumeUtf8Tail(uch, out)) + return false; + continue; + } + out += static_cast(uch); + } + } + + // Consumes and validates the continuation bytes for an already-stored + // UTF-8 lead byte, including overlong, surrogate, and range checks. + bool consumeUtf8Tail(unsigned char lead, std::string& out) { + int need = 0; + uint32_t code = 0; + if ((lead & 0xE0U) == 0xC0U) { + need = 1; + code = lead & 0x1FU; + } else if ((lead & 0xF0U) == 0xE0U) { + need = 2; + code = lead & 0x0FU; + } else if ((lead & 0xF8U) == 0xF0U) { + need = 3; + code = lead & 0x07U; + } else { + return fail("invalid UTF-8 sequence"); + } + for (int i = 0; i < need; ++i) { + char ch = 0; + if (!getChar(ch)) + return fail("invalid UTF-8 sequence"); + const unsigned char byte = static_cast(ch); + if ((byte & 0xC0U) != 0x80U) + return fail("invalid UTF-8 sequence"); + code = (code << 6) | (byte & 0x3FU); + out += ch; + } + if ((need == 1 && code < 0x80U) || (need == 2 && code < 0x800U) || + (need == 3 && code < 0x10000U) || code > 0x10FFFFU || + (code >= 0xD800U && code <= 0xDFFFU)) { + return fail("invalid UTF-8 sequence"); + } + return true; + } + + // Reads exactly four hexadecimal digits following a \u escape. + bool readHex4(uint32_t& out) { + out = 0; + for (int i = 0; i < 4; ++i) { + char ch = 0; + if (!getChar(ch)) + return fail("invalid \\u escape"); + out <<= 4; + if (ch >= '0' && ch <= '9') + out |= static_cast(ch - '0'); + else if (ch >= 'a' && ch <= 'f') + out |= static_cast(10 + ch - 'a'); + else if (ch >= 'A' && ch <= 'F') + out |= static_cast(10 + ch - 'A'); + else + return fail("invalid \\u escape"); + } + return true; + } + + // Consumes one known lowercase JSON literal. + bool matchLiteral(const char* lit) { + for (size_t i = 0; lit[i] != '\0'; ++i) { + char ch = 0; + if (!getChar(ch)) + return fail("invalid JSON value"); + const char want = lit[i]; + if (ch != want) { + return fail("invalid JSON value"); + } + } + return true; + } + + // Centralizes byte-budget enforcement so no consuming parser path can + // advance beyond maxInputBytes. + bool getChar(char& ch) { + if (opts.maxInputBytes != 0 && cur.position() >= opts.maxInputBytes) + return failAt(opts.maxInputBytes, cur.line(), cur.column(), + "input exceeds maxInputBytes"); + return cur.get(ch); + } + + // Accounts for one JSON value even when its callbacks are suppressed. + bool reserveNode() { + if (opts.maxNodes != 0 && nodeCount >= opts.maxNodes) + return fail("document too large (node budget exceeded)"); + ++nodeCount; + return true; + } + + // Converts a handler's false return into an exception solely to unwind + // nested parse calls; the public SAX API never exposes the exception. + bool dispatch(bool ok) { + if (!ok) + throw SaxParseCancelled(); + return true; + } + + // Records a failure at the cursor's current source location. + bool fail(const std::string& message) { + if (err) { + err->ok = false; + err->code = classifyParseMessage(message); + err->offset = cur.position(); + err->line = cur.line(); + err->column = cur.column(); + err->message = message; + } + return false; + } + + // Records a failure at a saved location, such as a duplicate key's start. + bool failAt(size_t offset, size_t line, size_t column, const std::string& message) { + if (err) { + err->ok = false; + err->code = classifyParseMessage(message); + err->offset = offset; + err->line = line; + err->column = column; + err->message = message; + } + return false; + } + + // Catch-path diagnostics must not replace the original handler/parser + // failure with an allocation exception while assigning the message. + bool failNoThrow(const char* message, pJsonParser::Error::Code code) noexcept { + if (err) { + err->ok = false; + err->code = code; + err->offset = cur.position(); + err->line = cur.line(); + err->column = cur.column(); + try { + err->message = message; + } catch (...) { + // basic_string::clear is non-allocating; retain the + // structured coordinates even when message assignment fails. + err->message.clear(); + } + } + return false; + } + }; +} // namespace + +// Records the first parse error (byte offset + message) and returns false so +// callers can `return fail(...)`. +/*static*/ +bool pJsonParserImpl::fail(pJsonParserImpl::ParseCtx& c, size_t aPos, const char* aMsg) { + if (!c.failed) { + c.failed = true; + c.errPos = aPos; + c.errMsg = aMsg; + } + return false; +} +// Allocates a new pjson while enforcing the node budget. Returns nullptr (and +// records a "document too large" failure) once maxNodes values have been +// created, which caps total memory even for inputs that stay within maxDepth +// (e.g. a huge flat array). The caller propagates the nullptr as a parse error. +/*static*/ +pjson* pJsonParserImpl::newNode(pJsonParserImpl::ParseCtx& c) { + if (c.maxNodes != 0 && c.nodeCount >= c.maxNodes) { + fail(c, c.pos, "document too large (node budget exceeded)"); + return nullptr; + } + ++c.nodeCount; + return pjsonImpl::_allocateNode(*c.allocator); +} + +// Decodes a JSON string body from c.pos into aOut. With bStopAtQuote, decoding +// stops at (and consumes) the first unescaped '"'. RFC 8259-invalid escapes, +// control bytes, surrogate halves, and UTF-8 are rejected. +/*static*/ +bool pJsonParserImpl::decodeStringBody(pJsonParserImpl::ParseCtx& c, std::string& aOut, + bool bStopAtQuote) { + aOut.clear(); + while (c.pos < c.end) { + unsigned char ch = static_cast(c.src[c.pos]); + if (bStopAtQuote && ch == '\"') { + ++c.pos; + return true; + } + if (ch == '\\') { + ++c.pos; + if (c.pos >= c.end) { + return fail(c, c.pos, "dangling escape at end of input"); + } + char e = c.src[c.pos++]; + switch (e) { + case '\"': + aOut += '\"'; + break; + case '\\': + aOut += '\\'; + break; + case '/': + aOut += '/'; + break; + case 'b': + aOut += '\b'; + break; + case 'f': + aOut += '\f'; + break; + case 'n': + aOut += '\n'; + break; + case 'r': + aOut += '\r'; + break; + case 't': + aOut += '\t'; + break; + case 'u': { + uint32_t cp = 0; + if (c.pos + 4 > c.end || !pJsonParserImpl::hex4(c.src, c.pos, cp)) { + return fail(c, c.pos, "invalid \\u escape"); + } + c.pos += 4; + if (cp >= 0xD800 && cp <= 0xDBFF) { + // High surrogate: look for a following low surrogate. + uint32_t low = 0; + if (c.pos + 6 <= c.end && c.src[c.pos] == '\\' && c.src[c.pos + 1] == 'u' && + pJsonParserImpl::hex4(c.src, c.pos + 2, low) && low >= 0xDC00 && + low <= 0xDFFF) { + cp = 0x10000 + ((cp - 0xD800) << 10) + (low - 0xDC00); + c.pos += 6; + } else { + return fail(c, c.pos, "unpaired high surrogate"); + } + } else if (cp >= 0xDC00 && cp <= 0xDFFF) { + return fail(c, c.pos, "unpaired low surrogate"); + } + appendUtf8(cp, aOut); + break; + } + default: + return fail(c, c.pos - 1, "invalid escape sequence"); + } + } else if (ch < 0x20) { + return fail(c, c.pos, "unescaped control character in string"); + } else if (ch >= 0x80) { + int n = pjsonImpl::_utf8Len(c.src, c.pos, c.end); + if (n == 0) { + return fail(c, c.pos, "invalid UTF-8 sequence"); + } + aOut.append(c.src + c.pos, static_cast(n)); + c.pos += static_cast(n); + } else { + aOut += static_cast(ch); + ++c.pos; + } + } + if (bStopAtQuote) { + return fail(c, c.pos, "unterminated string"); + } + return true; +} + +// Reads incrementally so maxInputBytes bounds memory before the complete stream +// has been materialized. Returns the parsed document by value (null on failure). +/*static*/ +pjson pJsonParserImpl::parseStream(std::istream& aIn, const pJsonParser::Options& aOpts, + pJsonParser::Error* aErr, pjson::Allocator& aAlloc) { + std::string content; + char buffer[8192]; + while (aIn.good()) { + aIn.read(buffer, sizeof(buffer)); + const std::streamsize got = aIn.gcount(); + if (got <= 0) + continue; + const size_t chunk = static_cast(got); + if (aOpts.maxInputBytes != 0 && (content.size() > aOpts.maxInputBytes || + chunk > aOpts.maxInputBytes - content.size())) { + // Include as much of this chunk as fits, allowing line/column to be + // calculated at the exact configured byte boundary. + if (content.size() < aOpts.maxInputBytes) { + content.append(buffer, aOpts.maxInputBytes - content.size()); + } + setParseError(aErr, content.data(), content.size(), aOpts.maxInputBytes, + "input exceeds maxInputBytes", pJsonParser::Error::InputLimit); + return pjson(aAlloc); + } + content.append(buffer, chunk); + } + if (aIn.bad()) { + setParseError(aErr, content.data(), content.size(), content.size(), "stream read failed", + pJsonParser::Error::StreamError); + return pjson(aAlloc); + } + return parseTop(content.c_str(), content.length(), aOpts, aErr, aAlloc); +} +/*static*/ +bool pJsonParserImpl::parseSaxTop(const char* aSrc, size_t aSize, pJsonParser::SaxHandler& aHandler, + const pJsonParser::Options& aOpts, pJsonParser::Error* aErr) { + resetParseError(aErr); + if (aSrc == nullptr) { + setParseError(aErr, "", 0, 0, "null input", pJsonParser::Error::InvalidArgument); + return false; + } + if (aOpts.maxInputBytes != 0 && aSize > aOpts.maxInputBytes) { + setParseError(aErr, aSrc, aSize, aOpts.maxInputBytes, "input exceeds maxInputBytes"); + return false; + } + BufferSaxCursor cursor(aSrc, aSize); + SaxParser parser(cursor, aHandler, aOpts, aErr); + return parser.parseDocument(); +} +/*static*/ +bool pJsonParserImpl::parseSaxStream(std::istream& aIn, pJsonParser::SaxHandler& aHandler, + const pJsonParser::Options& aOpts, pJsonParser::Error* aErr) { + resetParseError(aErr); + StreamSaxCursor cursor(aIn); + SaxParser parser(cursor, aHandler, aOpts, aErr); + return parser.parseDocument(); +} + +//===----------------------------------------------------------------------===// +// DOM recursive-descent parser +// +// The cursor advances only across validated syntax, every materialized value +// consumes the shared node budget, and local pjsonImpl::OwnedNode guards retain ownership +// until a child is attached. The first grammar error remains authoritative. +//===----------------------------------------------------------------------===// + +// Shared driver: parse a single top-level value, require only trailing +// whitespace, and report success/failure through the optional pJsonParser::Error. +/*static*/ +pjson pJsonParserImpl::parseTop(const char* aSrc, size_t aSize, const pJsonParser::Options& aOpts, + pJsonParser::Error* aErr, pjson::Allocator& aAlloc) { + resetParseError(aErr); + if (aSrc == nullptr) { + setParseError(aErr, "", 0, 0, "null input", pJsonParser::Error::InvalidArgument); + return pjson(aAlloc); + } + + // Reject an over-large input up front (cheap DoS guard before any work). + if (aOpts.maxInputBytes != 0 && aSize > aOpts.maxInputBytes) { + setParseError(aErr, aSrc, aSize, aOpts.maxInputBytes, "input exceeds maxInputBytes", + pJsonParser::Error::InputLimit); + return pjson(aAlloc); + } + + pJsonParserImpl::ParseCtx c; + c.src = aSrc; + c.pos = 0; + c.end = aSize; + c.duplicateKeys = aOpts.duplicateKeys; + c.numberPolicy = aOpts.numberPolicy; + c.depth = 0; + c.maxDepth = clampParseDepth(aOpts.maxDepth); + c.nodeCount = 0; + c.maxNodes = aOpts.maxNodes; + c.allocator = &aAlloc; + c.failed = false; + c.errPos = 0; + + try { + pjson* parsed = nullptr; + if (!parseValue(c, parsed)) { + pjsonImpl::_destroyNode(parsed); + setParseError(aErr, aSrc, aSize, c.errPos, c.errMsg.empty() ? "parse error" : c.errMsg); + return pjson(aAlloc); + } + // Own the parsed node so it is freed even if the trailing check throws. + pjsonImpl::OwnedNode owned(parsed); + + // A valid document is a single value; only trailing whitespace may follow. + char trailing; + if (peek(c, trailing)) { + setParseError(aErr, aSrc, aSize, c.pos, "trailing characters after JSON value", + pJsonParser::Error::Syntax); + return pjson(aAlloc); + } + // Move the parsed node's storage into a value bound to the same allocator. + // O(1): the value adopts the node's private implementation; the node wrapper is + // then freed empty by OwnedNode, so no smart pointer escapes to the caller. + pjson result(aAlloc); + pjsonImpl::_swapStorage(result, *parsed); + return result; + } catch (const std::bad_alloc&) { + setParseError(aErr, aSrc, aSize, c.pos, "parse ran out of memory", + pJsonParser::Error::AllocationFailure); + } catch (const std::exception& ex) { + (void)ex; + setParseError(aErr, aSrc, aSize, c.pos, "parse failed with exception"); + } catch (...) { + setParseError(aErr, aSrc, aSize, c.pos, "parse failed with exception"); + } + return pjson(aAlloc); +} +// Skips whitespace and reports the next character without consuming it. +/*static*/ +bool pJsonParserImpl::peek(pJsonParserImpl::ParseCtx& c, char& aOut) { + while (c.pos < c.end) { + aOut = c.src[c.pos]; + if (isWhitespace(aOut)) { + ++c.pos; + } else { + return true; + } + } + return false; +} +// Consumes the ':' separating an object key from its value (skipping ws). +/*static*/ +bool pJsonParserImpl::skipColon(pJsonParserImpl::ParseCtx& c) { + while (c.pos < c.end) { + char ch = c.src[c.pos++]; + if (ch == ':') { + return true; + } else if (isWhitespace(ch)) { + // ignore + } else { + return fail(c, c.pos - 1, "expected ':' after object key"); + } + } + return fail(c, c.pos, "expected ':' after object key"); +} +// Dispatches on the next non-whitespace character to the right sub-parser. +/*static*/ +bool pJsonParserImpl::parseValue(pJsonParserImpl::ParseCtx& c, pjson*& aOut) { + char ch; + if (!peek(c, ch)) { + return fail(c, c.pos, "unexpected end of input; expected a value"); + } + if (ch == '\"') { + return parseString(c, aOut); + } else if (ch == '{') { + return parseObject(c, aOut); + } else if (ch == '[') { + return parseArray(c, aOut); + } else if (ch == '-' || (ch >= '0' && ch <= '9')) { + return parseNumber(c, aOut); + } else { + // RFC 8259 null / true / false literals. + return parseKeyword(c, aOut); + } +} +// Matches a keyword literal using the exact lowercase RFC spelling. +/*static*/ +bool pJsonParserImpl::parseKeyword(pJsonParserImpl::ParseCtx& c, pjson*& aOut) { + struct KW { + const char* word; + size_t len; + int kind; + }; // kind: 0 null,1 true,2 false + static const KW kws[] = { + {"null", 4, 0}, + {"true", 4, 1}, + {"false", 5, 2}, + }; + for (const KW& kw : kws) { + if (c.pos + kw.len > c.end) + continue; + bool match = true; + for (size_t k = 0; k < kw.len; ++k) { + char a = c.src[c.pos + k]; + char b = kw.word[k]; + if (a != b) { + match = false; + break; + } + } + if (match) { + c.pos += kw.len; + pjsonImpl::OwnedNode value(newNode(c)); + if (!value) + return false; + if (kw.kind == 1) + *value = true; + else if (kw.kind == 2) + *value = false; + // kind 0 leaves it as null + aOut = value.release(); + return true; + } + } + return fail(c, c.pos, "invalid JSON value"); +} +// Reads a quoted string body starting at the opening '"'. +/*static*/ +bool pJsonParserImpl::extractString(pJsonParserImpl::ParseCtx& c, std::string& aOut) { + if (c.pos >= c.end || c.src[c.pos] != '\"') { + return fail(c, c.pos, "expected '\"' to start a string"); + } + ++c.pos; // consume opening quote + return pJsonParserImpl::decodeStringBody(c, aOut, /*aStopAtQuote=*/true); +} +/*static*/ +// Parses and allocates one string value after decoding its complete token. +bool pJsonParserImpl::parseString(pJsonParserImpl::ParseCtx& c, pjson*& aOut) { + std::string s; + if (!extractString(c, s)) { + return false; + } + pjsonImpl::OwnedNode value(newNode(c)); + if (!value) + return false; + *value = s; + aOut = value.release(); + return true; +} +// Parses a JSON number following the grammar +// -?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)? +// Integer tokens in [INT64_MIN, INT64_MAX] are stored as jsonNumberInt; tokens +// in (INT64_MAX, UINT64_MAX] are stored as jsonNumberUInt; anything with a +// fraction/exponent is stored as a double. Integer tokens outside the exact +// 64-bit range and floating tokens outside binary64 are rejected unless the +// AllowLossyNumbers policy opts in to storing the nearest finite double. Never +// throws. +/*static*/ +bool pJsonParserImpl::parseNumber(pJsonParserImpl::ParseCtx& c, pjson*& aOut) { + const size_t begin = c.pos; + size_t scanPosition = c.pos; + struct Adapter { + pJsonParserImpl::ParseCtx& context; + size_t& position; + bool peek(char& ch) { + if (position >= context.end) + return false; + ch = context.src[position]; + return true; + } + bool take(char& ch) { + if (!peek(ch)) + return false; + ++position; + return true; + } + } adapter = {c, scanPosition}; + std::string text; + bool bFloat = false; + const char* scanError = nullptr; + if (!scanJsonNumber(adapter, text, bFloat, scanError)) + return fail(c, scanPosition, scanError == nullptr ? "invalid number" : scanError); + pJsonParserImpl::ParsedNumber number; + const char* message = nullptr; + if (!convertNumberToken(text, bFloat, c.numberPolicy, number, message)) + return fail(c, begin, message); + pjsonImpl::OwnedNode value(newNode(c)); + if (!value) + return false; + if (number.kind == pJsonParserImpl::ParsedNumber::SignedInteger) + *value = number.signedValue; + else if (number.kind == pJsonParserImpl::ParsedNumber::UnsignedInteger) + *value = number.unsignedValue; + else + *value = number.floatingValue; + aOut = value.release(); + c.pos = scanPosition; + return true; +} +// Parses one array under a balanced depth charge. A child remains RAII-owned +// until vector growth succeeds, preventing leaks on allocation failure. +/*static*/ +bool pJsonParserImpl::parseArray(pJsonParserImpl::ParseCtx& c, pjson*& aOut) { + if (++c.depth > c.maxDepth) { + --c.depth; + return fail(c, c.pos, "maximum nesting depth exceeded"); + } + pjsonImpl::OwnedNode arr(newNode(c)); + if (!arr) { + --c.depth; + return false; + } + arr->resetTo(pjson::jsonType::jsonArray); + ++c.pos; // consume '[' + + bool bExpectValue = false; // a comma was seen, a value must follow + bool bAny = false; // at least one value parsed + char ch; + while (peek(c, ch)) { + if (ch == ']') { + if (bExpectValue) { + --c.depth; + return fail(c, c.pos, "trailing comma in array"); + } + ++c.pos; + --c.depth; + aOut = arr.release(); + return true; + } else if (ch == ',') { + if (!bAny || bExpectValue) { + --c.depth; + return fail(c, c.pos, "unexpected ',' in array"); + } + ++c.pos; + bExpectValue = true; + } else { + if (bAny && !bExpectValue) { + --c.depth; + return fail(c, c.pos, "missing ',' between array elements"); + } + pjson* elem = nullptr; + if (!parseValue(c, elem)) { + pjsonImpl::_destroyNode(elem); + --c.depth; + return false; + } + pjsonImpl::OwnedNode ownedElem(elem); + pjsonImpl::_array(*arr).push_back(nullptr); + pjsonImpl::_array(*arr).back() = ownedElem.release(); + bAny = true; + bExpectValue = false; + } + } + --c.depth; + return fail(c, c.pos, "unterminated array"); +} +// Parses one object under a balanced depth charge and applies duplicate policy +// only after the replacement value is fully parsed and owned. +/*static*/ +bool pJsonParserImpl::parseObject(pJsonParserImpl::ParseCtx& c, pjson*& aOut) { + if (++c.depth > c.maxDepth) { + --c.depth; + return fail(c, c.pos, "maximum nesting depth exceeded"); + } + pjsonImpl::OwnedNode obj(newNode(c)); + if (!obj) { + --c.depth; + return false; + } + obj->resetTo(pjson::jsonType::jsonObject); + ++c.pos; // consume '{' + + bool bExpectMember = false; // a comma was seen, a member must follow + bool bAny = false; // at least one member parsed + char ch; + while (peek(c, ch)) { + if (ch == '}') { + if (bExpectMember) { + --c.depth; + return fail(c, c.pos, "trailing comma in object"); + } + ++c.pos; + --c.depth; + aOut = obj.release(); + return true; + } else if (ch == ',') { + if (!bAny || bExpectMember) { + --c.depth; + return fail(c, c.pos, "unexpected ',' in object"); + } + ++c.pos; + bExpectMember = true; + } else if (ch == '\"') { + if (bAny && !bExpectMember) { + --c.depth; + return fail(c, c.pos, "missing ',' between object members"); + } + const size_t keyOffset = c.pos; + std::string mkey; + if (!extractString(c, mkey)) { + --c.depth; + return false; + } + // PJSON-PARSE-002: under the reject policy, report the duplicate + // immediately after the second name is decoded, before parsing (and + // allocating) its value subtree. + const bool duplicate = + pjsonImpl::_object(*obj).find(mkey) != pjsonImpl::_object(*obj).end(); + if (duplicate && c.duplicateKeys == pJsonParser::Options::RejectDuplicateKeys) { + --c.depth; + return fail(c, keyOffset, "duplicate object key"); + } + pjson* val = nullptr; + if (!skipColon(c) || !parseValue(c, val)) { + pjsonImpl::_destroyNode(val); + --c.depth; + return false; + } + // Apply the remaining duplicate-key policy: keep the first or last + // value deterministically (reject was already handled above). + if (duplicate) { + pjsonImpl::ObjectStorage::iterator it = pjsonImpl::_object(*obj).find(mkey); + if (c.duplicateKeys == pJsonParser::Options::KeepLastDuplicate) { + pjsonImpl::_destroyNode(it->second); + it->second = val; + } else { + pjsonImpl::_destroyNode(val); // KeepFirstDuplicate + } + } else { + pjsonImpl::OwnedNode ownedVal(val); + pjson*& slot = pjsonImpl::_object(*obj)[mkey]; + slot = ownedVal.release(); + } + bAny = true; + bExpectMember = false; + } else { + --c.depth; + return fail(c, c.pos, "expected '\"' to start an object key"); + } + } + --c.depth; + return fail(c, c.pos, "unterminated object"); +} + +//===----------------------------------------------------------------------===// diff --git a/pjsonlib/src/pjson_parser_internal.h b/pjsonlib/src/pjson_parser_internal.h new file mode 100644 index 0000000..eba2304 --- /dev/null +++ b/pjsonlib/src/pjson_parser_internal.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +#ifndef PRAVEENJSON_PARSER_INTERNAL_H +#define PRAVEENJSON_PARSER_INTERNAL_H + +#include "pjson_internal.h" +#include "pjson_parser.h" + +namespace ByteDance { + struct pJsonParserImpl { + static const int DepthHardLimit = 1024; + + pjson::Allocator* allocator; + pJsonParser::Options options; + + pJsonParserImpl(pjson::Allocator& aAllocator, const pJsonParser::Options& aOptions) + : allocator(&aAllocator) + , options(aOptions) {} + + struct ParseCtx { + const char* src; + size_t pos; + size_t end; + pJsonParser::Options::DuplicateKeyPolicy duplicateKeys; + pJsonParser::Options::NumberPolicy numberPolicy; + int depth; + int maxDepth; + size_t nodeCount; + size_t maxNodes; + pjson::Allocator* allocator; + bool failed; + size_t errPos; + std::string errMsg; + }; + + struct ParsedNumber { + enum Kind { SignedInteger, UnsignedInteger, FloatingPoint }; + Kind kind; + int64_t signedValue; + uint64_t unsignedValue; + double floatingValue; + }; + + static bool isWhitespace(char aChar); + static void appendUtf8(uint32_t aCodePoint, std::string& aOut); + static bool hex4(const char* aSource, size_t aStart, uint32_t& aOut); + static bool parseDouble(const std::string& aText, double& aValue, + bool* aUnderflowToZero = nullptr); + static bool convertNumberToken(const std::string& aText, bool aIsFloat, + pJsonParser::Options::NumberPolicy aPolicy, + ParsedNumber& aResult, const char*& aErrorMessage); + + static bool fail(ParseCtx& aContext, size_t aPosition, const char* aMessage); + static pjson* newNode(ParseCtx& aContext); + static bool peek(ParseCtx& aContext, char& aOut); + static bool skipColon(ParseCtx& aContext); + static bool parseValue(ParseCtx& aContext, pjson*& aOut); + static bool parseString(ParseCtx& aContext, pjson*& aOut); + static bool extractString(ParseCtx& aContext, std::string& aOut); + static bool decodeStringBody(ParseCtx& aContext, std::string& aOut, bool aStopAtQuote); + static bool parseKeyword(ParseCtx& aContext, pjson*& aOut); + static bool parseNumber(ParseCtx& aContext, pjson*& aOut); + static bool parseArray(ParseCtx& aContext, pjson*& aOut); + static bool parseObject(ParseCtx& aContext, pjson*& aOut); + + static pjson parseTop(const char* aSource, size_t aSize, + const pJsonParser::Options& aOptions, pJsonParser::Error* aError, + pjson::Allocator& aAllocator); + static pjson parseStream(std::istream& aInput, const pJsonParser::Options& aOptions, + pJsonParser::Error* aError, pjson::Allocator& aAllocator); + static bool parseSaxTop(const char* aSource, size_t aSize, + pJsonParser::SaxHandler& aHandler, + const pJsonParser::Options& aOptions, pJsonParser::Error* aError); + static bool parseSaxStream(std::istream& aInput, pJsonParser::SaxHandler& aHandler, + const pJsonParser::Options& aOptions, + pJsonParser::Error* aError); + }; +} // namespace ByteDance + +#endif // PRAVEENJSON_PARSER_INTERNAL_H diff --git a/pjsonlib/src/pjson_patch.cpp b/pjsonlib/src/pjson_patch.cpp new file mode 100644 index 0000000..5681c75 --- /dev/null +++ b/pjsonlib/src/pjson_patch.cpp @@ -0,0 +1,786 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +// +// RFC 6902 JSON Patch and RFC 7396 JSON Merge Patch. + +#include "pjson_internal.h" +#include "pjson_pointer_internal.h" + +#include +#include +#include +#include + +using namespace ByteDance; +using namespace ByteDance::pjson_pointer_detail; + +pjson::PatchError::PatchError() + : ok(true) + , code(Ok) + , opIndex(0) + , tokenIndex(0) {} + +pjson::PatchOptions::PatchOptions() + : maxOperations(10000) + , maxClonedNodes(1000000) + , maxClonedBytes(size_t(64) * 1024U * 1024U) + , maxWork(1000000) {} + +// RFC 6902 JSON Patch and RFC 7396 Merge Patch helpers +// +// Helpers accept ownership of values through pjsonImpl::OwnedNode and release only after +// attachment, so failed insertions cannot leak. Public entry points work on a +// full allocator-local clone and swap it into place only after every operation +// succeeds, giving both patch formats document-level atomicity. +//===----------------------------------------------------------------------===// + +namespace { + typedef pjson::PatchError PatchError; + bool failPatch(PatchError& aError, PatchError::Code aCode, const char* aMessage); + + struct PatchBudget { + size_t operations; + size_t nodes; + size_t bytes; + size_t work; + size_t operationLimit; + size_t nodeLimit; + size_t byteLimit; + size_t workLimit; + + explicit PatchBudget(const pjson::PatchOptions& options) + : operations(0) + , nodes(0) + , bytes(0) + , work(0) + , operationLimit(options.maxOperations == 0 ? size_t(10000) : options.maxOperations) + , nodeLimit(options.maxClonedNodes == 0 ? size_t(1000000) : options.maxClonedNodes) + , byteLimit(options.maxClonedBytes == 0 ? size_t(64) * 1024U * 1024U + : options.maxClonedBytes) + , workLimit(options.maxWork == 0 ? size_t(1000000) : options.maxWork) {} + }; + + bool chargePatch(size_t& used, size_t limit, size_t amount, PatchError& error, + const char* message) { + if (amount > limit - std::min(used, limit)) + return failPatch(error, PatchError::ResourceLimit, message); + used += amount; + return true; + } + + bool measureClone(const pjson& value, PatchBudget& budget, PatchError& error) { + std::vector work; + work.push_back(&value); + while (!work.empty()) { + if (!chargePatch(budget.work, budget.workLimit, 1, error, + "JSON patch work budget exceeded") || + !chargePatch(budget.nodes, budget.nodeLimit, 1, error, + "JSON patch cloned-node budget exceeded") || + !chargePatch(budget.bytes, budget.byteLimit, sizeof(pjson), error, + "JSON patch cloned-byte budget exceeded")) + return false; + const pjson* current = work.back(); + work.pop_back(); + if (current->isString()) { + if (!chargePatch(budget.bytes, budget.byteLimit, + pjsonImpl::_string(*current).size(), error, + "JSON patch cloned-byte budget exceeded")) + return false; + } else if (current->isArray()) { + const pjsonImpl::ArrayStorage& array = pjsonImpl::_array(*current); + const size_t remainingWork = + budget.workLimit - std::min(budget.work, budget.workLimit); + if (array.size() > remainingWork) + return failPatch(error, PatchError::ResourceLimit, + "JSON patch work budget exceeded"); + work.insert(work.end(), array.begin(), array.end()); + } else if (current->isObject()) { + const pjsonImpl::ObjectStorage& object = pjsonImpl::_object(*current); + const size_t remainingWork = + budget.workLimit - std::min(budget.work, budget.workLimit); + if (object.size() > remainingWork) + return failPatch(error, PatchError::ResourceLimit, + "JSON patch work budget exceeded"); + for (pjsonImpl::ObjectStorage::const_iterator it = object.begin(); + it != object.end(); ++it) { + if (!chargePatch(budget.bytes, budget.byteLimit, it->first.size(), error, + "JSON patch cloned-byte budget exceeded")) + return false; + work.push_back(it->second); + } + } + } + return true; + } + + // Patch `test` needs bounded structural equality so an adversarial value + // cannot hide unbounded traversal behind a single operation. + bool patchEqual(const pjson& left, const pjson& right, PatchBudget& budget, PatchError& error, + bool& equal) { + struct Pair { + const pjson* left; + const pjson* right; + }; + std::vector pending; + Pair root = {&left, &right}; + pending.push_back(root); + equal = false; + while (!pending.empty()) { + if (!chargePatch(budget.work, budget.workLimit, 1, error, + "JSON Patch work budget exceeded")) + return false; + const Pair current = pending.back(); + pending.pop_back(); + const pjson& lhs = *current.left; + const pjson& rhs = *current.right; + if (lhs.isNumber() && rhs.isNumber()) { + if (pjsonImpl::_compareNumbers(lhs, rhs) != 0) + return true; + continue; + } + if (lhs.getType() != rhs.getType()) + return true; + if (lhs.isString()) { + const std::string& l = pjsonImpl::_string(lhs); + const std::string& r = pjsonImpl::_string(rhs); + if (!chargePatch(budget.work, budget.workLimit, std::max(l.size(), r.size()), error, + "JSON Patch work budget exceeded")) + return false; + if (l != r) + return true; + } else if (lhs.isBool()) { + if (pjsonImpl::_boolean(lhs) != pjsonImpl::_boolean(rhs)) + return true; + } else if (lhs.isArray()) { + const pjsonImpl::ArrayStorage& l = pjsonImpl::_array(lhs); + const pjsonImpl::ArrayStorage& r = pjsonImpl::_array(rhs); + if (l.size() != r.size()) + return true; + for (size_t i = 0; i < l.size(); ++i) { + Pair child = {l[i], r[i]}; + pending.push_back(child); + } + } else if (lhs.isObject()) { + const pjsonImpl::ObjectStorage& l = pjsonImpl::_object(lhs); + const pjsonImpl::ObjectStorage& r = pjsonImpl::_object(rhs); + if (l.size() != r.size()) + return true; + for (pjsonImpl::ObjectStorage::const_iterator li = l.begin(); li != l.end(); ++li) { + pjsonImpl::ObjectStorage::const_iterator ri = r.find(li->first); + if (!chargePatch(budget.work, budget.workLimit, li->first.size() + size_t(1), + error, "JSON Patch work budget exceeded")) + return false; + if (ri == r.end()) + return true; + Pair child = {li->second, ri->second}; + pending.push_back(child); + } + } + } + equal = true; + return true; + } + + // Restores a reusable PatchError before processing a new patch document. + void resetPatchError(PatchError& aError) { + aError.ok = true; + aError.code = PatchError::Ok; + aError.opIndex = 0; + aError.op.clear(); + aError.path.clear(); + aError.from.clear(); + aError.tokenIndex = 0; + aError.token.clear(); + aError.message.clear(); + } + + // Records a patch failure while preserving operation metadata set by the caller. + bool failPatch(PatchError& aError, PatchError::Code aCode, const char* aMessage) { + aError.ok = false; + aError.code = aCode; + aError.message = aMessage; + return false; + } + + // Records a patch failure associated with one decoded pointer token. + bool failPatchAtToken(PatchError& aError, PatchError::Code aCode, size_t aTokenIndex, + const std::string& aToken, const char* aMessage) { + aError.tokenIndex = aTokenIndex; + aError.token = aToken; + return failPatch(aError, aCode, aMessage); + } + + // Best-effort noexcept diagnostic used while translating allocation or + // unexpected exceptions out of the public patch API. + void failPatchException(PatchError& aError, PatchError::Code aCode, + const char* aMessage) noexcept { + aError.ok = false; + aError.code = aCode; + try { + aError.message = aMessage; + } catch (...) { + aError.message.clear(); + } + } + + // Maps traversal categories into the smaller PatchError vocabulary. + PatchError::Code pointerCodeForPatch(pjson::PointerError::Code aCode) { + switch (aCode) { + case pjson::PointerError::InvalidArrayIndex: + case pjson::PointerError::AppendTokenNotAllowed: + return PatchError::InvalidArrayIndex; + case pjson::PointerError::ArrayIndexOutOfRange: + return PatchError::ArrayIndexOutOfRange; + case pjson::PointerError::MissingTarget: + case pjson::PointerError::ExpectedContainer: + return PatchError::TargetMissing; + case pjson::PointerError::AllocationFailure: + return PatchError::AllocationFailure; + case pjson::PointerError::InternalError: + return PatchError::InternalError; + default: + return PatchError::TargetMissing; + } + } + + // Copies token context from a pointer failure into the active operation error. + bool failPatchFromPointer(PatchError& aError, const pjson::PointerError& aPointerError) { + aError.tokenIndex = aPointerError.tokenIndex; + aError.token = aPointerError.token; + return failPatch(aError, pointerCodeForPatch(aPointerError.code), + aPointerError.message.c_str()); + } + + // Decodes a Patch pointer and classifies syntax failures as path or from errors. + bool decodePatchPointer(const std::string& aPointer, bool bFrom, + std::vector& aTokens, PatchBudget& aBudget, + PatchError& aError) { + if (!chargePatch(aBudget.work, aBudget.workLimit, aPointer.size() + size_t(1), aError, + "JSON patch work budget exceeded")) + return false; + pjson::PointerError pointerError; + if (decodePointer(aPointer, aTokens, pointerError)) + return true; + aError.tokenIndex = pointerError.tokenIndex; + aError.token = pointerError.token; + return failPatch(aError, bFrom ? PatchError::InvalidFrom : PatchError::InvalidPath, + pointerError.message.c_str()); + } + + // Resolves a mutable prefix and translates PointerError into PatchError. + pjson* resolvePatchTokens(pjson& aRoot, const std::vector& aTokens, size_t aCount, + const std::string& aPointer, PatchBudget& aBudget, + PatchError& aError) { + if (!chargePatch(aBudget.work, aBudget.workLimit, aCount + size_t(1), aError, + "JSON patch work budget exceeded")) + return nullptr; + pjson::PointerError pointerError; + const pjson* result = resolvePointerTokens(aRoot, aTokens, aCount, aPointer, pointerError); + if (result == nullptr) { + failPatchFromPointer(aError, pointerError); + return nullptr; + } + return const_cast(result); + } + + // Validates a destination/source array index. '-' denotes exactly size() and + // is accepted only for add, whose insertion range includes the end position. + bool patchArrayIndex(const pjson& aParent, const std::string& aToken, bool bAllowAppend, + size_t aTokenIndex, size_t& aIndex, bool& bAppend, PatchError& aError) { + bAppend = false; + if (aToken == "-") { + if (bAllowAppend) { + bAppend = true; + aIndex = aParent.size(); + return true; + } + return failPatchAtToken(aError, PatchError::InvalidArrayIndex, aTokenIndex, aToken, + "the '-' token is valid only for add destinations"); + } + + const PointerIndexResult result = parsePointerIndex(aToken, aIndex); + if (result == PointerIndexInvalid) + return failPatchAtToken(aError, PatchError::InvalidArrayIndex, aTokenIndex, aToken, + "array index is not canonical decimal"); + const size_t size = aParent.size(); + if (result == PointerIndexOverflow || (bAllowAppend ? aIndex > size : aIndex >= size)) + return failPatchAtToken(aError, PatchError::ArrayIndexOutOfRange, aTokenIndex, aToken, + "array index is out of range"); + return true; + } + + // Consumes an allocator-compatible value and implements Patch add. Existing + // object members are replaced; array insertion shifts following elements. + bool addOwnedAtPointer(pjson& aRoot, const std::vector& aTokens, + const std::string& aPointer, pjsonImpl::OwnedNode aValue, + PatchBudget& aBudget, PatchError& aError) { + if (aTokens.empty()) { + pjsonImpl::_swapStorage(aRoot, *aValue); + return true; + } + + const size_t finalIndex = aTokens.size() - 1; + pjson* parent = resolvePatchTokens(aRoot, aTokens, finalIndex, aPointer, aBudget, aError); + if (parent == nullptr) + return false; + const std::string& token = aTokens.back(); + + if (parent->isObject()) { + pjsonImpl::ObjectStorage* object = &pjsonImpl::_object(*parent); + pjsonImpl::ObjectStorage::iterator existing = object->find(token); + if (existing != object->end()) { + pjsonImpl::_swapStorage(*existing->second, *aValue); + return true; + } + if (!chargePatch(aBudget.bytes, aBudget.byteLimit, token.size(), aError, + "JSON Patch cloned-byte budget exceeded")) + return false; + const std::pair inserted = + object->insert(std::make_pair(token, static_cast(nullptr))); + if (!inserted.second) + return failPatchAtToken(aError, PatchError::InternalError, finalIndex, token, + "failed to insert object member"); + inserted.first->second = aValue.release(); + return true; + } + + if (parent->isArray()) { + size_t index = 0; + bool append = false; + if (!patchArrayIndex(*parent, token, true, finalIndex, index, append, aError)) + return false; + pjsonImpl::ArrayStorage* array = &pjsonImpl::_array(*parent); + if (!chargePatch(aBudget.work, aBudget.workLimit, array->size() - index, aError, + "JSON Patch work budget exceeded")) + return false; + const pjsonImpl::ArrayStorage::iterator inserted = + array->insert(array->begin() + static_cast(index), nullptr); + *inserted = aValue.release(); + return true; + } + + return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, + "add destination parent is not a container"); + } + + // Consumes a replacement only after proving the complete target exists. + bool replaceAtPointer(pjson& aRoot, const std::vector& aTokens, + const std::string& aPointer, pjsonImpl::OwnedNode aValue, + PatchBudget& aBudget, PatchError& aError) { + if (aTokens.empty()) { + pjsonImpl::_swapStorage(aRoot, *aValue); + return true; + } + + const size_t finalIndex = aTokens.size() - 1; + pjson* parent = resolvePatchTokens(aRoot, aTokens, finalIndex, aPointer, aBudget, aError); + if (parent == nullptr) + return false; + const std::string& token = aTokens.back(); + + if (parent->isObject()) { + pjsonImpl::ObjectStorage* object = &pjsonImpl::_object(*parent); + pjsonImpl::ObjectStorage::iterator existing = object->find(token); + if (existing == object->end()) + return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, + "replace target does not exist"); + pjsonImpl::_swapStorage(*existing->second, *aValue); + return true; + } + + if (parent->isArray()) { + size_t index = 0; + bool append = false; + if (!patchArrayIndex(*parent, token, false, finalIndex, index, append, aError)) + return false; + pjsonImpl::_swapStorage(*pjsonImpl::_array(*parent)[index], *aValue); + return true; + } + + return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, + "replace target parent is not a container"); + } + + // Detaches a target without destroying it. Removing the document root is + // represented by replacing the still-addressable root value with JSON null. + bool detachAtPointer(pjson& aRoot, const std::vector& aTokens, + const std::string& aPointer, pjsonImpl::OwnedNode& aValue, + PatchBudget& aBudget, PatchError& aError) { + if (aTokens.empty()) { + if (!chargePatch(aBudget.nodes, aBudget.nodeLimit, 1, aError, + "JSON Patch cloned-node budget exceeded") || + !chargePatch(aBudget.bytes, aBudget.byteLimit, sizeof(pjson), aError, + "JSON Patch cloned-byte budget exceeded")) + return false; + pjsonImpl::OwnedNode replacement = pjsonImpl::_makeNode(aRoot.getAllocator()); + pjsonImpl::_swapStorage(aRoot, *replacement); + aValue = std::move(replacement); + return true; + } + + const size_t finalIndex = aTokens.size() - 1; + pjson* parent = resolvePatchTokens(aRoot, aTokens, finalIndex, aPointer, aBudget, aError); + if (parent == nullptr) + return false; + const std::string& token = aTokens.back(); + + if (parent->isObject()) { + pjsonImpl::ObjectStorage* object = &pjsonImpl::_object(*parent); + pjsonImpl::ObjectStorage::iterator existing = object->find(token); + if (existing == object->end()) + return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, + "remove source does not exist"); + aValue.reset(existing->second); + object->erase(existing); + return true; + } + + if (parent->isArray()) { + size_t index = 0; + bool append = false; + if (!patchArrayIndex(*parent, token, false, finalIndex, index, append, aError)) + return false; + pjsonImpl::ArrayStorage* array = &pjsonImpl::_array(*parent); + if (!chargePatch(aBudget.work, aBudget.workLimit, array->size() - index - size_t(1), + aError, "JSON Patch work budget exceeded")) + return false; + aValue.reset((*array)[index]); + array->erase(array->begin() + static_cast(index)); + return true; + } + + return failPatchAtToken(aError, PatchError::TargetMissing, finalIndex, token, + "remove source parent is not a container"); + } + + // Compares decoded paths so alternate escape spellings cannot affect identity. + bool samePointerTokens(const std::vector& aLeft, + const std::vector& aRight) { + return aLeft == aRight; + } + + // Detects a move into the source's own descendant, which would invalidate + // the destination during detachment and is forbidden by JSON Patch. + bool isProperPointerAncestor(const std::vector& aAncestor, + const std::vector& aDescendant) { + return aAncestor.size() < aDescendant.size() && + std::equal(aAncestor.begin(), aAncestor.end(), aDescendant.begin()); + } + + // Replaces or adopts an allocator-compatible object child without exposing + // a null map entry if insertion fails. + bool insertObjectChild(pjson& aObject, const std::string& aKey, pjsonImpl::OwnedNode aChild) { + pjsonImpl::ObjectStorage* object = &pjsonImpl::_object(aObject); + pjsonImpl::ObjectStorage::iterator existing = object->find(aKey); + if (existing != object->end()) { + pjsonImpl::_swapStorage(*existing->second, *aChild); + return true; + } + const std::pair inserted = + object->insert(std::make_pair(aKey, static_cast(nullptr))); + if (!inserted.second) + return false; + inserted.first->second = aChild.release(); + return true; + } + + // Applies Merge Patch iteratively to a private working document. Object + // patches recurse, null members delete, and every other value replaces via + // an allocator-local clone. Atomic publication is handled by the caller. + bool applyMergePatchTo(pjson& aTarget, const pjson& aPatch, PatchBudget& aBudget, + PatchError& aError) { + struct MergeItem { + // target and patch describe one pending object/object merge. + pjson* target; + const pjson* patch; + }; + + if (!aPatch.isObject()) { + if (!chargePatch(aBudget.operations, aBudget.operationLimit, 1, aError, + "JSON Merge Patch operation budget exceeded")) + return false; + if (!measureClone(aPatch, aBudget, aError)) + return false; + pjson replacement(aPatch, aTarget.getAllocator()); + pjsonImpl::_swapStorage(aTarget, replacement); + return true; + } + + std::vector work; + MergeItem root = {&aTarget, &aPatch}; + work.push_back(root); + while (!work.empty()) { + const MergeItem item = work.back(); + work.pop_back(); + if (!item.target->isObject()) + item.target->resetTo(pjson::jsonObject); + + const pjsonImpl::ObjectStorage* patchObject = &pjsonImpl::_object(*item.patch); + for (pjsonImpl::ObjectStorage::const_iterator it = patchObject->begin(); + it != patchObject->end(); ++it) { + if (!chargePatch(aBudget.operations, aBudget.operationLimit, 1, aError, + "JSON Merge Patch operation budget exceeded") || + !chargePatch(aBudget.work, aBudget.workLimit, 1, aError, + "JSON Merge Patch work budget exceeded")) + return false; + const std::string& key = it->first; + const pjson& patchValue = *it->second; + if (!chargePatch(aBudget.bytes, aBudget.byteLimit, key.size(), aError, + "JSON Merge Patch cloned-byte budget exceeded")) + return false; + if (patchValue.isNull()) { + item.target->erase(key); + continue; + } + + pjson* targetValue = item.target->find(key); + if (patchValue.isObject()) { + if (targetValue == nullptr) { + if (!chargePatch(aBudget.nodes, aBudget.nodeLimit, 1, aError, + "JSON Merge Patch cloned-node budget exceeded") || + !chargePatch(aBudget.bytes, aBudget.byteLimit, sizeof(pjson), aError, + "JSON Merge Patch cloned-byte budget exceeded")) + return false; + pjsonImpl::OwnedNode child = + pjsonImpl::_makeNode(item.target->getAllocator()); + child->resetTo(pjson::jsonObject); + targetValue = child.get(); + if (!insertObjectChild(*item.target, key, std::move(child))) + return false; + } else if (!targetValue->isObject()) { + targetValue->resetTo(pjson::jsonObject); + } + MergeItem childItem = {targetValue, &patchValue}; + work.push_back(childItem); + continue; + } + + if (!measureClone(patchValue, aBudget, aError)) + return false; + pjsonImpl::OwnedNode replacement = + pjsonImpl::_cloneNode(patchValue, item.target->getAllocator()); + if (!insertObjectChild(*item.target, key, std::move(replacement))) + return false; + } + } + return true; + } +} // namespace + +// Applies JSON Patch while intentionally discarding detailed diagnostics. +bool pjson::applyPatch(const pjson& aPatch, const PatchOptions& aOpts) noexcept { + PatchError error; + return applyPatch(aPatch, error, aOpts); +} +// Applies an RFC 6902 operation sequence atomically. Validation and mutation +// happen on scratch; only a completely successful sequence is published by swap. +bool pjson::applyPatch(const pjson& aPatch, PatchError& aError, + const PatchOptions& aOpts) noexcept { + resetPatchError(aError); + try { + if (!aPatch.isArray()) + return failPatch(aError, PatchError::InvalidPatchDocument, + "JSON Patch document must be an array"); + + PatchBudget budget(aOpts); + const pjsonImpl::ArrayStorage& operations = pjsonImpl::_array(aPatch); + if (!chargePatch(budget.operations, budget.operationLimit, operations.size(), aError, + "JSON Patch operation budget exceeded") || + !measureClone(*this, budget, aError)) + return false; + + // The scratch copy is both the rollback boundary and the allocator domain + // into which every add/copy/replace value must be cloned. + pjson scratch(*this, *_allocator); + for (size_t operationIndex = 0; operationIndex < operations.size(); ++operationIndex) { + const pjson& operation = *operations[operationIndex]; + aError.opIndex = operationIndex; + aError.op.clear(); + aError.path.clear(); + aError.from.clear(); + aError.tokenIndex = 0; + aError.token.clear(); + aError.message.clear(); + + if (!operation.isObject()) + return failPatch(aError, PatchError::OperationNotObject, + "JSON Patch operation must be an object"); + + const pjson* opNode = operation.find("op"); + if (opNode == nullptr || !opNode->isString()) + return failPatch(aError, PatchError::MissingOp, + "JSON Patch operation requires string member 'op'"); + aError.op = pjsonImpl::_string(*opNode); + + const bool knownOperation = aError.op == "add" || aError.op == "remove" || + aError.op == "replace" || aError.op == "move" || + aError.op == "copy" || aError.op == "test"; + if (!knownOperation) + return failPatch(aError, PatchError::InvalidOp, + "JSON Patch operation name is not supported"); + + const pjson* pathNode = operation.find("path"); + if (pathNode == nullptr || !pathNode->isString()) + return failPatch(aError, PatchError::MissingPath, + "JSON Patch operation requires string member 'path'"); + aError.path = pjsonImpl::_string(*pathNode); + std::vector pathTokens; + if (!decodePatchPointer(aError.path, false, pathTokens, budget, aError)) + return false; + + // Validate and decode this operation's metadata before mutating the + // private scratch tree. Later operations are processed only after + // earlier ones succeed; the public target is still untouched. + const bool needsFrom = aError.op == "move" || aError.op == "copy"; + std::vector fromTokens; + if (needsFrom) { + const pjson* fromNode = operation.find("from"); + if (fromNode == nullptr || !fromNode->isString()) + return failPatch(aError, PatchError::MissingFrom, + "move and copy require string member 'from'"); + aError.from = pjsonImpl::_string(*fromNode); + if (!decodePatchPointer(aError.from, true, fromTokens, budget, aError)) + return false; + } + + const bool needsValue = + aError.op == "add" || aError.op == "replace" || aError.op == "test"; + const pjson* valueNode = operation.find("value"); + if (needsValue && valueNode == nullptr) + return failPatch(aError, PatchError::MissingValue, + "add, replace, and test require member 'value'"); + + if (aError.op == "add") { + if (!measureClone(*valueNode, budget, aError)) + return false; + pjsonImpl::OwnedNode value = + pjsonImpl::_cloneNode(*valueNode, scratch.getAllocator()); + if (!addOwnedAtPointer(scratch, pathTokens, aError.path, std::move(value), budget, + aError)) + return false; + continue; + } + + if (aError.op == "remove") { + pjsonImpl::OwnedNode removed; + if (!detachAtPointer(scratch, pathTokens, aError.path, removed, budget, aError)) + return false; + continue; + } + + if (aError.op == "replace") { + if (!measureClone(*valueNode, budget, aError)) + return false; + pjsonImpl::OwnedNode value = + pjsonImpl::_cloneNode(*valueNode, scratch.getAllocator()); + if (!replaceAtPointer(scratch, pathTokens, aError.path, std::move(value), budget, + aError)) + return false; + continue; + } + + if (aError.op == "test") { + const pjson* target = resolvePatchTokens(scratch, pathTokens, pathTokens.size(), + aError.path, budget, aError); + if (target == nullptr) + return false; + bool equal = false; + if (!patchEqual(*target, *valueNode, budget, aError, equal)) + return false; + if (!equal) + return failPatch(aError, PatchError::TestFailed, + "JSON Patch test value does not match target"); + continue; + } + + const pjson* source = resolvePatchTokens(scratch, fromTokens, fromTokens.size(), + aError.from, budget, aError); + if (source == nullptr) + return false; + + if (aError.op == "copy") { + if (!measureClone(*source, budget, aError)) + return false; + pjsonImpl::OwnedNode value = pjsonImpl::_cloneNode(*source, scratch.getAllocator()); + if (!addOwnedAtPointer(scratch, pathTokens, aError.path, std::move(value), budget, + aError)) + return false; + continue; + } + + if (samePointerTokens(fromTokens, pathTokens)) + continue; + if (fromTokens.empty()) { + // Moving the root can only succeed when the destination is + // also root (handled above); every other path is a descendant. + return failPatch(aError, PatchError::MoveRootNotAllowed, + "cannot move the document root below itself"); + } + if (isProperPointerAncestor(fromTokens, pathTokens)) + return failPatch(aError, PatchError::MoveIntoDescendant, + "cannot move a value into one of its descendants"); + + pjsonImpl::OwnedNode moved; + if (!detachAtPointer(scratch, fromTokens, aError.from, moved, budget, aError)) + return false; + if (!addOwnedAtPointer(scratch, pathTokens, aError.path, std::move(moved), budget, + aError)) + return false; + } + + // This is the sole publication point; all earlier exits leave *this intact. + pjsonImpl::_swapStorage(*this, scratch); + resetPatchError(aError); + return true; + } catch (const std::bad_alloc&) { + failPatchException(aError, PatchError::AllocationFailure, "JSON Patch ran out of memory"); + return false; + } catch (const std::exception&) { + failPatchException(aError, PatchError::InternalError, + "JSON Patch failed with an internal exception"); + return false; + } catch (...) { + failPatchException(aError, PatchError::InternalError, + "JSON Patch failed with an unknown exception"); + return false; + } +} +// Applies Merge Patch while intentionally discarding detailed diagnostics. +bool pjson::applyMergePatch(const pjson& aPatch, const PatchOptions& aOpts) noexcept { + PatchError error; + return applyMergePatch(aPatch, error, aOpts); +} +// Applies RFC 7396 atomically by mutating a private deep copy and publishing it +// only after the iterative merge has completed. +bool pjson::applyMergePatch(const pjson& aPatch, PatchError& aError, + const PatchOptions& aOpts) noexcept { + resetPatchError(aError); + try { + PatchBudget budget(aOpts); + if (!measureClone(*this, budget, aError)) + return false; + pjson scratch(*this, *_allocator); + if (!applyMergePatchTo(scratch, aPatch, budget, aError)) { + if (!aError.ok) + return false; + return failPatch(aError, PatchError::InternalError, + "JSON Merge Patch could not update an object member"); + } + pjsonImpl::_swapStorage(*this, scratch); + resetPatchError(aError); + return true; + } catch (const std::bad_alloc&) { + failPatchException(aError, PatchError::AllocationFailure, + "JSON Merge Patch ran out of memory"); + return false; + } catch (const std::exception&) { + failPatchException(aError, PatchError::InternalError, + "JSON Merge Patch failed with an internal exception"); + return false; + } catch (...) { + failPatchException(aError, PatchError::InternalError, + "JSON Merge Patch failed with an unknown exception"); + return false; + } +} +/*static*/ diff --git a/pjsonlib/src/pjson_pointer.cpp b/pjsonlib/src/pjson_pointer.cpp new file mode 100644 index 0000000..534bec9 --- /dev/null +++ b/pjsonlib/src/pjson_pointer.cpp @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +// +// JSON Pointer decoding and non-vivifying traversal. + +#include "pjson_internal.h" +#include "pjson_pointer_internal.h" + +#include +#include +#include + +namespace ByteDance { + pjson::PointerError::PointerError() + : ok(true) + , code(Ok) + , tokenIndex(0) {} + + namespace pjson_pointer_detail { + void resetPointerError(pjson::PointerError& error) { + error.ok = true; + error.code = pjson::PointerError::Ok; + error.pointer.clear(); + error.tokenIndex = 0; + error.token.clear(); + error.message.clear(); + } + + bool failPointer(pjson::PointerError& error, pjson::PointerError::Code code, + const std::string& pointer, size_t tokenIndex, const std::string& token, + const char* message) { + error.ok = false; + error.code = code; + error.pointer = pointer; + error.tokenIndex = tokenIndex; + error.token = token; + error.message = message; + return false; + } + + PointerIndexResult parsePointerIndex(const std::string& token, size_t& index) { + if (token.empty() || (token.size() > 1 && token[0] == '0')) + return PointerIndexInvalid; + + size_t value = 0; + for (size_t i = 0; i < token.size(); ++i) { + const unsigned char ch = static_cast(token[i]); + if (ch < static_cast('0') || ch > static_cast('9')) + return PointerIndexInvalid; + const size_t digit = static_cast(ch - static_cast('0')); + if (value > (std::numeric_limits::max() - digit) / size_t(10)) + return PointerIndexOverflow; + value = value * size_t(10) + digit; + } + index = value; + return PointerIndexOk; + } + + bool decodePointer(const std::string& pointer, std::vector& tokens, + pjson::PointerError& error) { + resetPointerError(error); + tokens.clear(); + if (pointer.empty()) + return true; + if (pointer[0] != '/') + return failPointer(error, pjson::PointerError::InvalidSyntax, pointer, 0, + std::string(), "JSON Pointer must be empty or begin with '/'"); + + size_t tokenIndex = 0; + size_t tokenStart = 1; + while (true) { + const size_t slash = pointer.find('/', tokenStart); + const size_t tokenEnd = slash == std::string::npos ? pointer.size() : slash; + std::string decoded; + decoded.reserve(tokenEnd - tokenStart); + for (size_t i = tokenStart; i < tokenEnd; ++i) { + const char ch = pointer[i]; + if (ch != '~') { + decoded += ch; + continue; + } + if (i + 1 >= tokenEnd || (pointer[i + 1] != '0' && pointer[i + 1] != '1')) { + return failPointer(error, pjson::PointerError::InvalidEscape, pointer, + tokenIndex, + pointer.substr(tokenStart, tokenEnd - tokenStart), + "JSON Pointer token contains an invalid '~' escape"); + } + decoded += pointer[i + 1] == '0' ? '~' : '/'; + ++i; + } + tokens.push_back(std::move(decoded)); + if (slash == std::string::npos) + break; + tokenStart = slash + 1; + ++tokenIndex; + } + return true; + } + + const pjson* resolvePointerTokens(const pjson& root, const std::vector& tokens, + size_t count, const std::string& pointer, + pjson::PointerError& error) { + const pjson* current = &root; + for (size_t i = 0; i < count; ++i) { + const std::string& token = tokens[i]; + if (current->isObject()) { + const pjson* child = current->find(token); + if (child == nullptr) { + failPointer(error, pjson::PointerError::MissingTarget, pointer, i, token, + "JSON Pointer object member does not exist"); + return nullptr; + } + current = child; + continue; + } + if (current->isArray()) { + if (token == "-") { + failPointer(error, pjson::PointerError::AppendTokenNotAllowed, pointer, i, + token, "the '-' token is only valid for JSON Patch add"); + return nullptr; + } + size_t index = 0; + const PointerIndexResult result = parsePointerIndex(token, index); + if (result == PointerIndexInvalid) { + failPointer(error, pjson::PointerError::InvalidArrayIndex, pointer, i, + token, "JSON Pointer array index is not canonical decimal"); + return nullptr; + } + if (result == PointerIndexOverflow || index >= current->size()) { + failPointer(error, pjson::PointerError::ArrayIndexOutOfRange, pointer, i, + token, "JSON Pointer array index is out of range"); + return nullptr; + } + current = current->findIndex(index); + continue; + } + failPointer(error, pjson::PointerError::ExpectedContainer, pointer, i, token, + "JSON Pointer traversal reached a non-container value"); + return nullptr; + } + return current; + } + } // namespace pjson_pointer_detail + + std::string pjson::escapePointerToken(const std::string& token) { + std::string escaped; + escaped.reserve(token.size()); + for (size_t i = 0; i < token.size(); ++i) { + if (token[i] == '~') + escaped += "~0"; + else if (token[i] == '/') + escaped += "~1"; + else + escaped += token[i]; + } + return escaped; + } + + const pjson* pjson::findPointer(const std::string& pointer, PointerError& error) const { + using namespace pjson_pointer_detail; + try { + std::vector tokens; + if (!decodePointer(pointer, tokens, error)) + return nullptr; + return resolvePointerTokens(*this, tokens, tokens.size(), pointer, error); + } catch (const std::bad_alloc&) { + try { + failPointer(error, PointerError::AllocationFailure, std::string(), 0, std::string(), + "JSON Pointer ran out of memory"); + } catch (...) { + error.ok = false; + error.code = PointerError::AllocationFailure; + } + return nullptr; + } catch (...) { + try { + failPointer(error, PointerError::InternalError, std::string(), 0, std::string(), + "JSON Pointer failed with an internal exception"); + } catch (...) { + error.ok = false; + error.code = PointerError::InternalError; + } + return nullptr; + } + } + + pjson* pjson::findPointer(const std::string& pointer, PointerError& error) { + return const_cast(static_cast(this)->findPointer(pointer, error)); + } + + const pjson* pjson::findPointer(const std::string& pointer) const { + PointerError error; + return findPointer(pointer, error); + } + + pjson* pjson::findPointer(const std::string& pointer) { + return const_cast(static_cast(this)->findPointer(pointer)); + } + + const pjson* pjson::findPointer(const char* pointer, PointerError& error) const { + using namespace pjson_pointer_detail; + try { + if (pointer != nullptr) + return findPointer(std::string(pointer), error); + resetPointerError(error); + failPointer(error, PointerError::InvalidSyntax, std::string(), 0, std::string(), + "JSON Pointer input is null"); + return nullptr; + } catch (const std::bad_alloc&) { + error.ok = false; + error.code = PointerError::AllocationFailure; + return nullptr; + } catch (...) { + error.ok = false; + error.code = PointerError::InternalError; + return nullptr; + } + } + + pjson* pjson::findPointer(const char* pointer, PointerError& error) { + return const_cast(static_cast(this)->findPointer(pointer, error)); + } + + const pjson* pjson::findPointer(const char* pointer) const { + PointerError error; + return findPointer(pointer, error); + } + + pjson* pjson::findPointer(const char* pointer) { + return const_cast(static_cast(this)->findPointer(pointer)); + } +} // namespace ByteDance diff --git a/pjsonlib/src/pjson_pointer_internal.h b/pjsonlib/src/pjson_pointer_internal.h new file mode 100644 index 0000000..a9a1b69 --- /dev/null +++ b/pjsonlib/src/pjson_pointer_internal.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +#ifndef PRAVEENJSON_POINTER_INTERNAL_H +#define PRAVEENJSON_POINTER_INTERNAL_H + +#include "pjson.h" + +#include +#include +#include + +namespace ByteDance { + namespace pjson_pointer_detail { + enum PointerIndexResult { PointerIndexOk, PointerIndexInvalid, PointerIndexOverflow }; + + void resetPointerError(pjson::PointerError& aError); + bool failPointer(pjson::PointerError& aError, pjson::PointerError::Code aCode, + const std::string& aPointer, size_t aTokenIndex, const std::string& aToken, + const char* aMessage); + PointerIndexResult parsePointerIndex(const std::string& aToken, size_t& aIndex); + bool decodePointer(const std::string& aPointer, std::vector& aTokens, + pjson::PointerError& aError); + const pjson* resolvePointerTokens(const pjson& aRoot, + const std::vector& aTokens, size_t aCount, + const std::string& aPointer, pjson::PointerError& aError); + } // namespace pjson_pointer_detail +} // namespace ByteDance + +#endif // PRAVEENJSON_POINTER_INTERNAL_H diff --git a/pjsonlib/src/pjson_schema.cpp b/pjsonlib/src/pjson_schema.cpp new file mode 100644 index 0000000..4f47101 --- /dev/null +++ b/pjsonlib/src/pjson_schema.cpp @@ -0,0 +1,2747 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// pjson_schema.cpp — standalone JSON Schema validation. +// +// Implements ByteDance::pJsonSchemaValidator. This translation unit is a pure +// consumer of pjson's PUBLIC API (pjson.h): it never touches pjson's private +// storage and does not include pjson_internal.h. That keeps the schema module +// fully decoupled from the DOM layout, so future DOM changes cannot silently +// alter validation behavior. +// +// Default options implement pjson's subset dialect; Options::draft2020() +// activates the required Draft 2020-12 vocabularies and meta-schema policy. +//===----------------------------------------------------------------------===// +#include "pjson_schema.h" +#include "pjson_parser.h" +#include "pjson_schema_builtins.h" +#include "pjson_schema_dialect.h" +#include "pjson_schema_regex.h" +#include "pjson_schema_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ByteDance; +using namespace ByteDance::pjson_schema_detail; + +namespace { + + typedef pJsonSchemaValidator::Error SchemaError; + typedef pJsonSchemaValidator::Options Options; + + // Recursive validation still uses native recursion for applicator keywords. + // Keep its logical depth below a conservative stack-safe ceiling even when a + // caller requests a larger value. + const size_t kSchemaValidationDepthHardLimit = 32; + + //===------------------------------------------------------------------===// + // Public-API accessors + // + // These small helpers express everything the validator needs to read from a + // pjson value using only the public interface. + //===------------------------------------------------------------------===// + + // Appends "/token" to a JSON Pointer path, escaping '~' and '/' per RFC 6901. + std::string pointerAppend(const std::string& base, const std::string& token) { + std::string escaped; + escaped.reserve(token.size()); + for (char c : token) { + if (c == '~') + escaped += "~0"; + else if (c == '/') + escaped += "~1"; + else + escaped += c; + } + return base + "/" + escaped; + } + + //===------------------------------------------------------------------===// + // Regex cache, run context, and diagnostic sink + //===------------------------------------------------------------------===// + + // One compiled schema regex or a cached policy/syntax rejection. + struct RegexCacheEntry { + enum State { Uninitialized, Ready, PatternTooLarge, UnsafePattern, InvalidPattern }; + State state; + EcmaRegex expression; + RegexCacheEntry() + : state(Uninitialized) {} + }; + + struct SchemaResource { + const pjson* root; + std::string baseUri; + DialectPolicy policy; + SchemaResource() + : root(nullptr) {} + SchemaResource(const pjson* aRoot, const std::string& aBase, const DialectPolicy& aPolicy) + : root(aRoot) + , baseUri(aBase) + , policy(aPolicy) {} + }; + + struct SchemaTarget { + const pjson* schema; + const pjson* resourceRoot; + std::string baseUri; + std::string location; + DialectPolicy policy; + SchemaTarget() + : schema(nullptr) + , resourceRoot(nullptr) {} + SchemaTarget(const pjson* aSchema, const pjson* aResourceRoot, const std::string& aBase, + const std::string& aLocation, const DialectPolicy& aPolicy) + : schema(aSchema) + , resourceRoot(aResourceRoot) + , baseUri(aBase) + , location(aLocation) + , policy(aPolicy) {} + }; + + struct ResolvedDocument { + std::string requestedUri; + pjson schema; + explicit ResolvedDocument(const std::string& aUri) + : requestedUri(aUri) {} + }; + + struct CompiledSchemaIndex { + std::deque documents; + std::deque dialectDocuments; + std::map resources; + std::map anchors; + std::map dynamicAnchors; + std::map nodeTargets; + std::set pendingDocuments; + std::set failedDocuments; + size_t resolvedBytes; + size_t workUsed; + + CompiledSchemaIndex() + : resolvedBytes(0) + , workUsed(0) {} + }; + + bool loadBuiltinSchema(const std::string& uri, pjson& output) { + std::string text; + if (!builtinSchemaText(stripFragment(uri), text)) + return false; + pJsonParser::Error error; + output = pJsonParser().parse(text, error); + return error.ok; + } + + struct SchemaAnnotations { + std::set properties; + std::set items; + + void merge(const SchemaAnnotations& other) { + properties.insert(other.properties.begin(), other.properties.end()); + items.insert(other.items.begin(), other.items.end()); + } + }; + + // Mutable limits and recursion state shared by one validation run. + struct ValidationCtx { + const pjson& rootSchema; + const Options& options; + const CompiledSchemaIndex& compiled; + std::vector* publicErrors; + size_t depth; + size_t refResolutions; + size_t workUsed; + size_t errorsUsed; + size_t diagnosticsUsed; + size_t publicErrorStart; + bool aborted; + std::vector> activeRefs; + std::map regexCache; + // Resource bases encountered along the reference evaluation path, + // outermost first. $dynamicRef searches these resources for a matching + // dynamic anchor after its initial static resolution. + std::vector dynamicScope; + + ValidationCtx(const pjson& aRootSchema, const Options& aOptions, + const CompiledSchemaIndex& aCompiled, std::vector* aPublicErrors) + : rootSchema(aRootSchema) + , options(aOptions) + , compiled(aCompiled) + , publicErrors(aPublicErrors) + , depth(0) + , refResolutions(0) + , workUsed(0) + , errorsUsed(0) + , diagnosticsUsed(0) + , publicErrorStart(aPublicErrors == nullptr ? 0 : aPublicErrors->size()) + , aborted(false) {} + }; + + struct SchemaBudgetExceeded {}; + + size_t diagnosticLimit(const Options& options) { + if (options.stopAfterFirstError) + return size_t(1); + return options.maxErrors == 0 ? size_t(100) : options.maxErrors; + } + + // Facade over a caller or speculative error vector enforcing one shared + // per-validation diagnostic budget. + struct ErrorSink { + enum Mode { Public, Causes, Discard }; + + std::vector& values; + ValidationCtx& ctx; + Mode mode; + size_t failures; + + ErrorSink(std::vector& aValues, ValidationCtx& aCtx, Mode aMode = Public) + : values(aValues) + , ctx(aCtx) + , mode(aMode) + , failures(0) {} + + size_t size() const { return failures; } + + void push_back(const SchemaError& error) { + if (ctx.aborted) + return; + if (failures != std::numeric_limits::max()) + ++failures; + if (mode == Discard) { + (void)error; + return; + } + const size_t limit = diagnosticLimit(ctx.options); + // Nested causes share the run-wide diagnostic budget and leave one + // slot for their enclosing public combinator error. + if (mode == Causes && ctx.diagnosticsUsed + size_t(1) >= limit) { + return; + } + if (mode == Public && (ctx.errorsUsed >= limit || ctx.diagnosticsUsed >= limit)) { + ctx.aborted = true; + if (ctx.publicErrors != nullptr && + ctx.publicErrors->size() - ctx.publicErrorStart < limit && + ctx.diagnosticsUsed < limit) { + try { + ctx.publicErrors->push_back( + SchemaError(SchemaError::ResourceLimit, SchemaError::InstanceValidation, + error.instanceLocation, error.schemaLocation, std::string(), + "schema validation error budget exceeded")); + ++ctx.diagnosticsUsed; + } catch (...) { + ctx.publicErrors = nullptr; + } + } + throw SchemaBudgetExceeded(); + } + values.push_back(error); + ++ctx.diagnosticsUsed; + if (mode == Public) { + ++ctx.errorsUsed; + if (ctx.options.stopAfterFirstError) { + ctx.aborted = true; + throw SchemaBudgetExceeded(); + } + } + } + }; + + std::string schemaLocationFor(const ValidationCtx& ctx, const pjson& schema, + const std::string& keyword = std::string()) { + std::map::const_iterator found = + ctx.compiled.nodeTargets.find(&schema); + const std::string base = + found == ctx.compiled.nodeTargets.end() ? std::string() : found->second.location; + return keyword.empty() ? base : pointerAppend(base, keyword); + } + + SchemaError validationError(const ValidationCtx& ctx, const pjson& schema, + SchemaError::Code code, const std::string& instanceLocation, + const std::string& keyword, const std::string& message) { + return SchemaError(code, SchemaError::InstanceValidation, instanceLocation, + schemaLocationFor(ctx, schema, keyword), keyword, message); + } + + void bestEffortSchemaError(std::vector& errors, SchemaError::Code code, + const std::string& path, const std::string& message) noexcept { + try { + errors.push_back(SchemaError(code, SchemaError::InstanceValidation, path, std::string(), + std::string(), message)); + } catch (...) { + return; + } + } + + struct DepthGuard { + ValidationCtx& ctx; + size_t levels; + explicit DepthGuard(ValidationCtx& aCtx) + : ctx(aCtx) + , levels(1) { + ++ctx.depth; + } + void enterResolvedReference() { + ++ctx.depth; + ++levels; + } + ~DepthGuard() { ctx.depth -= levels; } + }; + + struct ActiveRefGuard { + std::vector>& refs; + const size_t initialSize; + explicit ActiveRefGuard(std::vector>& aRefs) + : refs(aRefs) + , initialSize(aRefs.size()) {} + void push(const pjson* node, const pjson* schema) { + refs.push_back(std::make_pair(node, schema)); + } + ~ActiveRefGuard() { + while (refs.size() > initialSize) + refs.pop_back(); + } + }; + + struct DynamicScopeGuard { + std::vector& scope; + const size_t initialSize; + explicit DynamicScopeGuard(std::vector& aScope) + : scope(aScope) + , initialSize(aScope.size()) {} + void pushResource(const SchemaTarget& target) { + if (scope.empty() || scope.back().baseUri != target.baseUri || + scope.back().resourceRoot != target.resourceRoot) + scope.push_back(target); + } + ~DynamicScopeGuard() { scope.resize(initialSize); } + }; + + void failValidationBudget(ValidationCtx& ctx, ErrorSink& errors, const std::string& path, + const std::string& message) { + if (ctx.aborted) + return; + ctx.aborted = true; + const size_t errorLimit = diagnosticLimit(ctx.options); + if (ctx.errorsUsed >= errorLimit) + return; + std::vector& destination = + ctx.publicErrors != nullptr ? *ctx.publicErrors : errors.values; + const size_t before = destination.size(); + bestEffortSchemaError(destination, SchemaError::ResourceLimit, path, message); + if (destination.size() != before) { + ++ctx.errorsUsed; + ++ctx.diagnosticsUsed; + } + } + + size_t validationDepthLimit(const Options& options) { + const size_t requested = options.maxValidationDepth == 0 ? kSchemaValidationDepthHardLimit + : options.maxValidationDepth; + return std::min(requested, kSchemaValidationDepthHardLimit); + } + + size_t validationRefLimit(const Options& options) { + return options.maxRefResolutions == 0 ? size_t(1024) : options.maxRefResolutions; + } + + size_t validationWorkLimit(const Options& options) { + return options.maxValidationWork == 0 ? size_t(1000000) : options.maxValidationWork; + } + + size_t resolvedDocumentLimit(const Options& options) { + return options.maxResolvedDocuments == 0 ? size_t(32) : options.maxResolvedDocuments; + } + + size_t resolvedByteLimit(const Options& options) { + return options.maxResolvedBytes == 0 ? size_t(16) * 1024 * 1024 : options.maxResolvedBytes; + } + + bool chargeValidationWork(ValidationCtx& ctx, ErrorSink& errors, const std::string& path, + size_t amount = 1) { + const size_t limit = validationWorkLimit(ctx.options); + if (amount > limit - std::min(ctx.workUsed, limit)) { + failValidationBudget(ctx, errors, path, "schema validation work budget exceeded"); + return false; + } + ctx.workUsed += amount; + return true; + } + + bool chargeLoopWork(ValidationCtx& ctx, ErrorSink& errors, const std::string& path, + size_t amount = 1) { + return chargeValidationWork(ctx, errors, path, amount); + } + + bool unicodeLength(const std::string& value, ValidationCtx& ctx, ErrorSink& errors, + const std::string& path, size_t& count) { + count = 0; + for (size_t offset = 0; offset < value.size(); ++count) { + const int bytes = utf8Len(value.data(), offset, value.size()); + const size_t consumed = bytes > 0 ? static_cast(bytes) : size_t(1); + if (!chargeLoopWork(ctx, errors, path, consumed)) + return false; + offset += consumed; + } + return true; + } + + bool addSchemaError(ValidationCtx& ctx, ErrorSink& errors, const pjson& schema, + SchemaError::Code code, const std::string& path, const std::string& keyword, + const std::string& message) { + errors.push_back(validationError(ctx, schema, code, path, keyword, message)); + return !errors.ctx.aborted; + } + + bool evaluateRegex(const std::string& subject, const std::string& pattern, + const std::string& path, const pjson& schema, const std::string& keyword, + ErrorSink& errors, ValidationCtx& ctx, bool& matches) { + matches = false; + if (ctx.options.maxRegexSubjectBytes != 0 && + subject.size() > ctx.options.maxRegexSubjectBytes) { + errors.push_back(validationError( + ctx, schema, SchemaError::ResourceLimit, path, keyword, + "string exceeds regex safety limit (" + std::to_string(subject.size()) + + " bytes, limit " + std::to_string(ctx.options.maxRegexSubjectBytes) + ")")); + return false; + } + + RegexCacheEntry& cached = ctx.regexCache[pattern]; + if (cached.state == RegexCacheEntry::Uninitialized) { + if (!chargeLoopWork(ctx, errors, path, pattern.size() + size_t(1))) + return false; + if (ctx.options.maxRegexPatternBytes != 0 && + pattern.size() > ctx.options.maxRegexPatternBytes) { + cached.state = RegexCacheEntry::PatternTooLarge; + } else if (!ctx.options.allowUnsafeRegex && !isSafeRegex(pattern)) { + cached.state = RegexCacheEntry::UnsafePattern; + } else { + cached.state = cached.expression.compile(pattern) ? RegexCacheEntry::Ready + : RegexCacheEntry::InvalidPattern; + } + } + + if (cached.state == RegexCacheEntry::PatternTooLarge) { + errors.push_back(validationError(ctx, schema, SchemaError::RegexFailure, path, keyword, + "schema regex pattern exceeds safety limit")); + return false; + } + if (cached.state == RegexCacheEntry::UnsafePattern) { + errors.push_back(validationError(ctx, schema, SchemaError::RegexFailure, path, keyword, + "schema regex pattern rejected by safety policy")); + return false; + } + if (cached.state == RegexCacheEntry::InvalidPattern) { + errors.push_back(validationError(ctx, schema, SchemaError::RegexFailure, path, keyword, + "schema has an invalid regex pattern")); + return false; + } + if (!chargeLoopWork(ctx, errors, path, subject.size() + size_t(1))) + return false; + const EcmaRegex::Result result = cached.expression.search(subject); + if (result == EcmaRegex::WorkLimit) { + errors.push_back(validationError(ctx, schema, SchemaError::ResourceLimit, path, keyword, + "schema regex work limit exceeded")); + return false; + } + if (result == EcmaRegex::Invalid) { + errors.push_back(validationError(ctx, schema, SchemaError::RegexFailure, path, keyword, + "schema regex evaluation failed")); + return false; + } + matches = result == EcmaRegex::Match; + return true; + } + + bool isSupportedSchemaKeyword(const std::string& keyword) { + static const char* const kSupported[] = { + "type", + "enum", + "const", + "$ref", + "properties", + "patternProperties", + "propertyNames", + "required", + "dependentRequired", + "dependencies", + "dependentSchemas", + "additionalProperties", + "minProperties", + "maxProperties", + "items", + "prefixItems", + "contains", + "minContains", + "maxContains", + "minItems", + "maxItems", + "uniqueItems", + "minimum", + "maximum", + "exclusiveMinimum", + "exclusiveMaximum", + "multipleOf", + "minLength", + "maxLength", + "pattern", + "format", + "allOf", + "anyOf", + "oneOf", + "not", + "if", + "then", + "else", + // Metadata/identifier keywords impose no constraint and are always safe. + "$schema", + "$id", + "$anchor", + "$dynamicAnchor", + "$dynamicRef", + "$vocabulary", + "$defs", + "$comment", + "definitions", + "title", + "description", + "default", + "examples", + "deprecated", + "readOnly", + "writeOnly", + "unevaluatedItems", + "unevaluatedProperties", + }; + for (const char* name : kSupported) { + if (keyword == name) + return true; + } + return false; + } + + bool isStandardSchemaKeyword(const std::string& keyword) { + static const char* const kStandardUnsupported[] = { + "$recursiveRef", "$recursiveAnchor", "additionalItems", + "contentEncoding", "contentMediaType", "contentSchema", + }; + for (const char* name : kStandardUnsupported) { + if (keyword == name) + return true; + } + return false; + } + + std::string absoluteSchemaLocation(const std::string& documentUri, const std::string& pointer) { + return documentUri.empty() ? pointer : stripFragment(documentUri) + "#" + pointer; + } + + void addCompilationError(std::vector& errors, SchemaError::Code code, + const std::string& path, const std::string& keyword, + const std::string& message) { + errors.push_back(SchemaError(code, SchemaError::SchemaCompilation, std::string(), path, + keyword, message)); + } + + bool isSchemaNode(const pjson& value) { + return value.isObject() || value.isBool(); + } + + bool isFiniteSchemaNumber(const pjson& value) { + if (!value.isNumber()) + return false; + if (!value.isDouble()) + return true; + double number = 0.0; + return value.tryGet(number) && std::isfinite(number); + } + + bool isNonnegativeInteger(const pjson& value) { + size_t ignored = 0; + bool aboveRange = false; + return schemaSize(value, ignored, aboveRange); + } + + bool validTypeName(const std::string& value) { + static const char* const kTypes[] = { + "null", "boolean", "object", "array", "number", "string", "integer", + }; + for (const char* type : kTypes) { + if (value == type) + return true; + } + return false; + } + + bool isUniqueStringArray(const pjson& value, bool allowEmpty) { + if (!value.isArray() || (!allowEmpty && value.empty())) + return false; + std::set seen; + for (size_t i = 0; i < value.size(); ++i) { + const pjson* item = value.findIndex(i); + if (item == nullptr || !item->isString() || !seen.insert(strOf(*item)).second) + return false; + } + return true; + } + + bool isTypeDeclaration(const pjson& value) { + if (value.isString()) + return validTypeName(strOf(value)); + if (!isUniqueStringArray(value, false)) + return false; + for (size_t i = 0; i < value.size(); ++i) { + const pjson* item = value.findIndex(i); + if (item == nullptr || !validTypeName(strOf(*item))) + return false; + } + return true; + } + + bool hasDuplicateArrayValue(const pjson& value) { + if (!value.isArray()) + return false; + for (size_t i = 0; i < value.size(); ++i) { + const pjson* left = value.findIndex(i); + for (size_t j = i + 1; left != nullptr && j < value.size(); ++j) { + const pjson* right = value.findIndex(j); + if (right != nullptr && *left == *right) + return true; + } + } + return false; + } + + // Validates every implemented keyword before strict-mode validation begins. + // Permissive mode deliberately retains the historical ignore-malformed behavior. + void validateKeywordShapes(const pjson& schema, const Options& options, + std::vector& errors, const std::string& documentUri, + const std::string& path) { + if (!options.strictSubset || !schema.isObject()) + return; + const size_t limit = diagnosticLimit(options); + const auto reject = [&](const char* keyword, const std::string& expectation, + SchemaError::Code code = SchemaError::InvalidSchema) { + if (errors.size() < limit) + addCompilationError( + errors, code, absoluteSchemaLocation(documentUri, pointerAppend(path, keyword)), + keyword, std::string(keyword) + " " + expectation); + }; + const auto valueOf = [&](const char* keyword) { return schema.find(keyword); }; + const auto rejectSchemaContainerValues = [&](const char* keyword, const pjson& value) { + const std::vector keys = value.keys(); + for (size_t i = 0; i < keys.size() && errors.size() < limit; ++i) { + const pjson* child = value.find(keys[i]); + if (child == nullptr || !isSchemaNode(*child)) + addCompilationError( + errors, SchemaError::InvalidSchema, + absoluteSchemaLocation( + documentUri, pointerAppend(pointerAppend(path, keyword), keys[i])), + keyword, "schema must be an object or boolean"); + } + }; + const auto rejectSchemaArrayValues = [&](const char* keyword, const pjson& value) { + for (size_t i = 0; i < value.size() && errors.size() < limit; ++i) { + const pjson* child = value.findIndex(i); + if (child == nullptr || !isSchemaNode(*child)) + addCompilationError(errors, SchemaError::InvalidSchema, + absoluteSchemaLocation( + documentUri, pointerAppend(pointerAppend(path, keyword), + std::to_string(i))), + keyword, "schema must be an object or boolean"); + } + }; + + if (const pjson* value = valueOf("type")) { + if (!isTypeDeclaration(*value)) + reject("type", + "must be a valid type name or a non-empty array of unique type names"); + } + if (const pjson* value = valueOf("enum")) { + if (!value->isArray() || value->empty() || hasDuplicateArrayValue(*value)) + reject("enum", "must be a non-empty array of unique values"); + } + for (const char* keyword : + {"$schema", "$comment", "title", "description", "pattern", "format"}) { + const pjson* value = valueOf(keyword); + if (value != nullptr && !value->isString()) + reject(keyword, "must be a string"); + } + for (const char* keyword : {"deprecated", "readOnly", "writeOnly", "uniqueItems"}) { + const pjson* value = valueOf(keyword); + if (value != nullptr && !value->isBool()) + reject(keyword, "must be a boolean"); + } + for (const char* keyword : {"minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum"}) { + const pjson* value = valueOf(keyword); + if (value != nullptr && !isFiniteSchemaNumber(*value)) + reject(keyword, "must be a finite number"); + } + if (const pjson* value = valueOf("multipleOf")) { + if (!isFiniteSchemaNumber(*value) || numberAsDouble(*value) <= 0.0) + reject("multipleOf", "must be a finite number greater than zero"); + } + for (const char* keyword : {"minLength", "maxLength", "minItems", "maxItems", "minContains", + "maxContains", "minProperties", "maxProperties"}) { + const pjson* value = valueOf(keyword); + if (value != nullptr && !isNonnegativeInteger(*value)) + reject(keyword, "must be a non-negative integer"); + } + for (const char* keyword : + {"additionalProperties", "unevaluatedProperties", "unevaluatedItems", "contains", + "propertyNames", "not", "if", "then", "else"}) { + const pjson* value = valueOf(keyword); + if (value != nullptr && !isSchemaNode(*value)) + reject(keyword, "must be an object or boolean schema"); + } + if (const pjson* value = valueOf("items")) { + if (!isSchemaNode(*value) && !value->isArray()) + reject("items", "must be a schema or an array of schemas"); + else if (value->isArray()) + rejectSchemaArrayValues("items", *value); + } + for (const char* keyword : {"allOf", "anyOf", "oneOf"}) { + const pjson* value = valueOf(keyword); + if (value != nullptr) { + if (!value->isArray() || value->empty()) + reject(keyword, "must be a non-empty array of schemas"); + else + rejectSchemaArrayValues(keyword, *value); + } + } + if (const pjson* value = valueOf("prefixItems")) { + if (!value->isArray() || value->empty()) + reject("prefixItems", "must be a non-empty array of schemas"); + else + rejectSchemaArrayValues("prefixItems", *value); + } + for (const char* keyword : + {"$defs", "definitions", "properties", "patternProperties", "dependentSchemas"}) { + const pjson* value = valueOf(keyword); + if (value != nullptr) { + if (!value->isObject()) + reject(keyword, "must be an object whose values are schemas"); + else + rejectSchemaContainerValues(keyword, *value); + } + } + if (const pjson* value = valueOf("required")) { + if (!isUniqueStringArray(*value, true)) + reject("required", "must be an array of unique strings"); + } + if (const pjson* value = valueOf("dependentRequired")) { + if (!value->isObject()) { + reject("dependentRequired", "must be an object"); + } else { + const std::vector keys = value->keys(); + for (size_t i = 0; i < keys.size(); ++i) { + const pjson* entry = value->find(keys[i]); + if (entry == nullptr || !isUniqueStringArray(*entry, true)) { + addCompilationError( + errors, SchemaError::InvalidSchema, + absoluteSchemaLocation( + documentUri, + pointerAppend(pointerAppend(path, "dependentRequired"), keys[i])), + "dependentRequired", "value must be an array of unique strings"); + break; + } + } + } + } + if (const pjson* value = valueOf("dependencies")) { + if (!value->isObject()) { + reject("dependencies", "must be an object"); + } else { + const std::vector keys = value->keys(); + for (size_t i = 0; i < keys.size(); ++i) { + const pjson* entry = value->find(keys[i]); + if (entry == nullptr || + (!isSchemaNode(*entry) && !isUniqueStringArray(*entry, true))) { + addCompilationError( + errors, SchemaError::InvalidSchema, + absoluteSchemaLocation( + documentUri, + pointerAppend(pointerAppend(path, "dependencies"), keys[i])), + "dependencies", "value must be a schema or an array of unique strings"); + break; + } + } + } + } + if (const pjson* value = valueOf("$vocabulary")) { + if (!value->isObject()) { + reject("$vocabulary", "must be an object mapping URI strings to booleans"); + } else { + const std::vector keys = value->keys(); + for (size_t i = 0; i < keys.size(); ++i) { + const pjson* entry = value->find(keys[i]); + if (entry == nullptr || !entry->isBool()) { + reject("$vocabulary", "entries must be boolean"); + break; + } + } + } + } + if (const pjson* value = valueOf("examples")) { + if (!value->isArray()) + reject("examples", "must be an array"); + } + const auto rejectRegex = [&](const std::string& pattern, const std::string& location, + const char* keyword) { + if (options.maxRegexPatternBytes != 0 && + pattern.size() > options.maxRegexPatternBytes) { + if (errors.size() < limit) + addCompilationError(errors, SchemaError::RegexFailure, location, keyword, + "schema regex pattern exceeds safety limit"); + return; + } + if (!validEcmaRegex(pattern)) { + if (errors.size() < limit) + addCompilationError(errors, SchemaError::RegexFailure, location, keyword, + "schema has an invalid regex pattern"); + } + }; + if (const pjson* value = valueOf("pattern")) { + if (value->isString()) + rejectRegex(strOf(*value), + absoluteSchemaLocation(documentUri, pointerAppend(path, "pattern")), + "pattern"); + } + if (const pjson* value = valueOf("patternProperties")) { + if (value->isObject()) { + const std::vector patterns = value->keys(); + for (size_t i = 0; i < patterns.size() && errors.size() < limit; ++i) + rejectRegex( + patterns[i], + absoluteSchemaLocation( + documentUri, + pointerAppend(pointerAppend(path, "patternProperties"), patterns[i])), + "patternProperties"); + } + } + for (const std::string& keyword : schema.keys()) { + if (errors.size() >= limit) + break; + if (!isSupportedSchemaKeyword(keyword) && isStandardSchemaKeyword(keyword)) + reject(keyword.c_str(), "is not supported by the selected pjson dialect", + SchemaError::UnsupportedKeyword); + } + } + + bool policyFromVocabulary(const pjson& vocabularies, const Options& options, + DialectPolicy& policy, std::vector& errors, + const std::string& location) { + const size_t errorLimit = diagnosticLimit(options); + if (!vocabularies.isObject()) { + addCompilationError(errors, SchemaError::InvalidSchema, location, "$vocabulary", + "$vocabulary must be an object mapping URI strings to booleans"); + return false; + } + policy = DialectPolicy(); + policy.refSiblings = true; + const std::vector uris = vocabularies.keys(); + for (size_t i = 0; i < uris.size() && errors.size() < errorLimit; ++i) { + const pjson* requirement = vocabularies.find(uris[i]); + const std::string path = location + "/" + pjson::escapePointerToken(uris[i]); + if (requirement == nullptr || !requirement->isBool()) { + addCompilationError(errors, SchemaError::InvalidSchema, path, "$vocabulary", + "$vocabulary entries must be boolean"); + continue; + } + if (uris[i] == documentedSubsetVocabulary()) { + policy = subsetPolicy(options); + continue; + } + Vocabulary vocabulary = VCore; + if (vocabularyForUri(uris[i], vocabulary)) { + policy.vocabularies |= static_cast(vocabulary); + continue; + } + bool required = false; + requirement->tryGet(required); + if (required) + addCompilationError(errors, SchemaError::UnsupportedVocabulary, path, "$vocabulary", + "unsupported required schema vocabulary: " + uris[i]); + } + policy.assertFormats = false; + return errors.empty(); + } + + bool loadDialectDocument(const std::string& dialect, const Options& options, + CompiledSchemaIndex& index, const pjson*& document, + std::vector& errors, const std::string& location) { + for (size_t i = 0; i < index.dialectDocuments.size(); ++i) { + if (index.dialectDocuments[i].requestedUri == dialect) { + document = &index.dialectDocuments[i].schema; + return true; + } + } + if (!options.resolveCustomDialects || options.resolver == nullptr) { + addCompilationError(errors, SchemaError::UnsupportedDialect, location, "$schema", + "unsupported schema dialect: " + dialect); + return false; + } + if (index.documents.size() + index.dialectDocuments.size() >= + resolvedDocumentLimit(options)) { + addCompilationError(errors, SchemaError::ResourceLimit, location, "$schema", + "schema resolved-document budget exceeded"); + return false; + } + pjson loaded; + bool resolved = false; + try { + resolved = loadBuiltinSchema(dialect, loaded); + if (!resolved) + resolved = + options.resolver(stripFragment(dialect), loaded, options.resolverContext); + } catch (...) { + addCompilationError(errors, SchemaError::ResolverFailure, location, "$schema", + "schema dialect resolver threw for " + dialect); + return false; + } + if (!resolved || !loaded.isObject()) { + addCompilationError(errors, SchemaError::ResolverFailure, location, "$schema", + "schema dialect resolution failed: " + dialect); + return false; + } + index.dialectDocuments.push_back(ResolvedDocument(dialect)); + index.dialectDocuments.back().schema.copyFrom(loaded); + const size_t byteLimit = resolvedByteLimit(options); + try { + pjson::SerializeOptions compactOptions; + compactOptions.maxOutputBytes = + index.resolvedBytes < byteLimit ? byteLimit - index.resolvedBytes : 1; + const std::string compact = + index.dialectDocuments.back().schema.toString(compactOptions); + if (index.resolvedBytes >= byteLimit || + compact.size() > byteLimit - index.resolvedBytes) { + index.dialectDocuments.pop_back(); + addCompilationError(errors, SchemaError::ResourceLimit, location, "$schema", + "schema resolved-byte budget exceeded"); + return false; + } + index.resolvedBytes += compact.size(); + } catch (const std::exception&) { + index.dialectDocuments.pop_back(); + addCompilationError(errors, SchemaError::ResourceLimit, location, "$schema", + "schema dialect exceeds the resolved-byte budget"); + return false; + } + document = &index.dialectDocuments.back().schema; + return true; + } + + // Establishes one schema resource's dialect and required-vocabulary contract. + void compileDialectContract(const pjson& schema, const Options& options, + CompiledSchemaIndex& index, std::string& dialect, + DialectPolicy& policy, std::vector& errors, + const std::string& location = std::string()) { + dialect = options.defaultDialectUri.empty() ? documentedSubsetDialect() + : options.defaultDialectUri; + + if (schema.isObject()) { + const pjson* declared = schema.find("$schema"); + if (declared != nullptr) { + if (!declared->isString()) { + addCompilationError(errors, SchemaError::InvalidSchema, + pointerAppend(location, "$schema"), "$schema", + "$schema must be a string URI"); + return; + } else { + dialect = strOf(*declared); + } + } + } + + if (dialect == documentedSubsetDialect()) + policy = subsetPolicy(options); + else if (dialect == draft2020Dialect() && options.defaultDialectUri == draft2020Dialect()) + policy = draft2020Policy(options); + else { + const pjson* metaSchema = nullptr; + if (!loadDialectDocument(dialect, options, index, metaSchema, errors, + pointerAppend(location, "$schema"))) + return; + const pjson* declared = metaSchema->find("$vocabulary"); + if (declared == nullptr) { + addCompilationError(errors, SchemaError::InvalidSchema, + pointerAppend(location, "$schema"), "$schema", + "resolved meta-schema does not declare $vocabulary"); + return; + } + policyFromVocabulary(*declared, options, policy, errors, + pointerAppend(dialect + "#", "$vocabulary")); + return; + } + + if (!schema.isObject()) + return; + const pjson* vocabularies = schema.find("$vocabulary"); + if (vocabularies == nullptr) + return; + policyFromVocabulary(*vocabularies, options, policy, errors, + pointerAppend(location, "$vocabulary")); + } + + void compileSchemaResource(const pjson& node, const pjson* resourceRoot, + const std::string& inheritedBase, CompiledSchemaIndex& index, + std::vector& errors, const Options& options, + const DialectPolicy& inheritedPolicy, const std::string& path, + size_t depth = 0, const std::string& documentUri = std::string()) { + const size_t errorLimit = diagnosticLimit(options); + if (errors.size() >= errorLimit) + return; + if (depth >= validationDepthLimit(options)) { + addCompilationError(errors, SchemaError::ResourceLimit, + absoluteSchemaLocation(documentUri, path), std::string(), + "schema compilation depth budget exceeded"); + return; + } + const size_t workLimit = validationWorkLimit(options); + if (index.workUsed >= workLimit) { + addCompilationError(errors, SchemaError::ResourceLimit, + absoluteSchemaLocation(documentUri, path), std::string(), + "schema compilation work budget exceeded"); + return; + } + ++index.workUsed; + if (!node.isObject() && !node.isBool()) { + if (options.strictSubset) + addCompilationError(errors, SchemaError::InvalidSchema, + absoluteSchemaLocation(documentUri, path), std::string(), + "schema must be an object or boolean"); + return; + } + const pjson* currentResource = resourceRoot; + std::string currentBase = inheritedBase; + DialectPolicy currentPolicy = inheritedPolicy; + if (node.isBool()) + index.nodeTargets[&node] = + SchemaTarget(&node, currentResource, currentBase, + absoluteSchemaLocation(documentUri, path), currentPolicy); + if (node.isObject()) { + const pjson* id = node.find("$id"); + if (id != nullptr && !id->isString()) { + addCompilationError(errors, SchemaError::InvalidSchema, + absoluteSchemaLocation(documentUri, pointerAppend(path, "$id")), + "$id", "$id must be a string URI-reference"); + return; + } + if (id != nullptr) { + currentBase = stripFragment(resolveUri(inheritedBase, strOf(*id))); + currentResource = &node; + if (resourceRoot != &node && node.find("$schema") != nullptr) { + std::string nestedDialect; + compileDialectContract(node, options, index, nestedDialect, currentPolicy, + errors, absoluteSchemaLocation(documentUri, path)); + } + } + if (!currentBase.empty()) { + std::map::const_iterator existing = + index.resources.find(currentBase); + if (existing != index.resources.end() && existing->second.root != currentResource) { + addCompilationError( + errors, SchemaError::InvalidSchema, + absoluteSchemaLocation(documentUri, pointerAppend(path, "$id")), "$id", + "duplicate schema resource identifier: " + currentBase); + return; + } + index.resources[currentBase] = + SchemaResource(currentResource, currentBase, currentPolicy); + } + const SchemaTarget nodeTarget(&node, currentResource, currentBase, + absoluteSchemaLocation(documentUri, path), currentPolicy); + index.nodeTargets[&node] = nodeTarget; + validateKeywordShapes(node, options, errors, documentUri, path); + if (errors.size() >= errorLimit) + return; + + const pjson* anchor = node.find("$anchor"); + if (anchor != nullptr && (!anchor->isString() || !validAnchorName(strOf(*anchor)))) { + addCompilationError( + errors, SchemaError::InvalidSchema, + absoluteSchemaLocation(documentUri, pointerAppend(path, "$anchor")), "$anchor", + "$anchor must be a valid anchor name"); + return; + } + if (anchor != nullptr) { + const std::string name = strOf(*anchor); + const std::string key = currentBase + "#" + name; + if (index.anchors.find(key) != index.anchors.end()) { + addCompilationError( + errors, SchemaError::InvalidSchema, + absoluteSchemaLocation(documentUri, pointerAppend(path, "$anchor")), + "$anchor", "duplicate schema anchor: " + key); + return; + } + index.anchors[key] = nodeTarget; + } + const pjson* dynamicAnchor = node.find("$dynamicAnchor"); + if (dynamicAnchor != nullptr && + (!dynamicAnchor->isString() || !validAnchorName(strOf(*dynamicAnchor)))) { + addCompilationError( + errors, SchemaError::InvalidSchema, + absoluteSchemaLocation(documentUri, pointerAppend(path, "$dynamicAnchor")), + "$dynamicAnchor", "$dynamicAnchor must be a valid anchor name"); + return; + } + if (dynamicAnchor != nullptr) { + const std::string name = strOf(*dynamicAnchor); + const std::string key = currentBase + "#" + name; + if (index.dynamicAnchors.find(key) != index.dynamicAnchors.end() || + index.anchors.find(key) != index.anchors.end()) { + addCompilationError( + errors, SchemaError::InvalidSchema, + absoluteSchemaLocation(documentUri, pointerAppend(path, "$dynamicAnchor")), + "$dynamicAnchor", "duplicate schema anchor: " + key); + return; + } + index.dynamicAnchors[key] = nodeTarget; + index.anchors[key] = nodeTarget; + } + + for (const char* keyword : {"$ref", "$dynamicRef"}) { + const pjson* reference = node.find(keyword); + if (reference == nullptr) + continue; + if (!reference->isString()) { + addCompilationError( + errors, SchemaError::InvalidSchema, + absoluteSchemaLocation(documentUri, pointerAppend(path, keyword)), keyword, + std::string(keyword) + " must be a string URI-reference"); + continue; + } + std::string document; + std::string fragment; + splitReference(resolveUri(currentBase, strOf(*reference)), document, fragment); + if (!document.empty()) + index.pendingDocuments.insert(document); + } + + // Traverse only positions whose values are schemas. Objects stored + // in const/default/examples or application annotations are instance + // data and must never create resources or anchors. + for (const char* keyword : + {"additionalProperties", "unevaluatedProperties", "unevaluatedItems", "items", + "contains", "propertyNames", "not", "if", "then", "else"}) { + const pjson* child = node.find(keyword); + if (child != nullptr && (child->isObject() || child->isBool())) + compileSchemaResource(*child, currentResource, currentBase, index, errors, + options, currentPolicy, pointerAppend(path, keyword), + depth + 1, documentUri); + } + for (const char* keyword : + {"$defs", "definitions", "properties", "patternProperties", "dependentSchemas"}) { + const pjson* container = node.find(keyword); + if (container == nullptr || !container->isObject()) + continue; + const std::vector names = container->keys(); + for (size_t i = 0; i < names.size(); ++i) { + const pjson* child = container->find(names[i]); + if (child != nullptr) + compileSchemaResource(*child, currentResource, currentBase, index, errors, + options, currentPolicy, + pointerAppend(pointerAppend(path, keyword), names[i]), + depth + 1, documentUri); + } + } + // Legacy dependencies may contain either property-name arrays or schemas. + if (const pjson* dependencies = node.find("dependencies")) { + if (dependencies->isObject()) { + const std::vector names = dependencies->keys(); + for (size_t i = 0; i < names.size(); ++i) { + const pjson* child = dependencies->find(names[i]); + if (child != nullptr && (child->isObject() || child->isBool())) + compileSchemaResource( + *child, currentResource, currentBase, index, errors, options, + currentPolicy, + pointerAppend(pointerAppend(path, "dependencies"), names[i]), + depth + 1, documentUri); + } + } + } + for (const char* keyword : {"allOf", "anyOf", "oneOf", "prefixItems"}) { + const pjson* array = node.find(keyword); + if (array == nullptr || !array->isArray()) + continue; + for (size_t i = 0; i < array->size(); ++i) { + const pjson* child = array->findIndex(i); + if (child != nullptr) + compileSchemaResource( + *child, currentResource, currentBase, index, errors, options, + currentPolicy, + pointerAppend(pointerAppend(path, keyword), std::to_string(i)), + depth + 1, documentUri); + } + } + // Draft 7 tuple-form items is an array of schemas. + if (const pjson* items = node.find("items")) { + if (items->isArray()) { + for (size_t i = 0; i < items->size(); ++i) { + const pjson* child = items->findIndex(i); + if (child != nullptr) + compileSchemaResource( + *child, currentResource, currentBase, index, errors, options, + currentPolicy, + pointerAppend(pointerAppend(path, "items"), std::to_string(i)), + depth + 1, documentUri); + } + } + } + } + return; + } + + void compileExternalResources(CompiledSchemaIndex& index, const Options& options, + std::vector& errors) { + const size_t errorLimit = diagnosticLimit(options); + while (!index.pendingDocuments.empty() && errors.size() < errorLimit) { + const std::string documentUri = *index.pendingDocuments.begin(); + index.pendingDocuments.erase(index.pendingDocuments.begin()); + if (index.resources.find(documentUri) != index.resources.end()) + continue; + if (!uriHasScheme(documentUri)) { + addCompilationError( + errors, SchemaError::ReferenceFailure, "", "$ref", + "relative external schema reference requires a retrieval URI or root $id: " + + documentUri); + index.failedDocuments.insert(documentUri); + continue; + } + + // A dialect document was already resolved, copied, and charged by + // loadDialectDocument(). Compile that stable owned copy directly + // instead of counting and copying the same URI a second time. + const pjson* existingDialectDocument = nullptr; + for (size_t i = 0; i < index.dialectDocuments.size(); ++i) { + if (index.dialectDocuments[i].requestedUri == documentUri) { + existingDialectDocument = &index.dialectDocuments[i].schema; + break; + } + } + if (existingDialectDocument != nullptr) { + std::string resolvedDialect; + DialectPolicy resolvedPolicy; + const size_t beforeContract = errors.size(); + compileDialectContract(*existingDialectDocument, options, index, resolvedDialect, + resolvedPolicy, errors, documentUri + "#"); + if (errors.size() != beforeContract) { + index.failedDocuments.insert(documentUri); + continue; + } + index.resources[documentUri] = + SchemaResource(existingDialectDocument, documentUri, resolvedPolicy); + compileSchemaResource(*existingDialectDocument, existingDialectDocument, + documentUri, index, errors, options, resolvedPolicy, "", 0, + documentUri); + continue; + } + std::string builtinText; + const bool hasBuiltin = builtinSchemaText(documentUri, builtinText); + if (options.resolver == nullptr && !hasBuiltin) { + addCompilationError(errors, SchemaError::ResolverFailure, "", "$ref", + "no resolver for external schema: " + documentUri); + index.failedDocuments.insert(documentUri); + continue; + } + if (index.documents.size() + index.dialectDocuments.size() >= + resolvedDocumentLimit(options)) { + addCompilationError(errors, SchemaError::ResourceLimit, "", "$ref", + "schema resolved-document budget exceeded"); + index.failedDocuments.insert(documentUri); + return; + } + + index.documents.push_back(ResolvedDocument(documentUri)); + ResolvedDocument& loaded = index.documents.back(); + pjson temporary; + bool resolved = false; + try { + resolved = loadBuiltinSchema(documentUri, temporary); + if (!resolved && options.resolver != nullptr) + resolved = options.resolver(documentUri, temporary, options.resolverContext); + } catch (const std::exception& exception) { + index.documents.pop_back(); + addCompilationError(errors, SchemaError::ResolverFailure, documentUri, "$ref", + "external schema resolver threw for " + documentUri + ": " + + exception.what()); + index.failedDocuments.insert(documentUri); + continue; + } catch (...) { + index.documents.pop_back(); + addCompilationError(errors, SchemaError::ResolverFailure, documentUri, "$ref", + "external schema resolver threw for " + documentUri); + index.failedDocuments.insert(documentUri); + continue; + } + if (!resolved) { + index.documents.pop_back(); + addCompilationError(errors, SchemaError::ResolverFailure, documentUri, "$ref", + "external schema resolution failed: " + documentUri); + index.failedDocuments.insert(documentUri); + continue; + } + loaded.schema.copyFrom(temporary); + std::string resolvedDialect; + DialectPolicy resolvedPolicy; + const size_t beforeContract = errors.size(); + compileDialectContract(loaded.schema, options, index, resolvedDialect, resolvedPolicy, + errors, documentUri + "#"); + if (errors.size() != beforeContract) { + index.documents.pop_back(); + index.failedDocuments.insert(documentUri); + continue; + } + const size_t limit = resolvedByteLimit(options); + if (index.resolvedBytes >= limit) { + index.documents.pop_back(); + addCompilationError(errors, SchemaError::ResourceLimit, documentUri, "$ref", + "schema resolved-byte budget exceeded"); + index.failedDocuments.insert(documentUri); + return; + } + std::string compact; + try { + pjson::SerializeOptions compactOptions; + compactOptions.maxOutputBytes = limit - index.resolvedBytes; + compact = loaded.schema.toString(compactOptions); + } catch (const std::length_error&) { + index.documents.pop_back(); + addCompilationError(errors, SchemaError::ResourceLimit, documentUri, "$ref", + "schema resolved-byte budget exceeded"); + index.failedDocuments.insert(documentUri); + return; + } catch (const std::exception& exception) { + index.documents.pop_back(); + addCompilationError(errors, SchemaError::InvalidSchema, documentUri, std::string(), + "resolved schema is not serializable: " + + std::string(exception.what())); + index.failedDocuments.insert(documentUri); + continue; + } + if (compact.size() > limit - std::min(index.resolvedBytes, limit)) { + index.documents.pop_back(); + addCompilationError(errors, SchemaError::ResourceLimit, documentUri, "$ref", + "schema resolved-byte budget exceeded"); + index.failedDocuments.insert(documentUri); + return; + } + index.resolvedBytes += compact.size(); + + const pjson* root = &loaded.schema; + // Keep the retrieval URI as an alias, then let compilation apply the + // root `$id` exactly once relative to that retrieval URI. + index.resources[documentUri] = SchemaResource(root, documentUri, resolvedPolicy); + compileSchemaResource(*root, root, documentUri, index, errors, options, resolvedPolicy, + "", 0, documentUri); + } + } + + bool resolveCompiledTarget(const std::string& reference, const std::string& baseUri, + const CompiledSchemaIndex& index, SchemaTarget& target) { + const std::string absolute = resolveUri(baseUri, reference); + std::string document; + std::string fragment; + splitReference(absolute, document, fragment); + if (document.empty()) + document = stripFragment(baseUri); + std::map::const_iterator resource = + index.resources.find(document); + if (resource == index.resources.end()) + return false; + + const pjson* root = resource->second.root; + if (fragment.empty()) { + target = SchemaTarget(root, root, resource->second.baseUri, + absoluteSchemaLocation(document, ""), resource->second.policy); + return true; + } + + std::string decoded; + if (!percentDecodeFragment(fragment, decoded)) + return false; + if (decoded.empty() || decoded[0] != '/') { + const std::string key = document + "#" + decoded; + std::map::const_iterator anchor = index.anchors.find(key); + if (anchor == index.anchors.end()) + return false; + target = anchor->second; + return true; + } + + pjson::PointerError error; + const pjson* selected = root->findPointer(decoded, error); + if (selected == nullptr) + return false; + std::map::const_iterator indexed = + index.nodeTargets.find(selected); + target = + indexed == index.nodeTargets.end() + ? SchemaTarget(selected, root, resource->second.baseUri, + absoluteSchemaLocation(document, decoded), resource->second.policy) + : indexed->second; + return true; + } + + void validateCompiledReferences(const CompiledSchemaIndex& index, const Options& options, + std::vector& errors) { + const size_t errorLimit = diagnosticLimit(options); + for (std::map::const_iterator it = index.nodeTargets.begin(); + it != index.nodeTargets.end() && errors.size() < errorLimit; ++it) { + const pjson* schema = it->first; + if (!schema->isObject()) + continue; + for (const char* keyword : {"$ref", "$dynamicRef"}) { + const pjson* reference = schema->find(keyword); + if (reference == nullptr || !reference->isString()) + continue; + SchemaTarget target; + if (!resolveCompiledTarget(strOf(*reference), it->second.baseUri, index, target)) { + std::string document; + std::string fragment; + splitReference(resolveUri(it->second.baseUri, strOf(*reference)), document, + fragment); + if (index.failedDocuments.find(document) != index.failedDocuments.end()) + continue; + addCompilationError(errors, SchemaError::ReferenceFailure, + pointerAppend(it->second.location, keyword), keyword, + std::string("unresolved ") + keyword + ": " + + strOf(*reference)); + } else if (options.strictSubset && target.schema != nullptr && + !target.schema->isObject() && !target.schema->isBool()) { + addCompilationError(errors, SchemaError::InvalidSchema, + pointerAppend(it->second.location, keyword), keyword, + std::string(keyword) + + " target must be an object or boolean schema"); + } + } + } + } + + bool resolveSchemaReference(const std::string& reference, const pjson* resourceRoot, + const std::string& baseUri, ValidationCtx& ctx, ErrorSink& errors, + const std::string& path, const pjson& schema, + const std::string& keyword, SchemaTarget& target) { + const std::string absolute = resolveUri(baseUri, reference); + std::string document; + std::string fragment; + splitReference(absolute, document, fragment); + std::string decodedFragment; + if (!percentDecodeFragment(fragment, decodedFragment)) { + errors.push_back(validationError(ctx, schema, SchemaError::ReferenceFailure, path, + keyword, "malformed schema reference: " + reference)); + return false; + } + + if (document.empty()) + document = stripFragment(baseUri); + std::map::const_iterator resource = + ctx.compiled.resources.find(document); + if (resource == ctx.compiled.resources.end()) { + errors.push_back(validationError(ctx, schema, SchemaError::ReferenceFailure, path, + keyword, + "unresolved compiled schema resource: " + document)); + return false; + } + + const pjson* root = resource->second.root != nullptr ? resource->second.root : resourceRoot; + if (fragment.empty()) { + target = SchemaTarget(root, root, resource->second.baseUri, + absoluteSchemaLocation(document, ""), resource->second.policy); + return true; + } + if (decodedFragment.empty() || decodedFragment[0] != '/') { + const std::string anchorKey = document + "#" + decodedFragment; + std::map::const_iterator found = + ctx.compiled.anchors.find(anchorKey); + if (found == ctx.compiled.anchors.end()) { + errors.push_back(validationError(ctx, schema, SchemaError::ReferenceFailure, path, + keyword, "unresolved schema anchor: " + absolute)); + return false; + } + target = found->second; + return true; + } + + pjson::PointerError pointerError; + const pjson* selected = root->findPointer(decodedFragment, pointerError); + if (selected == nullptr) { + errors.push_back(validationError(ctx, schema, SchemaError::ReferenceFailure, path, + keyword, "unresolved schema reference: " + reference)); + return false; + } + std::map::const_iterator indexed = + ctx.compiled.nodeTargets.find(selected); + target = indexed == ctx.compiled.nodeTargets.end() + ? SchemaTarget(selected, root, resource->second.baseUri, + absoluteSchemaLocation(document, decodedFragment), + resource->second.policy) + : indexed->second; + return true; + } + + // Forward declaration: the recursive core. + bool validateCtx(const pjson& node, const pjson& schema, const std::string& path, + ErrorSink& errors, ValidationCtx& ctx, const pjson* resourceRoot = nullptr, + const std::string& baseUri = std::string(), + SchemaAnnotations* annotations = nullptr); + + //===------------------------------------------------------------------===// + // Budgeted structural equality (public-API traversal) + //===------------------------------------------------------------------===// + // Returns true and sets equal when the comparison completes within budget; + // returns false only when a resource budget was exhausted. + bool equalWithBudget(const pjson& left, const pjson& right, ValidationCtx& ctx, + ErrorSink& errors, const std::string& path, bool& equal) { + struct Pair { + const pjson* left; + const pjson* right; + }; + std::vector work; + Pair root = {&left, &right}; + work.push_back(root); + equal = false; + + while (!work.empty()) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const Pair current = work.back(); + work.pop_back(); + const pjson& l = *current.left; + const pjson& r = *current.right; + + if (l.isNumber() && r.isNumber()) { + int order = 0; + if (!l.tryCompareNumber(r, order) || order != 0) + return true; // not equal (or NaN-unordered) + continue; + } + if (l.getType() != r.getType()) + return true; + + if (l.isNull() || l.isBool()) { + bool lb = false, rb = false; + if (l.isBool()) { + l.tryGet(lb); + r.tryGet(rb); + if (lb != rb) + return true; + } + } else if (l.isString()) { + std::string ls = strOf(l), rs = strOf(r); + const size_t bytes = std::max(ls.size(), rs.size()); + if (!chargeLoopWork(ctx, errors, path, bytes)) + return false; + if (ls != rs) + return true; + } else if (l.isArray()) { + if (l.size() != r.size()) + return true; + for (size_t i = 0; i < l.size(); ++i) { + const pjson* le = l.findIndex(i); + const pjson* re = r.findIndex(i); + if (le == nullptr || re == nullptr) + return true; + Pair child = {le, re}; + work.push_back(child); + } + } else if (l.isObject()) { + if (l.size() != r.size()) + return true; + const std::vector lk = l.keys(); + for (size_t i = 0; i < lk.size(); ++i) { + if (!chargeLoopWork(ctx, errors, path, lk[i].size() + size_t(1))) + return false; + const pjson* lv = l.find(lk[i]); + const pjson* rv = r.find(lk[i]); + if (lv == nullptr || rv == nullptr) + return true; + Pair child = {lv, rv}; + work.push_back(child); + } + } + } + equal = true; + return true; + } + + //===------------------------------------------------------------------===// + // Recursive validation core (pure public-API traversal) + //===------------------------------------------------------------------===// + bool validateCtx(const pjson& node, const pjson& schema0, const std::string& path, + ErrorSink& errors, ValidationCtx& ctx, const pjson* resourceRoot, + const std::string& baseUri, SchemaAnnotations* annotations) { + SchemaAnnotations localAnnotations; + SchemaAnnotations& evaluated = annotations == nullptr ? localAnnotations : *annotations; + if (ctx.aborted) + return false; + if (!chargeValidationWork(ctx, errors, path)) + return false; + if (ctx.depth >= validationDepthLimit(ctx.options)) { + failValidationBudget(ctx, errors, path, "schema validation depth budget exceeded"); + return false; + } + DepthGuard depthGuard(ctx); + ActiveRefGuard activeRefGuard(ctx.activeRefs); + DynamicScopeGuard dynamicScopeGuard(ctx.dynamicScope); + const pjson* currentSchema = &schema0; + const pjson* currentResourceRoot = resourceRoot == nullptr ? &ctx.rootSchema : resourceRoot; + std::string currentBaseUri = baseUri; + + std::map::const_iterator initialTarget = + ctx.compiled.nodeTargets.find(currentSchema); + if (initialTarget != ctx.compiled.nodeTargets.end()) { + currentResourceRoot = initialTarget->second.resourceRoot; + currentBaseUri = initialTarget->second.baseUri; + } + DialectPolicy currentPolicy = initialTarget == ctx.compiled.nodeTargets.end() + ? subsetPolicy(ctx.options) + : initialTarget->second.policy; + if (initialTarget == ctx.compiled.nodeTargets.end()) { + const std::string resourceUri = stripFragment(currentBaseUri); + std::map::const_iterator resource = + ctx.compiled.resources.find(resourceUri); + if (resource != ctx.compiled.resources.end()) + currentPolicy = resource->second.policy; + } + + // Resolve consecutive static references iteratively (stack-safe). In + // pjson's subset dialect a string $ref ignores siblings, preserving its + // documented draft-07-compatible behavior. + for (;;) { + if (currentSchema->isBool()) { + if (!boolOf(*currentSchema)) { + errors.push_back(validationError(ctx, *currentSchema, SchemaError::FalseSchema, + path, std::string(), + "schema is false; no value is valid here")); + return false; + } + return true; + } + if (!currentSchema->isObject()) + return true; + + const pjson* ref = currentSchema->find("$ref"); + if (ref == nullptr || !ref->isString()) + break; + if (currentPolicy.refSiblings) + break; + + const std::string refText = strOf(*ref); + if (ctx.refResolutions >= validationRefLimit(ctx.options)) { + failValidationBudget(ctx, errors, path, "schema $ref resolution budget exceeded"); + return false; + } + ++ctx.refResolutions; + + SchemaTarget resolved; + if (!resolveSchemaReference(refText, currentResourceRoot, currentBaseUri, ctx, errors, + path, *currentSchema, "$ref", resolved)) + return false; + + const std::pair active(&node, resolved.schema); + if (std::find(ctx.activeRefs.begin(), ctx.activeRefs.end(), active) != + ctx.activeRefs.end()) { + errors.push_back(validationError(ctx, *currentSchema, SchemaError::ReferenceCycle, + path, "$ref", + "schema reference cycle detected: " + refText)); + return false; + } + activeRefGuard.push(&node, resolved.schema); + + if (!chargeValidationWork(ctx, errors, path)) + return false; + if (ctx.depth >= validationDepthLimit(ctx.options)) { + failValidationBudget(ctx, errors, path, "schema validation depth budget exceeded"); + return false; + } + depthGuard.enterResolvedReference(); + currentSchema = resolved.schema; + currentResourceRoot = resolved.resourceRoot; + currentBaseUri = resolved.baseUri; + currentPolicy = resolved.policy; + } + + const pjson& schema = *currentSchema; + std::map::const_iterator resolvedTarget = + ctx.compiled.nodeTargets.find(currentSchema); + if (resolvedTarget != ctx.compiled.nodeTargets.end()) { + currentResourceRoot = resolvedTarget->second.resourceRoot; + currentBaseUri = resolvedTarget->second.baseUri; + } + const size_t before = errors.size(); + if (resolvedTarget != ctx.compiled.nodeTargets.end()) + currentPolicy = resolvedTarget->second.policy; + dynamicScopeGuard.pushResource(SchemaTarget(currentResourceRoot, currentResourceRoot, + currentBaseUri, std::string(), currentPolicy)); + + if (currentPolicy.refSiblings) { + const pjson* ref = schema.find("$ref"); + if (ref != nullptr && ref->isString()) { + if (ctx.refResolutions >= validationRefLimit(ctx.options)) { + failValidationBudget(ctx, errors, path, + "schema $ref resolution budget exceeded"); + return false; + } + ++ctx.refResolutions; + SchemaTarget resolved; + const std::string refText = strOf(*ref); + if (!resolveSchemaReference(refText, currentResourceRoot, currentBaseUri, ctx, + errors, path, schema, "$ref", resolved)) + return false; + const std::pair active(&node, resolved.schema); + if (std::find(ctx.activeRefs.begin(), ctx.activeRefs.end(), active) != + ctx.activeRefs.end()) { + errors.push_back( + validationError(ctx, schema, SchemaError::ReferenceCycle, path, "$ref", + "schema reference cycle detected: " + refText)); + return false; + } + activeRefGuard.push(&node, resolved.schema); + SchemaAnnotations referenced; + const bool referenceValid = + validateCtx(node, *resolved.schema, path, errors, ctx, resolved.resourceRoot, + resolved.baseUri, &referenced); + if (referenceValid) + evaluated.merge(referenced); + if (ctx.aborted) + return false; + } + } + + const auto validationKeyword = [&](const char* name) -> const pjson* { + return hasVocabulary(currentPolicy, VValidation) ? schema.find(name) : nullptr; + }; + const auto applicatorKeyword = [&](const char* name) -> const pjson* { + return hasVocabulary(currentPolicy, VApplicator) ? schema.find(name) : nullptr; + }; + const auto unevaluatedKeyword = [&](const char* name) -> const pjson* { + return hasVocabulary(currentPolicy, VUnevaluated) ? schema.find(name) : nullptr; + }; + + // A dynamic reference first resolves statically. When that target + // declares the same dynamic anchor, the outermost matching resource in + // the current dynamic scope replaces it, as required by Draft 2020-12. + if (const pjson* dynamicRef = schema.find("$dynamicRef")) { + if (dynamicRef->isString()) { + const std::string refText = strOf(*dynamicRef); + if (ctx.refResolutions >= validationRefLimit(ctx.options)) { + failValidationBudget(ctx, errors, path, + "schema $dynamicRef resolution budget exceeded"); + return false; + } + ++ctx.refResolutions; + SchemaTarget resolved; + if (!resolveSchemaReference(refText, currentResourceRoot, currentBaseUri, ctx, + errors, path, schema, "$dynamicRef", resolved)) + return false; + + std::string document; + std::string fragment; + splitReference(resolveUri(currentBaseUri, refText), document, fragment); + if (!fragment.empty() && fragment[0] != '/' && resolved.schema->isObject()) { + const pjson* declaration = resolved.schema->find("$dynamicAnchor"); + if (declaration != nullptr && declaration->isString() && + strOf(*declaration) == fragment) { + for (size_t i = 0; i < ctx.dynamicScope.size(); ++i) { + const std::string key = ctx.dynamicScope[i].baseUri + "#" + fragment; + std::map::const_iterator scoped = + ctx.compiled.dynamicAnchors.find(key); + if (scoped != ctx.compiled.dynamicAnchors.end()) { + resolved = scoped->second; + break; + } + } + } + } + + const std::pair active(&node, resolved.schema); + if (std::find(ctx.activeRefs.begin(), ctx.activeRefs.end(), active) != + ctx.activeRefs.end()) { + errors.push_back(validationError( + ctx, schema, SchemaError::ReferenceCycle, path, "$dynamicRef", + "schema dynamic-reference cycle detected: " + refText)); + return false; + } + activeRefGuard.push(&node, resolved.schema); + SchemaAnnotations referenced; + const bool referenceValid = + validateCtx(node, *resolved.schema, path, errors, ctx, resolved.resourceRoot, + resolved.baseUri, &referenced); + if (referenceValid) + evaluated.merge(referenced); + if (ctx.aborted) + return false; + } + } + + // ---- strict, fail-closed subset check ---- + if (ctx.options.strictSubset) { + const std::vector keys = schema.keys(); + for (size_t i = 0; i < keys.size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + if (!isSupportedSchemaKeyword(keys[i]) && isStandardSchemaKeyword(keys[i])) { + addSchemaError( + ctx, errors, schema, SchemaError::UnsupportedKeyword, path, keys[i], + "strict schema mode: unsupported standard keyword \"" + keys[i] + "\""); + } + } + if (ctx.aborted) + return false; + } + + // ---- type ---- + if (const pjson* t = validationKeyword("type")) { + if (t->isString()) { + if (!typeMatches(node, strOf(*t))) + errors.push_back( + validationError(ctx, schema, SchemaError::TypeMismatch, path, "type", + "expected type " + strOf(*t) + ", got " + typeName(node))); + } else if (t->isArray()) { + bool matched = false; + std::string names; + for (size_t i = 0; i < t->size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* e = t->findIndex(i); + if (e && e->isString()) { + if (!names.empty()) + names += ", "; + names += strOf(*e); + if (typeMatches(node, strOf(*e))) { + matched = true; + break; + } + } + } + if (!matched) + errors.push_back(validationError( + ctx, schema, SchemaError::TypeMismatch, path, "type", + "expected one of type [" + names + "], got " + typeName(node))); + } + } + + // ---- const ---- + if (const pjson* cst = validationKeyword("const")) { + bool equal = false; + if (!equalWithBudget(node, *cst, ctx, errors, path, equal)) + return false; + if (!equal) + errors.push_back(validationError(ctx, schema, SchemaError::ConstMismatch, path, + "const", + "value does not equal the required const")); + } + + // ---- enum ---- + if (const pjson* en = validationKeyword("enum")) { + if (en->isArray()) { + bool found = false; + for (size_t i = 0; i < en->size(); ++i) { + const pjson* opt = en->findIndex(i); + if (opt == nullptr) + continue; + bool equal = false; + if (!equalWithBudget(node, *opt, ctx, errors, path, equal)) + return false; + if (equal) { + found = true; + break; + } + } + if (!found) + errors.push_back(validationError(ctx, schema, SchemaError::EnumMismatch, path, + "enum", "value is not in the allowed enum")); + } + } + + // ---- numeric constraints ---- + if (node.isNumber()) { + int order = 0; + if (const pjson* m = validationKeyword("minimum")) { + if (m->isNumber() && node.tryCompareNumber(*m, order) && order < 0) + addSchemaError( + ctx, errors, schema, SchemaError::NumericConstraint, path, "minimum", + "value " + formatNumber(node) + " is below minimum " + formatNumber(*m)); + } + if (const pjson* m = validationKeyword("maximum")) { + if (m->isNumber() && node.tryCompareNumber(*m, order) && order > 0) + addSchemaError( + ctx, errors, schema, SchemaError::NumericConstraint, path, "maximum", + "value " + formatNumber(node) + " is above maximum " + formatNumber(*m)); + } + if (const pjson* m = validationKeyword("exclusiveMinimum")) { + if (m->isNumber() && node.tryCompareNumber(*m, order) && order <= 0) + addSchemaError(ctx, errors, schema, SchemaError::NumericConstraint, path, + "exclusiveMinimum", + "value " + formatNumber(node) + + " is not greater than exclusiveMinimum " + formatNumber(*m)); + } + if (const pjson* m = validationKeyword("exclusiveMaximum")) { + if (m->isNumber() && node.tryCompareNumber(*m, order) && order >= 0) + addSchemaError(ctx, errors, schema, SchemaError::NumericConstraint, path, + "exclusiveMaximum", + "value " + formatNumber(node) + + " is not less than exclusiveMaximum " + formatNumber(*m)); + } + if (const pjson* m = validationKeyword("multipleOf")) { + if (m->isNumber() && !isExactMultiple(node, *m)) + addSchemaError(ctx, errors, schema, SchemaError::NumericConstraint, path, + "multipleOf", + "value " + formatNumber(node) + " is not a multiple of " + + formatNumber(*m)); + } + } + + // ---- string constraints ---- + if (node.isString()) { + const std::string s = strOf(node); + size_t length = 0; + if (!unicodeLength(s, ctx, errors, path, length)) + return false; + if (const pjson* m = validationKeyword("minLength")) { + size_t bound = 0; + bool aboveRange = false; + if (schemaSize(*m, bound, aboveRange) && (aboveRange || length < bound)) + addSchemaError(ctx, errors, schema, SchemaError::StringConstraint, path, + "minLength", + "string length " + std::to_string(length) + + " is below minLength " + formatNumber(*m)); + } + if (const pjson* m = validationKeyword("maxLength")) { + size_t bound = 0; + bool aboveRange = false; + if (schemaSize(*m, bound, aboveRange) && !aboveRange && length > bound) + addSchemaError(ctx, errors, schema, SchemaError::StringConstraint, path, + "maxLength", + "string length " + std::to_string(length) + + " is above maxLength " + formatNumber(*m)); + } + if (const pjson* p = validationKeyword("pattern")) { + if (p->isString()) { + const std::string pattern = strOf(*p); + bool matches = false; + if (evaluateRegex(s, pattern, path, schema, "pattern", errors, ctx, matches) && + !matches) + errors.push_back(validationError( + ctx, schema, SchemaError::StringConstraint, path, "pattern", + "string does not match pattern /" + pattern + "/")); + } + } + if (currentPolicy.assertFormats) { + if (const pjson* format = schema.find("format")) { + if (format->isString()) { + bool known = false; + if (!knownFormatValid(strOf(*format), s, known) && known) + errors.push_back(validationError( + ctx, schema, SchemaError::FormatMismatch, path, "format", + "string is not a valid " + strOf(*format) + " format")); + } + } + } + } + + // ---- array constraints ---- + if (node.isArray()) { + const size_t arrSize = node.size(); + if (const pjson* m = validationKeyword("minItems")) { + size_t bound = 0; + bool aboveRange = false; + if (schemaSize(*m, bound, aboveRange) && (aboveRange || arrSize < bound)) + addSchemaError(ctx, errors, schema, SchemaError::ArrayConstraint, path, + "minItems", + "array has " + std::to_string(arrSize) + + " items, below minItems " + formatNumber(*m)); + } + if (const pjson* m = validationKeyword("maxItems")) { + size_t bound = 0; + bool aboveRange = false; + if (schemaSize(*m, bound, aboveRange) && !aboveRange && arrSize > bound) + addSchemaError(ctx, errors, schema, SchemaError::ArrayConstraint, path, + "maxItems", + "array has " + std::to_string(arrSize) + + " items, above maxItems " + formatNumber(*m)); + } + if (const pjson* u = validationKeyword("uniqueItems")) { + if (u->isBool() && boolOf(*u)) { + bool dup = false; + for (size_t i = 0; i < arrSize && !dup; ++i) { + for (size_t j = i + 1; j < arrSize; ++j) { + const pjson* a = node.findIndex(i); + const pjson* b = node.findIndex(j); + bool equal = false; + if (a && b && !equalWithBudget(*a, *b, ctx, errors, path, equal)) + return false; + if (equal) { + dup = true; + break; + } + } + } + if (dup) + errors.push_back(validationError(ctx, schema, SchemaError::ArrayConstraint, + path, "uniqueItems", + "array items are not unique")); + } + } + const pjson* items = applicatorKeyword("items"); + const pjson* prefixItems = applicatorKeyword("prefixItems"); + size_t prefixCount = 0; + if (prefixItems && prefixItems->isArray()) { + prefixCount = std::min(arrSize, prefixItems->size()); + for (size_t i = 0; i < prefixCount && !ctx.aborted; ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* elem = node.findIndex(i); + const pjson* sub = prefixItems->findIndex(i); + if (elem && sub) { + evaluated.items.insert(i); + validateCtx(*elem, *sub, pointerAppend(path, std::to_string(i)), errors, + ctx); + } + } + } + if (items != nullptr) { + if (items->isArray() && prefixItems == nullptr) { + // Legacy tuple form of "items". + const size_t count = std::min(arrSize, items->size()); + for (size_t i = 0; i < count && !ctx.aborted; ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* elem = node.findIndex(i); + const pjson* sub = items->findIndex(i); + if (elem && sub) { + evaluated.items.insert(i); + validateCtx(*elem, *sub, pointerAppend(path, std::to_string(i)), errors, + ctx); + } + } + } else { + for (size_t i = prefixCount; i < arrSize && !ctx.aborted; ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* elem = node.findIndex(i); + if (elem) { + evaluated.items.insert(i); + validateCtx(*elem, *items, pointerAppend(path, std::to_string(i)), + errors, ctx); + } + } + } + } + + // ---- contains / minContains / maxContains ---- + if (const pjson* contains = applicatorKeyword("contains")) { + size_t matched = 0; + for (size_t i = 0; i < arrSize && !ctx.aborted; ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* elem = node.findIndex(i); + if (elem == nullptr) + continue; + std::vector scratch; + ErrorSink scratchSink(scratch, ctx, ErrorSink::Discard); + if (validateCtx(*elem, *contains, pointerAppend(path, std::to_string(i)), + scratchSink, ctx)) { + ++matched; + evaluated.items.insert(i); + } + if (ctx.aborted) + return false; + } + size_t minContains = 1; + bool aboveRange = false; + if (const pjson* mc = validationKeyword("minContains")) { + size_t bound = 0; + if (schemaSize(*mc, bound, aboveRange)) + minContains = aboveRange ? std::numeric_limits::max() : bound; + } + if (matched < minContains) + addSchemaError(ctx, errors, schema, SchemaError::ArrayConstraint, path, + "contains", + "array has " + std::to_string(matched) + + " items matching \"contains\", below minContains " + + std::to_string(minContains)); + if (const pjson* xc = validationKeyword("maxContains")) { + size_t bound = 0; + bool xcAbove = false; + if (schemaSize(*xc, bound, xcAbove) && !xcAbove && matched > bound) + addSchemaError(ctx, errors, schema, SchemaError::ArrayConstraint, path, + "contains", + "array has " + std::to_string(matched) + + " items matching \"contains\", above maxContains " + + std::to_string(bound)); + } + } + } + + // ---- object constraints ---- + if (node.isObject()) { + const std::vector memberKeys = node.keys(); + + if (const pjson* req = validationKeyword("required")) { + if (req->isArray()) { + for (size_t i = 0; i < req->size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* k = req->findIndex(i); + if (k && k->isString() && !node.hasKey(strOf(*k))) + errors.push_back(validationError( + ctx, schema, SchemaError::ObjectConstraint, path, "required", + "missing required property \"" + strOf(*k) + "\"")); + } + } + } + if (const pjson* m = validationKeyword("minProperties")) { + size_t bound = 0; + bool aboveRange = false; + if (schemaSize(*m, bound, aboveRange) && (aboveRange || memberKeys.size() < bound)) + addSchemaError(ctx, errors, schema, SchemaError::ObjectConstraint, path, + "minProperties", + "object has " + std::to_string(memberKeys.size()) + + " properties, below minProperties " + formatNumber(*m)); + } + if (const pjson* m = validationKeyword("maxProperties")) { + size_t bound = 0; + bool aboveRange = false; + if (schemaSize(*m, bound, aboveRange) && !aboveRange && memberKeys.size() > bound) + addSchemaError(ctx, errors, schema, SchemaError::ObjectConstraint, path, + "maxProperties", + "object has " + std::to_string(memberKeys.size()) + + " properties, above maxProperties " + formatNumber(*m)); + } + + const pjson* props = applicatorKeyword("properties"); + if (props && props->isObject()) { + const std::vector propKeys = props->keys(); + for (size_t i = 0; i < propKeys.size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* member = node.find(propKeys[i]); + const pjson* sub = props->find(propKeys[i]); + if (member && sub) { + evaluated.properties.insert(propKeys[i]); + validateCtx(*member, *sub, pointerAppend(path, propKeys[i]), errors, ctx); + } + if (ctx.aborted) + return false; + } + } + + const pjson* patternProps = applicatorKeyword("patternProperties"); + std::set patternMatched; + if (patternProps && patternProps->isObject()) { + const std::vector patKeys = patternProps->keys(); + for (size_t p = 0; p < patKeys.size(); ++p) { + const pjson* patSchema = patternProps->find(patKeys[p]); + for (size_t i = 0; i < memberKeys.size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + bool matches = false; + if (evaluateRegex(memberKeys[i], patKeys[p], + pointerAppend(path, memberKeys[i]), schema, + "patternProperties", errors, ctx, matches) && + matches) { + patternMatched.insert(memberKeys[i]); + evaluated.properties.insert(memberKeys[i]); + const pjson* member = node.find(memberKeys[i]); + if (member && patSchema) + validateCtx(*member, *patSchema, pointerAppend(path, memberKeys[i]), + errors, ctx); + } + if (ctx.aborted) + return false; + } + } + } + + if (const pjson* propertyNames = applicatorKeyword("propertyNames")) { + for (size_t i = 0; i < memberKeys.size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + pjson nameValue; + nameValue = memberKeys[i]; + validateCtx(nameValue, *propertyNames, pointerAppend(path, memberKeys[i]), + errors, ctx); + if (ctx.aborted) + return false; + } + } + + const pjson* dependentRequired = validationKeyword("dependentRequired"); + if (dependentRequired && dependentRequired->isObject()) { + const std::vector depKeys = dependentRequired->keys(); + for (size_t d = 0; d < depKeys.size(); ++d) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* list = dependentRequired->find(depKeys[d]); + if (!node.hasKey(depKeys[d]) || list == nullptr || !list->isArray()) + continue; + for (size_t i = 0; i < list->size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* required = list->findIndex(i); + if (required && required->isString() && !node.hasKey(strOf(*required))) + errors.push_back(validationError( + ctx, schema, SchemaError::ObjectConstraint, path, + "dependentRequired", + "property \"" + depKeys[d] + "\" requires property \"" + + strOf(*required) + "\"")); + } + } + } + + const pjson* dependencies = applicatorKeyword("dependencies"); + if (dependencies && dependencies->isObject()) { + const std::vector depKeys = dependencies->keys(); + for (size_t d = 0; d < depKeys.size(); ++d) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + if (!node.hasKey(depKeys[d])) + continue; + const pjson* dep = dependencies->find(depKeys[d]); + if (dep == nullptr) + continue; + if (dep->isArray()) { + for (size_t i = 0; i < dep->size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* required = dep->findIndex(i); + if (required && required->isString() && !node.hasKey(strOf(*required))) + errors.push_back(validationError( + ctx, schema, SchemaError::ObjectConstraint, path, + "dependencies", + "property \"" + depKeys[d] + "\" requires property \"" + + strOf(*required) + "\"")); + } + } else { + SchemaAnnotations dependencyAnnotations; + const bool dependencyValid = + validateCtx(node, *dep, path, errors, ctx, nullptr, std::string(), + &dependencyAnnotations); + if (dependencyValid) + evaluated.merge(dependencyAnnotations); + if (ctx.aborted) + return false; + } + } + } + + if (const pjson* addl = applicatorKeyword("additionalProperties")) { + for (size_t i = 0; i < memberKeys.size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const bool declared = + props && props->isObject() && props->hasKey(memberKeys[i]); + const bool matched = patternMatched.find(memberKeys[i]) != patternMatched.end(); + if (declared || matched) + continue; + evaluated.properties.insert(memberKeys[i]); + if (addl->isBool()) { + if (!boolOf(*addl)) + errors.push_back(validationError( + ctx, schema, SchemaError::ObjectConstraint, + pointerAppend(path, memberKeys[i]), "additionalProperties", + "additional property \"" + memberKeys[i] + "\" is not allowed")); + } else { + const pjson* member = node.find(memberKeys[i]); + if (member) + validateCtx(*member, *addl, pointerAppend(path, memberKeys[i]), errors, + ctx); + } + if (ctx.aborted) + return false; + } + } + + // ---- dependentSchemas ---- + const pjson* dependentSchemas = applicatorKeyword("dependentSchemas"); + if (dependentSchemas && dependentSchemas->isObject()) { + const std::vector depKeys = dependentSchemas->keys(); + for (size_t d = 0; d < depKeys.size(); ++d) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + if (!node.hasKey(depKeys[d])) + continue; + const pjson* dep = dependentSchemas->find(depKeys[d]); + if (dep) { + SchemaAnnotations dependencyAnnotations; + const bool dependencyValid = + validateCtx(node, *dep, path, errors, ctx, nullptr, std::string(), + &dependencyAnnotations); + if (dependencyValid) + evaluated.merge(dependencyAnnotations); + } + if (ctx.aborted) + return false; + } + } + } + + // ---- if / then / else ---- + if (const pjson* ifSchema = applicatorKeyword("if")) { + std::vector scratch; + ErrorSink scratchSink(scratch, ctx, ErrorSink::Discard); + SchemaAnnotations conditionalAnnotations; + const bool matched = validateCtx(node, *ifSchema, path, scratchSink, ctx, nullptr, + std::string(), &conditionalAnnotations); + if (ctx.aborted) + return false; + if (matched) { + evaluated.merge(conditionalAnnotations); + if (const pjson* thenSchema = applicatorKeyword("then")) { + SchemaAnnotations branchAnnotations; + const bool branchValid = + validateCtx(node, *thenSchema, path, errors, ctx, nullptr, std::string(), + &branchAnnotations); + if (branchValid) { + evaluated.merge(branchAnnotations); + } + if (ctx.aborted) + return false; + } + } else { + if (const pjson* elseSchema = applicatorKeyword("else")) { + SchemaAnnotations branchAnnotations; + const bool branchValid = + validateCtx(node, *elseSchema, path, errors, ctx, nullptr, std::string(), + &branchAnnotations); + if (branchValid) + evaluated.merge(branchAnnotations); + if (ctx.aborted) + return false; + } + } + } + + // ---- logical combinators ---- + if (const pjson* allOf = applicatorKeyword("allOf")) { + if (allOf->isArray()) { + for (size_t i = 0; i < allOf->size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* sub = allOf->findIndex(i); + if (sub) { + SchemaAnnotations branchAnnotations; + const bool branchValid = validateCtx(node, *sub, path, errors, ctx, nullptr, + std::string(), &branchAnnotations); + if (branchValid) + evaluated.merge(branchAnnotations); + } + if (ctx.aborted) + return false; + } + } + } + if (const pjson* anyOf = applicatorKeyword("anyOf")) { + if (anyOf->isArray()) { + bool any = false; + std::vector causes; + const size_t causeBudgetStart = ctx.diagnosticsUsed; + for (size_t i = 0; i < anyOf->size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* sub = anyOf->findIndex(i); + if (sub == nullptr) + continue; + std::vector discarded; + std::vector& branchErrors = + ctx.options.collectNestedCauses ? causes : discarded; + ErrorSink scratchSink(branchErrors, ctx, + ctx.options.collectNestedCauses ? ErrorSink::Causes + : ErrorSink::Discard); + SchemaAnnotations branchAnnotations; + if (validateCtx(node, *sub, path, scratchSink, ctx, nullptr, std::string(), + &branchAnnotations)) { + any = true; + evaluated.merge(branchAnnotations); + } + if (ctx.aborted) + return false; + } + if (!any) { + SchemaError error = + validationError(ctx, schema, SchemaError::CombinatorMismatch, path, "anyOf", + "value does not match any schema in anyOf"); + if (ctx.options.collectNestedCauses) + error.causes.swap(causes); + errors.push_back(error); + } else + ctx.diagnosticsUsed = causeBudgetStart; + } + } + if (const pjson* oneOf = applicatorKeyword("oneOf")) { + if (oneOf->isArray()) { + int matches = 0; + SchemaAnnotations matchingAnnotations; + std::vector causes; + const size_t causeBudgetStart = ctx.diagnosticsUsed; + for (size_t i = 0; i < oneOf->size(); ++i) { + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* sub = oneOf->findIndex(i); + if (sub == nullptr) + continue; + std::vector discarded; + std::vector& branchErrors = + ctx.options.collectNestedCauses ? causes : discarded; + ErrorSink scratchSink(branchErrors, ctx, + ctx.options.collectNestedCauses ? ErrorSink::Causes + : ErrorSink::Discard); + SchemaAnnotations branchAnnotations; + if (validateCtx(node, *sub, path, scratchSink, ctx, nullptr, std::string(), + &branchAnnotations)) { + ++matches; + matchingAnnotations = branchAnnotations; + } + if (ctx.aborted) + return false; + } + if (matches != 1) { + SchemaError error = + validationError(ctx, schema, SchemaError::CombinatorMismatch, path, "oneOf", + "value matched " + std::to_string(matches) + + " schemas in oneOf (exactly 1 required)"); + if (ctx.options.collectNestedCauses && matches == 0) + error.causes.swap(causes); + else + ctx.diagnosticsUsed = causeBudgetStart; + errors.push_back(error); + } else { + ctx.diagnosticsUsed = causeBudgetStart; + evaluated.merge(matchingAnnotations); + } + } + } + const pjson* nots = applicatorKeyword("not"); + if (nots != nullptr && (nots->isBool() || nots->isObject())) { + std::vector scratch; + ErrorSink scratchSink(scratch, ctx, ErrorSink::Discard); + if (validateCtx(node, *nots, path, scratchSink, ctx)) + errors.push_back(validationError(ctx, schema, SchemaError::CombinatorMismatch, path, + "not", "value must not match the \"not\" schema")); + if (ctx.aborted) + return false; + } + + if (node.isObject()) { + if (const pjson* unevaluated = unevaluatedKeyword("unevaluatedProperties")) { + const std::vector keys = node.keys(); + for (size_t i = 0; i < keys.size(); ++i) { + if (evaluated.properties.find(keys[i]) != evaluated.properties.end()) + continue; + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* member = node.find(keys[i]); + const std::string memberPath = pointerAppend(path, keys[i]); + if (unevaluated->isBool() && !boolOf(*unevaluated)) { + errors.push_back(validationError(ctx, schema, SchemaError::ObjectConstraint, + memberPath, "unevaluatedProperties", + "unevaluated property is not allowed")); + } else if (member != nullptr) { + validateCtx(*member, *unevaluated, memberPath, errors, ctx); + } + evaluated.properties.insert(keys[i]); + if (ctx.aborted) + return false; + } + } + } + + if (node.isArray()) { + if (const pjson* unevaluated = unevaluatedKeyword("unevaluatedItems")) { + for (size_t i = 0; i < node.size(); ++i) { + if (evaluated.items.find(i) != evaluated.items.end()) + continue; + if (!chargeLoopWork(ctx, errors, path)) + return false; + const pjson* item = node.findIndex(i); + const std::string itemPath = pointerAppend(path, std::to_string(i)); + if (unevaluated->isBool() && !boolOf(*unevaluated)) { + errors.push_back(validationError(ctx, schema, SchemaError::ArrayConstraint, + itemPath, "unevaluatedItems", + "unevaluated item is not allowed")); + } else if (item != nullptr) { + validateCtx(*item, *unevaluated, itemPath, errors, ctx); + } + evaluated.items.insert(i); + if (ctx.aborted) + return false; + } + } + } + + return !ctx.aborted && errors.size() == before; + } + + // Runs one noexcept validation session over a compiled schema. + bool runValidation(const pjson& node, const pjson& schema, std::vector& errors, + const Options& options, const CompiledSchemaIndex& compiled) noexcept { + try { + ValidationCtx ctx(schema, options, compiled, &errors); + ErrorSink sink(errors, ctx); + return validateCtx(node, schema, "", sink, ctx); + } catch (const SchemaBudgetExceeded&) { + return false; + } catch (const std::bad_alloc&) { + bestEffortSchemaError(errors, SchemaError::AllocationFailure, "", + "schema validation ran out of memory"); + } catch (const std::exception&) { + bestEffortSchemaError(errors, SchemaError::InternalError, "", + "schema validation failed with an internal exception"); + } catch (...) { + bestEffortSchemaError(errors, SchemaError::InternalError, "", + "schema validation failed with an unknown exception"); + } + return false; + } + + void validateAgainstMetaSchema(const pjson& schema, const std::string& dialect, + const std::string& retrievalBase, const Options& options, + const CompiledSchemaIndex& compiled, + std::vector& errors) { + if (dialect == documentedSubsetDialect() || !errors.empty()) + return; + SchemaTarget metaSchema; + if (!resolveCompiledTarget(dialect, retrievalBase, compiled, metaSchema)) { + addCompilationError(errors, SchemaError::ReferenceFailure, + pointerAppend(retrievalBase, "$schema"), "$schema", + "compiled meta-schema is unavailable: " + dialect); + return; + } + std::vector validationErrors; + Options metaOptions = options; + // Published/application-selected meta-schemas may contain valid complex + // ECMAScript. SRELL's finite work ceiling remains active even though the + // conservative user-pattern syntax filter is disabled for this phase. + metaOptions.allowUnsafeRegex = true; + if (runValidation(schema, *metaSchema.schema, validationErrors, metaOptions, compiled)) + return; + const size_t limit = diagnosticLimit(options); + for (size_t i = 0; i < validationErrors.size() && errors.size() < limit; ++i) { + SchemaError error = validationErrors[i]; + const std::string schemaPath = error.instanceLocation; + error.category = SchemaError::SchemaCompilation; + error.schemaLocation = absoluteSchemaLocation(retrievalBase, schemaPath); + error.instanceLocation.clear(); + error.message = "schema does not satisfy its meta-schema: " + error.message; + errors.push_back(error); + } + if (validationErrors.empty()) + addCompilationError(errors, SchemaError::InvalidSchema, retrievalBase, "$schema", + "schema does not satisfy its meta-schema"); + } + +} // namespace + +//===----------------------------------------------------------------------===// +// Public pJsonSchemaValidator surface +//===----------------------------------------------------------------------===// +struct pJsonSchemaValidator::Impl { + pjson schema; + Options options; + std::string dialect; + DialectPolicy rootPolicy; + std::vector schemaErrors; + CompiledSchemaIndex compiled; + + Impl(const pjson& aSchema, const Options& aOptions) + : options(aOptions) { + // copyFrom() preserves this default-constructed destination allocator, + // so the validator never borrows the caller's allocator lifetime. + schema.copyFrom(aSchema); + const std::string retrievalBase = stripFragment(options.retrievalUri); + compileDialectContract(schema, options, compiled, dialect, rootPolicy, schemaErrors, + retrievalBase.empty() ? std::string() : retrievalBase + "#"); + if (!schemaErrors.empty()) { + options.resolver = nullptr; + options.resolverContext = nullptr; + return; + } + compiled.resources[retrievalBase] = SchemaResource(&schema, retrievalBase, rootPolicy); + if (dialect != documentedSubsetDialect()) + compiled.pendingDocuments.insert(stripFragment(dialect)); + compileSchemaResource(schema, &schema, retrievalBase, compiled, schemaErrors, options, + rootPolicy, "", 0, retrievalBase); + if (options.stopAfterFirstError && !schemaErrors.empty()) { + schemaErrors.resize(1); + options.resolver = nullptr; + options.resolverContext = nullptr; + return; + } + compileExternalResources(compiled, options, schemaErrors); + if (options.stopAfterFirstError && schemaErrors.size() > size_t(1)) + schemaErrors.resize(1); + if (options.stopAfterFirstError && !schemaErrors.empty()) { + options.resolver = nullptr; + options.resolverContext = nullptr; + return; + } + validateCompiledReferences(compiled, options, schemaErrors); + if (options.stopAfterFirstError && schemaErrors.size() > size_t(1)) + schemaErrors.resize(1); + validateAgainstMetaSchema(schema, dialect, retrievalBase, options, compiled, schemaErrors); + if (options.stopAfterFirstError && schemaErrors.size() > size_t(1)) + schemaErrors.resize(1); + // Resolver state is construction-only. Do not retain an application + // context pointer that may become dangling after compilation finishes. + options.resolver = nullptr; + options.resolverContext = nullptr; + } +}; + +pJsonSchemaValidator::Error::Error() + : code(None) + , category(InstanceValidation) {} +pJsonSchemaValidator::Error::Error(Code aCode, Category aCategory, + const std::string& aInstanceLocation, + const std::string& aSchemaLocation, const std::string& aKeyword, + const std::string& aMessage) + : code(aCode) + , category(aCategory) + , instanceLocation(aInstanceLocation) + , schemaLocation(aSchemaLocation) + , keyword(aKeyword) + , message(aMessage) {} + +pJsonSchemaValidator::Options::Options() + : maxRegexPatternBytes(256) + , maxRegexSubjectBytes(4096) + , allowUnsafeRegex(false) + , maxValidationDepth(kSchemaValidationDepthHardLimit) + , maxRefResolutions(1024) + , maxValidationWork(1000000) + , maxErrors(100) + , stopAfterFirstError(false) + , collectNestedCauses(false) + , validateFormats(true) + , strictSubset(false) + , refSiblings(false) + , resolveCustomDialects(false) + , retrievalUri() + , defaultDialectUri(documentedSubsetDialect()) + , resolver(nullptr) + , resolverContext(nullptr) + , maxResolvedDocuments(32) + , maxResolvedBytes(size_t(16) * 1024 * 1024) {} + +/*static*/ +pJsonSchemaValidator::Options pJsonSchemaValidator::Options::trustedRegex() { + Options o; + o.maxRegexPatternBytes = 0; + o.maxRegexSubjectBytes = 0; + o.allowUnsafeRegex = true; + return o; +} + +/*static*/ +pJsonSchemaValidator::Options pJsonSchemaValidator::Options::strict() { + Options o; + o.strictSubset = true; + return o; +} + +/*static*/ +pJsonSchemaValidator::Options pJsonSchemaValidator::Options::modernSubset() { + Options o; + o.refSiblings = true; + o.validateFormats = false; // Draft 2020-12's default format vocabulary is annotation-only. + return o; +} + +/*static*/ +pJsonSchemaValidator::Options pJsonSchemaValidator::Options::draft2020() { + Options o = modernSubset(); + o.defaultDialectUri = draft2020Dialect(); + o.resolveCustomDialects = true; + return o; +} + +pJsonSchemaValidator::pJsonSchemaValidator(const pjson& aSchema, const Options& aOptions) + : _impl(new Impl(aSchema, aOptions)) {} + +pJsonSchemaValidator::~pJsonSchemaValidator() { + delete _impl; +} + +bool pJsonSchemaValidator::validate(const pjson& aInstance) const noexcept { + if (!isSchemaValid()) + return false; + std::vector errors; + return runValidation(aInstance, _impl->schema, errors, _impl->options, _impl->compiled); +} + +bool pJsonSchemaValidator::validate(const pjson& aInstance, + std::vector& aErrors) const noexcept { + if (!isSchemaValid()) { + try { + aErrors.insert(aErrors.end(), _impl->schemaErrors.begin(), _impl->schemaErrors.end()); + } catch (...) { + // The invalid-schema result remains reliable even when the + // best-effort diagnostic copy cannot allocate. + return false; + } + return false; + } + return runValidation(aInstance, _impl->schema, aErrors, _impl->options, _impl->compiled); +} + +/*static*/ +const char* pJsonSchemaValidator::documentedSubsetDialectUri() noexcept { + return documentedSubsetDialect(); +} + +/*static*/ +const char* pJsonSchemaValidator::documentedSubsetVocabularyUri() noexcept { + return documentedSubsetVocabulary(); +} + +/*static*/ +const char* pJsonSchemaValidator::draft2020DialectUri() noexcept { + return draft2020Dialect(); +} + +bool pJsonSchemaValidator::isSchemaValid() const noexcept { + return _impl->schemaErrors.empty(); +} + +const std::vector& +pJsonSchemaValidator::schemaErrors() const noexcept { + return _impl->schemaErrors; +} + +const std::string& pJsonSchemaValidator::dialect() const noexcept { + return _impl->dialect; +} + +const pjson& pJsonSchemaValidator::schema() const noexcept { + return _impl->schema; +} + +const pJsonSchemaValidator::Options& pJsonSchemaValidator::options() const noexcept { + return _impl->options; +} diff --git a/pjsonlib/src/pjson_schema_builtins.h.in b/pjsonlib/src/pjson_schema_builtins.h.in new file mode 100644 index 0000000..b6fbb2f --- /dev/null +++ b/pjsonlib/src/pjson_schema_builtins.h.in @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +// Generated from the pinned JSON Schema Draft 2020-12 meta-schema files. +#ifndef PRAVEENJSON_SCHEMA_BUILTINS_H +#define PRAVEENJSON_SCHEMA_BUILTINS_H + +#include +#include + +namespace ByteDance { + namespace pjson_schema_detail { + struct BuiltinSchemaText { + const char* uri; + const char* json; + }; + + static const BuiltinSchemaText kBuiltinSchemaTexts[] = { + {"https://json-schema.org/draft/2020-12/schema", R"PJS0(@PJSON_META_SCHEMA@)PJS0"}, + {"https://json-schema.org/draft/2020-12/meta/core", R"PJS1(@PJSON_META_CORE@)PJS1"}, + {"https://json-schema.org/draft/2020-12/meta/applicator", + R"PJS2(@PJSON_META_APPLICATOR@)PJS2"}, + {"https://json-schema.org/draft/2020-12/meta/unevaluated", + R"PJS3(@PJSON_META_UNEVALUATED@)PJS3"}, + {"https://json-schema.org/draft/2020-12/meta/validation", + R"PJS4(@PJSON_META_VALIDATION@)PJS4"}, + {"https://json-schema.org/draft/2020-12/meta/meta-data", + R"PJS5(@PJSON_META_METADATA@)PJS5"}, + {"https://json-schema.org/draft/2020-12/meta/format-annotation", + R"PJS6(@PJSON_META_FORMAT_ANNOTATION@)PJS6"}, + {"https://json-schema.org/draft/2020-12/meta/format-assertion", + R"PJS7(@PJSON_META_FORMAT_ASSERTION@)PJS7"}, + {"https://json-schema.org/draft/2020-12/meta/content", + R"PJS8(@PJSON_META_CONTENT@)PJS8"}, + }; + + inline bool builtinSchemaText(const std::string& uri, std::string& json) { + for (size_t i = 0; i < sizeof(kBuiltinSchemaTexts) / sizeof(kBuiltinSchemaTexts[0]); + ++i) { + if (uri == kBuiltinSchemaTexts[i].uri) { + json = kBuiltinSchemaTexts[i].json; + return true; + } + } + return false; + } + } // namespace pjson_schema_detail +} // namespace ByteDance + +#endif diff --git a/pjsonlib/src/pjson_schema_dialect.cpp b/pjsonlib/src/pjson_schema_dialect.cpp new file mode 100644 index 0000000..174e4df --- /dev/null +++ b/pjsonlib/src/pjson_schema_dialect.cpp @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +#include "pjson_schema_dialect.h" + +namespace ByteDance { + namespace pjson_schema_detail { + namespace { + const char kSubsetDialect[] = "urn:bytedance:pjson:schema:documented-subset:2"; + const char kSubsetVocabulary[] = + "urn:bytedance:pjson:schema:vocabulary:documented-subset:2"; + const char kDraft2020[] = "https://json-schema.org/draft/2020-12/schema"; + } // namespace + + DialectPolicy::DialectPolicy() + : vocabularies(0) + , refSiblings(false) + , assertFormats(false) {} + + const char* documentedSubsetDialect() { + return kSubsetDialect; + } + const char* documentedSubsetVocabulary() { + return kSubsetVocabulary; + } + const char* draft2020Dialect() { + return kDraft2020; + } + + bool hasVocabulary(const DialectPolicy& policy, Vocabulary vocabulary) { + return (policy.vocabularies & static_cast(vocabulary)) != 0U; + } + + bool vocabularyForUri(const std::string& uri, Vocabulary& vocabulary) { + static const char prefix[] = "https://json-schema.org/draft/2020-12/vocab/"; + if (uri.compare(0, sizeof(prefix) - 1, prefix) != 0) + return false; + const std::string name = uri.substr(sizeof(prefix) - 1); + if (name == "core") + vocabulary = VCore; + else if (name == "applicator") + vocabulary = VApplicator; + else if (name == "unevaluated") + vocabulary = VUnevaluated; + else if (name == "validation") + vocabulary = VValidation; + else if (name == "meta-data") + vocabulary = VMetadata; + else if (name == "format-annotation") + vocabulary = VFormatAnnotation; + else if (name == "content") + vocabulary = VContent; + else + return false; + return true; + } + + DialectPolicy subsetPolicy(const pJsonSchemaValidator::Options& options) { + DialectPolicy policy; + policy.vocabularies = VCore | VApplicator | VUnevaluated | VValidation | VMetadata | + VFormatAnnotation | VFormatAssertion | VContent; + policy.refSiblings = options.refSiblings; + policy.assertFormats = options.validateFormats; + return policy; + } + + DialectPolicy draft2020Policy(const pJsonSchemaValidator::Options& options) { + DialectPolicy policy; + policy.vocabularies = VCore | VApplicator | VUnevaluated | VValidation | VMetadata | + VFormatAnnotation | VContent; + policy.refSiblings = true; + policy.assertFormats = options.validateFormats; + return policy; + } + } // namespace pjson_schema_detail +} // namespace ByteDance diff --git a/pjsonlib/src/pjson_schema_dialect.h b/pjsonlib/src/pjson_schema_dialect.h new file mode 100644 index 0000000..174dbba --- /dev/null +++ b/pjsonlib/src/pjson_schema_dialect.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +#ifndef PRAVEENJSON_SCHEMA_DIALECT_H +#define PRAVEENJSON_SCHEMA_DIALECT_H + +#include "pjson_schema.h" + +#include + +namespace ByteDance { + namespace pjson_schema_detail { + enum Vocabulary { + VCore = 1U << 0U, + VApplicator = 1U << 1U, + VUnevaluated = 1U << 2U, + VValidation = 1U << 3U, + VMetadata = 1U << 4U, + VFormatAnnotation = 1U << 5U, + VFormatAssertion = 1U << 6U, + VContent = 1U << 7U + }; + + struct DialectPolicy { + unsigned vocabularies; + bool refSiblings; + bool assertFormats; + DialectPolicy(); + }; + + const char* documentedSubsetDialect(); + const char* documentedSubsetVocabulary(); + const char* draft2020Dialect(); + bool hasVocabulary(const DialectPolicy& aPolicy, Vocabulary aVocabulary); + bool vocabularyForUri(const std::string& aUri, Vocabulary& aVocabulary); + DialectPolicy subsetPolicy(const pJsonSchemaValidator::Options& aOptions); + DialectPolicy draft2020Policy(const pJsonSchemaValidator::Options& aOptions); + } // namespace pjson_schema_detail +} // namespace ByteDance + +#endif diff --git a/pjsonlib/src/pjson_schema_format.cpp b/pjsonlib/src/pjson_schema_format.cpp new file mode 100644 index 0000000..82a2531 --- /dev/null +++ b/pjsonlib/src/pjson_schema_format.cpp @@ -0,0 +1,206 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// +#include "pjson_schema_regex.h" +#include "pjson_schema_util.h" + +#include + +namespace ByteDance { + namespace pjson_schema_detail { + namespace { + bool isAsciiDigit(char ch) { + return ch >= '0' && ch <= '9'; + } + + bool isAsciiHex(char ch) { + return isAsciiDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + } + + bool parseFixedDigits(const std::string& value, size_t offset, size_t count, + int& result) { + if (offset > value.size() || count > value.size() - offset) + return false; + result = 0; + for (size_t i = 0; i < count; ++i) { + if (!isAsciiDigit(value[offset + i])) + return false; + result = result * 10 + (value[offset + i] - '0'); + } + return true; + } + + bool isLeapYear(int year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + } + + bool validDate(const std::string& value) { + if (value.size() != 10 || value[4] != '-' || value[7] != '-') + return false; + int year = 0, month = 0, day = 0; + if (!parseFixedDigits(value, 0, 4, year) || !parseFixedDigits(value, 5, 2, month) || + !parseFixedDigits(value, 8, 2, day) || month < 1 || month > 12 || day < 1) + return false; + static const int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + int maxDay = days[month - 1]; + if (month == 2 && isLeapYear(year)) + maxDay = 29; + return day <= maxDay; + } + + bool validTime(const std::string& value) { + if (value.size() < 9 || value[2] != ':' || value[5] != ':') + return false; + int hour = 0, minute = 0, second = 0; + if (!parseFixedDigits(value, 0, 2, hour) || + !parseFixedDigits(value, 3, 2, minute) || + !parseFixedDigits(value, 6, 2, second) || hour > 23 || minute > 59 || + second > 60) + return false; + size_t pos = 8; + if (pos < value.size() && value[pos] == '.') { + ++pos; + const size_t fractionStart = pos; + while (pos < value.size() && isAsciiDigit(value[pos])) + ++pos; + if (pos == fractionStart) + return false; + } + int offsetMinutes = 0; + if (pos < value.size() && (value[pos] == 'Z' || value[pos] == 'z')) { + ++pos; + } else { + if (pos + 6 != value.size() || (value[pos] != '+' && value[pos] != '-') || + value[pos + 3] != ':') + return false; + int offsetHour = 0, offsetMinute = 0; + if (!parseFixedDigits(value, pos + 1, 2, offsetHour) || + !parseFixedDigits(value, pos + 4, 2, offsetMinute) || offsetHour > 23 || + offsetMinute > 59) + return false; + offsetMinutes = offsetHour * 60 + offsetMinute; + if (value[pos] == '-') + offsetMinutes = -offsetMinutes; + pos += 6; + } + if (pos != value.size()) + return false; + if (second == 60) { + int utcMinute = (hour * 60 + minute - offsetMinutes) % (24 * 60); + if (utcMinute < 0) + utcMinute += 24 * 60; + if (utcMinute != 23 * 60 + 59) + return false; + } + return true; + } + + bool validIPv4(const std::string& value) { + size_t pos = 0; + for (int part = 0; part < 4; ++part) { + const size_t begin = pos; + int octet = 0; + while (pos < value.size() && isAsciiDigit(value[pos])) { + octet = octet * 10 + (value[pos] - '0'); + if (octet > 255) + return false; + ++pos; + } + const size_t digits = pos - begin; + if (digits == 0 || digits > 3 || (digits > 1 && value[begin] == '0')) + return false; + if (part != 3) { + if (pos >= value.size() || value[pos] != '.') + return false; + ++pos; + } + } + return pos == value.size(); + } + + bool parseIPv6Side(const std::string& side, bool mayContainIPv4, int& units) { + if (side.empty()) + return true; + size_t start = 0; + while (start <= side.size()) { + const size_t colon = side.find(':', start); + const size_t end = colon == std::string::npos ? side.size() : colon; + if (end == start) + return false; + const std::string token = side.substr(start, end - start); + if (token.find('.') != std::string::npos) { + if (!mayContainIPv4 || end != side.size() || !validIPv4(token)) + return false; + units += 2; + } else { + if (token.size() > 4) + return false; + for (size_t i = 0; i < token.size(); ++i) { + if (!isAsciiHex(token[i])) + return false; + } + ++units; + } + if (colon == std::string::npos) + break; + start = colon + 1; + if (start == side.size()) + return false; + } + return true; + } + + bool validIPv6(const std::string& value) { + if (value.empty()) + return false; + const size_t compression = value.find("::"); + if (compression != std::string::npos && + value.find("::", compression + 2) != std::string::npos) + return false; + int units = 0; + if (compression == std::string::npos) + return parseIPv6Side(value, true, units) && units == 8; + const std::string left = value.substr(0, compression); + const std::string right = value.substr(compression + 2); + if (!parseIPv6Side(left, false, units) || !parseIPv6Side(right, true, units)) + return false; + return units < 8; + } + + bool validUuid(const std::string& value) { + if (value.size() != 36 || value[8] != '-' || value[13] != '-' || value[18] != '-' || + value[23] != '-') + return false; + for (size_t i = 0; i < value.size(); ++i) { + if (i == 8 || i == 13 || i == 18 || i == 23) + continue; + if (!isAsciiHex(value[i])) + return false; + } + return true; + } + } // namespace + + bool knownFormatValid(const std::string& format, const std::string& value, bool& known) { + known = true; + if (format == "date") + return validDate(value); + if (format == "time") + return validTime(value); + if (format == "date-time") + return value.size() > 11 && (value[10] == 'T' || value[10] == 't') && + validDate(value.substr(0, 10)) && validTime(value.substr(11)); + if (format == "ipv4") + return validIPv4(value); + if (format == "ipv6") + return validIPv6(value); + if (format == "uuid") + return validUuid(value); + if (format == "regex") + return validEcmaRegex(value); + known = false; + return true; + } + } // namespace pjson_schema_detail +} // namespace ByteDance diff --git a/pjsonlib/src/pjson_schema_regex.cpp b/pjsonlib/src/pjson_schema_regex.cpp new file mode 100644 index 0000000..bf4a0a9 --- /dev/null +++ b/pjsonlib/src/pjson_schema_regex.cpp @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +#include "pjson_schema_regex.h" + +#include + +#include + +namespace ByteDance { + namespace pjson_schema_detail { + namespace { + bool isAsciiLetter(char value) { + return (value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z'); + } + + bool validEcmaSyntaxSubset(const std::string& pattern) { + bool inClass = false; + size_t groups = 0; + for (size_t i = 0; i < pattern.size(); ++i) { + const char value = pattern[i]; + if (value == '\\') { + ++i; + if (i >= pattern.size()) + return false; + const char escaped = pattern[i]; + if (escaped == 'c') { + ++i; + if (i >= pattern.size() || !isAsciiLetter(pattern[i])) + return false; + } else if (escaped == 'p' || escaped == 'P') { + ++i; + if (i >= pattern.size() || pattern[i] != '{') + return false; + const size_t close = pattern.find('}', i + 1); + if (close == std::string::npos || close == i + 1) + return false; + i = close; + } else if (isAsciiLetter(escaped) && + std::string("bBfnrtvxdDsSwWuk").find(escaped) == + std::string::npos) { + return false; + } + continue; + } + if (value == '[' && !inClass) { + inClass = true; + continue; + } + if (value == ']' && inClass) { + inClass = false; + continue; + } + if (inClass) + continue; + if (value == '(') { + ++groups; + if (i + 2 < pattern.size() && pattern[i + 1] == '?' && + (pattern[i + 2] == 'P' || pattern[i + 2] == '#' || + pattern[i + 2] == 'i' || pattern[i + 2] == 'm' || + pattern[i + 2] == 's' || pattern[i + 2] == '-')) + return false; + } else if (value == ')') { + if (groups == 0) + return false; + --groups; + } + } + if (inClass || groups != 0) + return false; + return true; + } + + bool hasUnsupportedGlobalModifiers(const std::string& pattern) { + for (size_t i = 0; i + 3 < pattern.size(); ++i) { + if (pattern[i] == '(' && pattern[i + 1] == '?' && + (pattern[i + 2] == 'i' || pattern[i + 2] == 'm' || pattern[i + 2] == 's' || + pattern[i + 2] == '-')) + return true; + } + return false; + } + } // namespace + + struct EcmaRegex::Impl { + srell::u8regex expression; + }; + + EcmaRegex::EcmaRegex() + : _impl(new Impl()) {} + + EcmaRegex::~EcmaRegex() { + delete _impl; + } + + bool EcmaRegex::compile(const std::string& pattern) { + if (!validEcmaSyntaxSubset(pattern) || hasUnsupportedGlobalModifiers(pattern)) + return false; + try { + _impl->expression.assign(pattern); + return true; + } catch (const srell::regex_error&) { + return false; + } + } + + EcmaRegex::Result EcmaRegex::search(const std::string& subject) const { + try { + return srell::regex_search(subject, _impl->expression) ? Match : NoMatch; + } catch (const srell::regex_error& error) { + return error.code() == srell::regex_constants::error_complexity || + error.code() == srell::regex_constants::error_stack + ? WorkLimit + : Invalid; + } + } + + bool validEcmaRegex(const std::string& pattern) { + EcmaRegex expression; + return expression.compile(pattern); + } + } // namespace pjson_schema_detail +} // namespace ByteDance diff --git a/pjsonlib/src/pjson_schema_regex.h b/pjsonlib/src/pjson_schema_regex.h new file mode 100644 index 0000000..6ae64f9 --- /dev/null +++ b/pjsonlib/src/pjson_schema_regex.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +#ifndef PRAVEENJSON_SCHEMA_REGEX_H +#define PRAVEENJSON_SCHEMA_REGEX_H + +#include + +namespace ByteDance { + namespace pjson_schema_detail { + class EcmaRegex { + public: + enum Result { Match, NoMatch, Invalid, WorkLimit }; + + EcmaRegex(); + ~EcmaRegex(); + bool compile(const std::string& aPattern); + Result search(const std::string& aSubject) const; + + private: + EcmaRegex(const EcmaRegex&); + EcmaRegex& operator=(const EcmaRegex&); + struct Impl; + Impl* _impl; + }; + + bool validEcmaRegex(const std::string& aPattern); + } // namespace pjson_schema_detail +} // namespace ByteDance + +#endif diff --git a/pjsonlib/src/pjson_schema_uri.cpp b/pjsonlib/src/pjson_schema_uri.cpp new file mode 100644 index 0000000..b19be05 --- /dev/null +++ b/pjsonlib/src/pjson_schema_uri.cpp @@ -0,0 +1,183 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// +#include "pjson_schema_util.h" + +#include +#include + +namespace ByteDance { + namespace pjson_schema_detail { + namespace { + bool isAsciiDigit(char ch) { + return ch >= '0' && ch <= '9'; + } + bool isAsciiHex(char ch) { + return isAsciiDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + } + + std::string normalizePath(const std::string& path) { + const bool absolute = !path.empty() && path[0] == '/'; + std::vector segments; + size_t begin = 0; + while (begin <= path.size()) { + const size_t slash = path.find('/', begin); + const std::string segment = path.substr( + begin, slash == std::string::npos ? std::string::npos : slash - begin); + if (segment.empty() || segment == ".") { + } else if (segment == "..") { + if (!segments.empty()) + segments.pop_back(); + } else { + segments.push_back(segment); + } + if (slash == std::string::npos) + break; + begin = slash + 1; + } + std::string result = absolute ? "/" : std::string(); + for (size_t i = 0; i < segments.size(); ++i) { + if (!result.empty() && result[result.size() - 1] != '/') + result += '/'; + result += segments[i]; + } + if (!path.empty() && path[path.size() - 1] == '/' && + (result.empty() || result[result.size() - 1] != '/')) + result += '/'; + return result; + } + + void splitPathSuffix(const std::string& value, std::string& path, std::string& suffix) { + const size_t marker = value.find_first_of("?#"); + path = marker == std::string::npos ? value : value.substr(0, marker); + suffix = marker == std::string::npos ? std::string() : value.substr(marker); + } + } // namespace + + bool percentDecodeFragment(const std::string& fragment, std::string& decoded) { + decoded.clear(); + for (size_t i = 0; i < fragment.size(); ++i) { + if (fragment[i] != '%') { + decoded += fragment[i]; + continue; + } + if (i + 2 >= fragment.size() || !isAsciiHex(fragment[i + 1]) || + !isAsciiHex(fragment[i + 2])) + return false; + const char hi = fragment[i + 1]; + const char lo = fragment[i + 2]; + const int high = + isAsciiDigit(hi) ? hi - '0' : (hi >= 'a' ? hi - 'a' + 10 : hi - 'A' + 10); + const int low = + isAsciiDigit(lo) ? lo - '0' : (lo >= 'a' ? lo - 'a' + 10 : lo - 'A' + 10); + decoded += static_cast((high << 4) | low); + i += 2; + } + return true; + } + + bool uriHasScheme(const std::string& uri) { + if (uri.empty() || + !((uri[0] >= 'A' && uri[0] <= 'Z') || (uri[0] >= 'a' && uri[0] <= 'z'))) + return false; + for (size_t i = 1; i < uri.size(); ++i) { + const char c = uri[i]; + if (c == ':') + return true; + if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + c == '+' || c == '-' || c == '.')) + return false; + } + return false; + } + + bool validAnchorName(const std::string& name) { + if (name.empty() || !((name[0] >= 'A' && name[0] <= 'Z') || + (name[0] >= 'a' && name[0] <= 'z') || name[0] == '_')) + return false; + for (size_t i = 1; i < name.size(); ++i) { + const char c = name[i]; + if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + c == '_' || c == '-' || c == '.' || c == ':')) + return false; + } + return true; + } + + std::string stripFragment(const std::string& uri) { + const size_t hash = uri.find('#'); + return hash == std::string::npos ? uri : uri.substr(0, hash); + } + + void splitReference(const std::string& uri, std::string& document, std::string& fragment) { + const size_t hash = uri.find('#'); + document = hash == std::string::npos ? uri : uri.substr(0, hash); + fragment = hash == std::string::npos ? std::string() : uri.substr(hash + 1); + } + + std::string resolveUri(const std::string& baseWithFragment, const std::string& reference) { + std::string base = stripFragment(baseWithFragment); + if (reference.empty()) + return base; + if (uriHasScheme(reference)) + return reference; + if (reference[0] == '#') + return base + reference; + + std::string referencePath; + std::string referenceSuffix; + splitPathSuffix(reference, referencePath, referenceSuffix); + + const size_t colon = base.find(':'); + if (colon == std::string::npos) { + std::string basePath; + std::string ignoredSuffix; + splitPathSuffix(base, basePath, ignoredSuffix); + if (reference[0] == '?') + return basePath + reference; + if (!referencePath.empty() && referencePath[0] == '/') + return normalizePath(referencePath) + referenceSuffix; + const size_t slash = basePath.rfind('/'); + const std::string directory = + slash == std::string::npos ? std::string() : basePath.substr(0, slash + 1); + return normalizePath(directory + referencePath) + referenceSuffix; + } + const std::string scheme = base.substr(0, colon + 1); + const std::string remainder = base.substr(colon + 1); + if (remainder.compare(0, 2, "//") != 0) { + std::string basePath; + std::string ignoredSuffix; + splitPathSuffix(remainder, basePath, ignoredSuffix); + if (reference[0] == '?') + return scheme + basePath + reference; + if (!referencePath.empty() && referencePath[0] == '/') + return scheme + normalizePath(referencePath) + referenceSuffix; + const size_t slash = basePath.rfind('/'); + const std::string directory = + slash == std::string::npos ? std::string() : basePath.substr(0, slash + 1); + return scheme + normalizePath(directory + referencePath) + referenceSuffix; + } + if (reference.compare(0, 2, "//") == 0) + return scheme + reference; + + const size_t authorityEnd = remainder.find('/', 2); + const std::string authority = + authorityEnd == std::string::npos ? remainder : remainder.substr(0, authorityEnd); + const std::string basePath = authorityEnd == std::string::npos + ? std::string("/") + : remainder.substr(authorityEnd); + std::string cleanBasePath; + std::string ignoredSuffix; + splitPathSuffix(basePath, cleanBasePath, ignoredSuffix); + if (reference[0] == '?') + return scheme + authority + cleanBasePath + reference; + if (!referencePath.empty() && referencePath[0] == '/') + return scheme + authority + normalizePath(referencePath) + referenceSuffix; + const size_t slash = cleanBasePath.rfind('/'); + const std::string directory = + slash == std::string::npos ? std::string() : cleanBasePath.substr(0, slash + 1); + return scheme + authority + normalizePath(directory + referencePath) + referenceSuffix; + } + } // namespace pjson_schema_detail +} // namespace ByteDance diff --git a/pjsonlib/src/pjson_schema_util.h b/pjsonlib/src/pjson_schema_util.h new file mode 100644 index 0000000..e38d41f --- /dev/null +++ b/pjsonlib/src/pjson_schema_util.h @@ -0,0 +1,37 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// +#ifndef PRAVEENJSON_SCHEMA_UTIL_H +#define PRAVEENJSON_SCHEMA_UTIL_H + +#include "pjson.h" + +#include +#include + +namespace ByteDance { + namespace pjson_schema_detail { + std::string strOf(const pjson& aValue); + bool boolOf(const pjson& aValue); + std::string numberText(const pjson& aValue); + double numberAsDouble(const pjson& aValue); + int utf8Len(const char* aSource, size_t aPosition, size_t aEnd); + std::string typeName(const pjson& aNode); + bool typeMatches(const pjson& aNode, const std::string& aType); + bool isSafeRegex(const std::string& aPattern); + std::string formatNumber(const pjson& aValue); + bool schemaSize(const pjson& aValue, size_t& aResult, bool& aAboveRange); + bool isExactMultiple(const pjson& aValue, const pjson& aDivisor); + bool knownFormatValid(const std::string& aFormat, const std::string& aValue, bool& aKnown); + bool percentDecodeFragment(const std::string& aFragment, std::string& aDecoded); + bool uriHasScheme(const std::string& aUri); + bool validAnchorName(const std::string& aName); + std::string stripFragment(const std::string& aUri); + void splitReference(const std::string& aUri, std::string& aDocument, + std::string& aFragment); + std::string resolveUri(const std::string& aBaseWithFragment, const std::string& aReference); + } // namespace pjson_schema_detail +} // namespace ByteDance + +#endif // PRAVEENJSON_SCHEMA_UTIL_H diff --git a/pjsonlib/src/pjson_schema_value.cpp b/pjsonlib/src/pjson_schema_value.cpp new file mode 100644 index 0000000..288eb53 --- /dev/null +++ b/pjsonlib/src/pjson_schema_value.cpp @@ -0,0 +1,389 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// +#include "pjson_schema_util.h" + +#include +#include +#include +#include +#include + +namespace ByteDance { + namespace pjson_schema_detail { + std::string strOf(const pjson& value) { + std::string result; + value.tryGet(result); + return result; + } + + bool boolOf(const pjson& value) { + bool result = false; + value.tryGet(result); + return result; + } + + std::string numberText(const pjson& value) { + int64_t i = 0; + if (value.isInt() && value.tryGet(i)) + return std::to_string(i); + uint64_t u = 0; + if (value.isUInt() && value.tryGet(u)) + return std::to_string(u); + pjson::SerializeOptions options; + options.nonFinite = pjson::SerializeOptions::NonFiniteToString; + return value.toString(options); + } + + double numberAsDouble(const pjson& value) { + double result = 0.0; + value.tryGet(result); + return result; + } + + int utf8Len(const char* source, size_t position, size_t end) { + const unsigned char c0 = static_cast(source[position]); + int count; + uint32_t codePoint; + uint32_t minimum; + if (c0 < 0x80) + return 1; + if ((c0 & 0xE0) == 0xC0) { + count = 2; + codePoint = c0 & 0x1F; + minimum = 0x80; + } else if ((c0 & 0xF0) == 0xE0) { + count = 3; + codePoint = c0 & 0x0F; + minimum = 0x800; + } else if ((c0 & 0xF8) == 0xF0) { + count = 4; + codePoint = c0 & 0x07; + minimum = 0x10000; + } else { + return 0; + } + if (position + static_cast(count) > end) + return 0; + for (int i = 1; i < count; ++i) { + const unsigned char continuation = + static_cast(source[position + static_cast(i)]); + if ((continuation & 0xC0) != 0x80) + return 0; + codePoint = (codePoint << 6) | (continuation & 0x3F); + } + if (codePoint < minimum || codePoint > 0x10FFFF || + (codePoint >= 0xD800 && codePoint <= 0xDFFF)) + return 0; + return count; + } + + std::string typeName(const pjson& node) { + if (node.isNull()) + return "null"; + if (node.isString()) + return "string"; + if (node.isInteger()) + return "integer"; + if (node.isDouble()) + return "number"; + if (node.isBool()) + return "boolean"; + if (node.isArray()) + return "array"; + if (node.isObject()) + return "object"; + return "unknown"; + } + + bool typeMatches(const pjson& node, const std::string& type) { + if (type == "null") + return node.isNull(); + if (type == "string") + return node.isString(); + if (type == "boolean") + return node.isBool(); + if (type == "array") + return node.isArray(); + if (type == "object") + return node.isObject(); + if (type == "number") + return node.isNumber(); + if (type == "integer") { + if (node.isInteger()) + return true; + double number = 0.0; + return node.isDouble() && node.tryGet(number) && std::isfinite(number) && + std::floor(number) == number; + } + return false; + } + + bool isSafeRegex(const std::string& pattern) { + bool escaped = false; + bool inClass = false; + int groups = 0; + int quantifiers = 0; + struct Group { + bool hasQuantifier; + bool hasAlternation; + }; + std::vector stack; + for (size_t i = 0; i < pattern.size(); ++i) { + const char c = pattern[i]; + if (escaped) { + if (c >= '1' && c <= '9') + return false; + if ((c == 'p' || c == 'P') && i + 1 < pattern.size() && pattern[i + 1] == '{') { + const size_t close = pattern.find('}', i + 2); + if (close == std::string::npos || close == i + 2) + return false; + i = close; + } + escaped = false; + continue; + } + if (c == '\\') { + escaped = true; + continue; + } + if (c == '[') { + inClass = true; + continue; + } + if (c == ']' && inClass) { + inClass = false; + continue; + } + if (inClass) + continue; + if (c == '(') { + if (++groups > 16) + return false; + stack.push_back(Group{false, false}); + } else if (c == '|') { + return false; + } else if (c == '*' || c == '+' || c == '?' || c == '{') { + if (++quantifiers > 1) + return false; + if (c == '{') { + size_t j = i + 1; + size_t first = 0; + size_t second = 0; + bool haveFirst = false; + bool haveSecond = false; + while (j < pattern.size() && pattern[j] >= '0' && pattern[j] <= '9') { + haveFirst = true; + if (first > 1000) + return false; + first = first * 10 + static_cast(pattern[j] - '0'); + ++j; + } + if (j < pattern.size() && pattern[j] == ',') { + ++j; + while (j < pattern.size() && pattern[j] >= '0' && pattern[j] <= '9') { + haveSecond = true; + if (second > 1000) + return false; + second = second * 10 + static_cast(pattern[j] - '0'); + ++j; + } + } + if ((haveFirst && first > 1000) || (haveSecond && second > 1000)) + return false; + } + if (!stack.empty()) + stack.back().hasQuantifier = true; + } else if (c == ')' && !stack.empty()) { + const Group closed = stack.back(); + stack.pop_back(); + const size_t next = i + 1; + const bool quantified = + next < pattern.size() && (pattern[next] == '*' || pattern[next] == '+' || + pattern[next] == '?' || pattern[next] == '{'); + if (quantified && (closed.hasQuantifier || closed.hasAlternation)) + return false; + if (!stack.empty()) + stack.back().hasQuantifier = + stack.back().hasQuantifier || quantified || closed.hasQuantifier; + } + } + return true; + } + + namespace { + struct ExactDecimal { + uint64_t coefficient; + int exponent10; + }; + + uint64_t magnitudeOf(int64_t value) { + return value < 0 ? uint64_t(-(value + 1)) + uint64_t(1) : uint64_t(value); + } + + bool decimalFromText(const std::string& text, ExactDecimal& result) { + size_t position = 0; + if (position < text.size() && (text[position] == '+' || text[position] == '-')) + ++position; + uint64_t coefficient = 0; + int fractionDigits = 0; + bool seenDigit = false; + bool afterPoint = false; + while (position < text.size() && text[position] != 'e' && text[position] != 'E') { + const char ch = text[position++]; + if (ch == '.' && !afterPoint) { + afterPoint = true; + continue; + } + if (ch < '0' || ch > '9') + return false; + const uint64_t digit = static_cast(ch - '0'); + if (coefficient > (std::numeric_limits::max() - digit) / uint64_t(10)) + return false; + coefficient = coefficient * uint64_t(10) + digit; + if (afterPoint) + ++fractionDigits; + seenDigit = true; + } + int explicitExponent = 0; + if (position < text.size()) { + ++position; + bool negative = false; + if (position < text.size() && + (text[position] == '+' || text[position] == '-')) { + negative = text[position] == '-'; + ++position; + } + if (position == text.size()) + return false; + while (position < text.size()) { + const char ch = text[position++]; + if (ch < '0' || ch > '9' || explicitExponent > 10000) + return false; + explicitExponent = explicitExponent * 10 + (ch - '0'); + } + if (negative) + explicitExponent = -explicitExponent; + } + if (!seenDigit) + return false; + if (coefficient == 0) { + result = ExactDecimal{0, 0}; + return true; + } + int exponent = explicitExponent - fractionDigits; + while (coefficient % uint64_t(10) == 0) { + coefficient /= uint64_t(10); + ++exponent; + } + result = ExactDecimal{coefficient, exponent}; + return true; + } + + bool decimalFromNumber(const pjson& value, ExactDecimal& result) { + if (value.isInteger()) { + uint64_t unsignedValue = 0; + int64_t signedValue = 0; + result.coefficient = + value.isUInt() ? (value.tryGet(unsignedValue), unsignedValue) + : (value.tryGet(signedValue), magnitudeOf(signedValue)); + result.exponent10 = 0; + while (result.coefficient != 0 && result.coefficient % uint64_t(10) == 0) { + result.coefficient /= uint64_t(10); + ++result.exponent10; + } + return true; + } + double number = 0.0; + return value.isDouble() && value.tryGet(number) && std::isfinite(number) && + decimalFromText(numberText(value), result); + } + } // namespace + + std::string formatNumber(const pjson& value) { + return numberText(value); + } + + bool schemaSize(const pjson& value, size_t& result, bool& aboveRange) { + aboveRange = false; + if (value.isUInt()) { + uint64_t magnitude = 0; + value.tryGet(magnitude); + if (magnitude > static_cast(std::numeric_limits::max())) { + aboveRange = true; + return true; + } + result = static_cast(magnitude); + return true; + } + if (value.isInt()) { + int64_t integer = 0; + value.tryGet(integer); + if (integer < 0) + return false; + const uint64_t magnitude = static_cast(integer); + if (magnitude > static_cast(std::numeric_limits::max())) { + aboveRange = true; + return true; + } + result = static_cast(magnitude); + return true; + } + double number = 0.0; + if (!value.isDouble() || !value.tryGet(number) || !std::isfinite(number) || + number < 0.0 || std::floor(number) != number) + return false; + const double exclusiveUpper = std::ldexp(1.0, std::numeric_limits::digits); + if (number >= exclusiveUpper) { + aboveRange = true; + return true; + } + result = static_cast(number); + return true; + } + + bool isExactMultiple(const pjson& value, const pjson& divisor) { + if (numberAsDouble(divisor) <= 0.0) + return true; + if (value.isInt() && divisor.isInt()) { + int64_t valueInteger = 0, divisorInteger = 0; + value.tryGet(valueInteger); + divisor.tryGet(divisorInteger); + return magnitudeOf(valueInteger) % magnitudeOf(divisorInteger) == 0; + } + ExactDecimal valueDecimal = {0, 0}; + ExactDecimal divisorDecimal = {0, 0}; + if (!decimalFromNumber(divisor, divisorDecimal) || divisorDecimal.coefficient == 0) + return true; + if (!decimalFromNumber(value, valueDecimal)) + return false; + if (valueDecimal.coefficient == 0) + return true; + const int shift = valueDecimal.exponent10 - divisorDecimal.exponent10; + if (shift >= 0) { + uint64_t reduced = divisorDecimal.coefficient; + int remainingTwos = shift; + int remainingFives = shift; + while (remainingTwos > 0 && reduced % uint64_t(2) == 0) { + reduced /= uint64_t(2); + --remainingTwos; + } + while (remainingFives > 0 && reduced % uint64_t(5) == 0) { + reduced /= uint64_t(5); + --remainingFives; + } + return valueDecimal.coefficient % reduced == 0; + } + if (valueDecimal.coefficient % divisorDecimal.coefficient != 0) + return false; + uint64_t quotient = valueDecimal.coefficient / divisorDecimal.coefficient; + int decimalPlaces = -shift; + while (decimalPlaces > 0 && quotient % uint64_t(10) == 0) { + quotient /= uint64_t(10); + --decimalPlaces; + } + return decimalPlaces == 0; + } + } // namespace pjson_schema_detail +} // namespace ByteDance diff --git a/pjsonlib/src/pjson_serialize.cpp b/pjsonlib/src/pjson_serialize.cpp new file mode 100644 index 0000000..1f89f01 --- /dev/null +++ b/pjsonlib/src/pjson_serialize.cpp @@ -0,0 +1,704 @@ +// SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +// SPDX-License-Identifier: Apache-2.0 +// +// JSON serialization, output validation, and floating-point formatting. + +#include "pjson_internal.h" +#include "ryu/ryu.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ByteDance; + +namespace { + // Publishes a structured serialization failure without weakening the + // noexcept contract if storing the optional diagnostic text allocates. + void setSerializeError(pjson::SerializeError& aError, pjson::SerializeError::Code aCode, + const char* aMessage) noexcept { + aError.code = aCode; + try { + aError.message = aMessage; + } catch (...) { + aError.message.clear(); + } + } +} // namespace + +pjson::SerializeOptions::SerializeOptions() + : pretty(false) + , indentWidth(2) + , indentCharacter(' ') + , escapeNonAscii(false) + , nonFinite(RejectNonFinite) + , maxOutputBytes(size_t(64) * 1024U * 1024U) {} + +pjson::SerializeOptions pjson::SerializeOptions::prettyPrinted() { + SerializeOptions options; + options.pretty = true; + return options; +} + +pjson::SerializeError::SerializeError() + : code(None) {} + +void pjson::SerializeError::reset() noexcept { + code = None; + message.clear(); +} + +// Formats a finite double with Ryu's proven shortest-round-trip conversion. +// A '.0' suffix is appended when the result would otherwise look like an +// integer, so the value re-parses into the double representation (type-stable). +/*static*/ +std::string pjsonImpl::_formatDouble(double aValue) { + if (!std::isfinite(aValue)) { + // JSON has no representation for NaN/Infinity. + return "null"; + } + char buffer[32]; + const int length = d2s_buffered_n(aValue, buffer); + std::string result(buffer, static_cast(length)); + for (size_t i = 0; i < result.size(); ++i) { + if (result[i] == 'E') + result[i] = 'e'; + } + const size_t exponent = result.find('e'); + if (exponent != std::string::npos) { + size_t cursor = exponent + 1; + bool negativeExponent = false; + if (cursor < result.size() && (result[cursor] == '+' || result[cursor] == '-')) { + negativeExponent = result[cursor] == '-'; + ++cursor; + } + int exponentValue = 0; + while (cursor < result.size()) { + exponentValue = exponentValue * 10 + (result[cursor] - '0'); + ++cursor; + } + if (negativeExponent) + exponentValue = -exponentValue; + if (exponentValue >= -4 && exponentValue < std::numeric_limits::digits10) { + const bool negative = !result.empty() && result[0] == '-'; + const size_t mantissaBegin = negative ? 1 : 0; + std::string digits = result.substr(mantissaBegin, exponent - mantissaBegin); + const size_t dot = digits.find('.'); + if (dot != std::string::npos) + digits.erase(dot, 1); + const int decimalPosition = 1 + exponentValue; + if (decimalPosition <= 0) { + digits.insert(0, static_cast(-decimalPosition), '0'); + digits.insert(0, "0."); + } else if (static_cast(decimalPosition) >= digits.size()) { + digits.append(static_cast(decimalPosition) - digits.size(), '0'); + } else { + digits.insert(static_cast(decimalPosition), 1, '.'); + } + result = negative ? "-" + digits : digits; + } + } + if (result.find_first_of(".eE") == std::string::npos) { + result += ".0"; + } + return result; +} +namespace { + //===------------------------------------------------------------------===// + // Serializer sink adapters + // + // The serializer targets this tiny common protocol. String failures throw + // as normal allocation/length errors; stream failures set failbit and are + // returned as false. This keeps traversal and escaping logic identical. + //===------------------------------------------------------------------===// + + // Appends serialized bytes directly to a caller-owned string. + class StringSink { + public: + // Binds to the output string without clearing its existing contents. + StringSink(std::string& aOut, size_t aLimit) + : _out(aOut) + , _limit(aLimit) + , _written(0) {} + + // Appends one byte. + void put(char aChar) { + account(1); + _out += aChar; + } + // Appends an exact byte range. + void write(const char* aData, size_t aSize) { + account(aSize); + _out.append(aData, aSize); + } + // Appends repeated indentation, preserving std::string's length checks. + bool repeat(char aChar, size_t aCount) { + // std::string::append performs the correct max_size check and + // throws std::length_error before attempting an impossible + // allocation. This keeps pathological indentation options from + // turning into an effectively unbounded byte-at-a-time loop. + account(aCount); + _out.append(aCount, aChar); + return true; + } + // Converts arithmetic overflow in indentation sizing into a length error. + bool fail() { throw std::length_error("JSON indentation exceeds string limits"); } + // A std::string cannot report failure state, so reject invalid UTF-8 by exception. + bool invalidUtf8() { throw std::invalid_argument("JSON string contains invalid UTF-8"); } + // Non-finite double under the RejectNonFinite policy: report by exception. + bool invalidNumber() { throw std::invalid_argument("JSON number is not finite"); } + // A live string sink has no independent error state. + explicit operator bool() const { return true; } + + private: + void account(size_t amount) { + if (_limit != 0 && amount > _limit - std::min(_written, _limit)) + throw std::length_error("JSON output exceeds maxOutputBytes"); + _written += amount; + } + + std::string& _out; + size_t _limit; + size_t _written; + }; + + // Runs the exact serializer without retaining bytes. This preflight keeps + // logical failures (invalid UTF-8, indentation overflow, and output-budget + // exhaustion) from partially modifying a caller's stream. + class CountingSink { + public: + explicit CountingSink(size_t aLimit) + : _limit(aLimit) + , _written(0) + , _valid(true) + , _invalidUtf8(false) + , _invalidNumber(false) {} + + void put(char) { account(1); } + void write(const char*, size_t aSize) { account(aSize); } + bool repeat(char, size_t aCount) { return account(aCount); } + bool fail() { + _valid = false; + return false; + } + bool invalidUtf8() { + _invalidUtf8 = true; + return fail(); + } + bool invalidNumber() { + _invalidNumber = true; + return fail(); + } + explicit operator bool() const { return _valid; } + size_t size() const { return _written; } + bool hasInvalidUtf8() const { return _invalidUtf8; } + bool hasInvalidNumber() const { return _invalidNumber; } + + private: + bool account(size_t aAmount) { + if (!_valid) + return false; + if (aAmount > std::numeric_limits::max() - _written || + (_limit != 0 && aAmount > _limit - std::min(_written, _limit))) { + _valid = false; + return false; + } + _written += aAmount; + return true; + } + + size_t _limit; + size_t _written; + bool _valid; + bool _invalidUtf8; + bool _invalidNumber; + }; + + // Writes serialized bytes incrementally and reflects ostream failure state. + class StreamSink { + public: + // Binds to a caller-owned stream without changing its formatting flags. + StreamSink(std::ostream& aOut, size_t aLimit) + : _out(aOut) + , _limit(aLimit) + , _written(0) {} + + // Writes one byte through the stream buffer. + void put(char aChar) { + if (account(1)) + _out.put(aChar); + } + // Writes an exact byte range. + void write(const char* aData, size_t aSize) { + if (aSize > static_cast(std::numeric_limits::max())) { + _out.setstate(std::ios::failbit); + return; + } + if (!account(aSize)) + return; + _out.write(aData, static_cast(aSize)); + } + // Emits indentation in bounded blocks and rejects counts that cannot be + // represented by ostream::write's streamsize parameter. + bool repeat(char aChar, size_t aCount) { + const size_t maxWrite = + static_cast(std::numeric_limits::max()); + if (aCount > maxWrite || !account(aCount)) { + if (_out) + _out.setstate(std::ios::failbit); + return false; + } + + char block[256]; + std::memset(block, static_cast(aChar), sizeof(block)); + while (aCount != 0 && _out) { + const size_t amount = std::min(aCount, sizeof(block)); + _out.write(block, static_cast(amount)); + aCount -= amount; + } + return static_cast(_out); + } + // Marks non-I/O serializer failures in the stream's normal error state. + bool fail() { + _out.setstate(std::ios::failbit); + return false; + } + // Streaming reports invalid programmatic string data through failbit. + bool invalidUtf8() { return fail(); } + // Streaming reports a non-finite double (RejectNonFinite) through failbit. + bool invalidNumber() { return fail(); } + // Exposes the underlying stream state to generic serializer code. + explicit operator bool() const { return static_cast(_out); } + + private: + bool account(size_t amount) { + if (_limit != 0 && amount > _limit - std::min(_written, _limit)) { + _out.setstate(std::ios::failbit); + return false; + } + _written += amount; + return true; + } + + std::ostream& _out; + size_t _limit; + size_t _written; + }; + + template + // Writes depth * indentWidth characters after checking multiplication overflow. + bool writeIndent(Sink& out, size_t depth, const pjson::SerializeOptions& opts) { + const char indent = opts.indentCharacter == '\t' ? '\t' : ' '; + if (opts.indentWidth != 0 && depth > size_t(-1) / opts.indentWidth) + return out.fail(); + const size_t count = depth * opts.indentWidth; + return out.repeat(indent, count); + } + + // Writes one UTF-16 code unit in canonical lower-case \uXXXX form. + template bool writeUnicodeEscape(Sink& out, uint16_t value) { + static const char hex[] = "0123456789abcdef"; + char escape[6] = {'\\', + 'u', + hex[(value >> 12U) & 0x0FU], + hex[(value >> 8U) & 0x0FU], + hex[(value >> 4U) & 0x0FU], + hex[value & 0x0FU]}; + out.write(escape, sizeof(escape)); + return static_cast(out); + } +} // namespace + +template +// Writes a JSON string body, optionally converting every non-ASCII code point +// to one UTF-16 escape (or a surrogate pair) without adding surrounding quotes. +bool pjsonImpl::_writeEscapedTo(Sink& aOut, const std::string& aIn, bool bEscapeNonAscii) { + size_t i = 0; + while (i < aIn.size()) { + const unsigned char ch = static_cast(aIn[i]); + const char* escape = nullptr; + switch (ch) { + case '"': + escape = "\\\""; + break; + case '\\': + escape = "\\\\"; + break; + case '\b': + escape = "\\b"; + break; + case '\f': + escape = "\\f"; + break; + case '\n': + escape = "\\n"; + break; + case '\r': + escape = "\\r"; + break; + case '\t': + escape = "\\t"; + break; + default: + break; + } + if (escape) { + aOut.write(escape, 2); + ++i; + if (!aOut) + return false; + continue; + } + if (ch < 0x20) { + if (!writeUnicodeEscape(aOut, static_cast(ch))) + return false; + ++i; + continue; + } + if (ch < 0x80) { + aOut.put(static_cast(ch)); + ++i; + if (!aOut) + return false; + continue; + } + + const int byteCount = _utf8Len(aIn.data(), i, aIn.size()); + if (byteCount == 0) + return aOut.invalidUtf8(); + if (!bEscapeNonAscii) { + aOut.write(aIn.data() + i, static_cast(byteCount)); + i += static_cast(byteCount); + if (!aOut) + return false; + continue; + } + + uint32_t codePoint = ch & (byteCount == 2 ? 0x1FU : byteCount == 3 ? 0x0FU : 0x07U); + for (size_t k = 1; k < static_cast(byteCount); ++k) + codePoint = (codePoint << 6U) | (static_cast(aIn[i + k]) & 0x3FU); + if (codePoint <= 0xFFFFU) { + if (!writeUnicodeEscape(aOut, static_cast(codePoint))) + return false; + } else { + codePoint -= 0x10000U; + const uint16_t high = static_cast(0xD800U + (codePoint >> 10U)); + const uint16_t low = static_cast(0xDC00U + (codePoint & 0x3FFU)); + if (!writeUnicodeEscape(aOut, high) || !writeUnicodeEscape(aOut, low)) + return false; + } + i += static_cast(byteCount); + } + return static_cast(aOut); +} + +template +// Emits a scalar or an empty container immediately. For a non-empty container, +// emits its opening delimiter and pushes a frame whose cursor is at its first +// child; the caller owns closing it after all children have been traversed. +bool pjsonImpl::_openOrEmit(Sink& aOut, const pjson* aValue, size_t aDepth, + const pjson::SerializeOptions& aOpts, + std::vector& aFrames) { + switch (aValue->_pImpl->_eType) { + case pjson::jsonType::jsonNull: + aOut.write("null", 4); + return static_cast(aOut); + case pjson::jsonType::jsonString: + aOut.put('"'); + if (!aOut || + !_writeEscapedTo(aOut, *aValue->_pImpl->_pValueString, aOpts.escapeNonAscii)) + return false; + aOut.put('"'); + return static_cast(aOut); + case pjson::jsonType::jsonNumberInt: { + const std::string text = std::to_string(aValue->_pImpl->_valueInt); + aOut.write(text.data(), text.size()); + return static_cast(aOut); + } + case pjson::jsonType::jsonNumberUInt: { + const std::string text = std::to_string(aValue->_pImpl->_valueUInt); + aOut.write(text.data(), text.size()); + return static_cast(aOut); + } + case pjson::jsonType::jsonNumberDouble: { + const double d = aValue->_pImpl->_valueDouble; + if (!std::isfinite(d)) { + switch (aOpts.nonFinite) { + case pjson::SerializeOptions::RejectNonFinite: + return aOut.invalidNumber(); + case pjson::SerializeOptions::NonFiniteToNull: + aOut.write("null", 4); + return static_cast(aOut); + case pjson::SerializeOptions::NonFiniteToString: { + const char* text = + std::isnan(d) ? "\"NaN\"" : (d < 0 ? "\"-Infinity\"" : "\"Infinity\""); + aOut.write(text, std::char_traits::length(text)); + return static_cast(aOut); + } + } + } + const std::string text = _formatDouble(d); + aOut.write(text.data(), text.size()); + return static_cast(aOut); + } + case pjson::jsonType::jsonBoolean: + if (aValue->_pImpl->_valueBool) + aOut.write("true", 4); + else + aOut.write("false", 5); + return static_cast(aOut); + case pjson::jsonType::jsonArray: + if (aValue->_pImpl->_pValueArray->empty()) { + aOut.write("[]", 2); + return static_cast(aOut); + } + aOut.put('['); + break; + case pjson::jsonType::jsonObject: + if (aValue->_pImpl->_pValueMap->empty()) { + aOut.write("{}", 2); + return static_cast(aOut); + } + aOut.put('{'); + break; + } + if (!aOut) + return false; + + SerializeFrame frame; + frame.isObject = aValue->_pImpl->_eType == pjson::jsonType::jsonObject; + frame.depth = aDepth; + frame.first = true; + frame.array = frame.isObject ? nullptr : aValue->_pImpl->_pValueArray; + frame.arrayIndex = 0; + frame.object = frame.isObject ? aValue->_pImpl->_pValueMap : nullptr; + if (frame.isObject) + frame.objectIt = frame.object->begin(); + aFrames.push_back(frame); + return true; +} + +template +// Serializes without recursive C++ calls. Before descending, the parent cursor +// advances past the chosen child, so a pushed child frame cannot invalidate the +// parent's progress when the vector reallocates. +bool pjsonImpl::_writeValueTo(Sink& aOut, const pjson& aValue, + const pjson::SerializeOptions& aOpts) { + std::vector stack; + stack.reserve(32); + if (!_openOrEmit(aOut, &aValue, 0, aOpts, stack)) + return false; + + while (!stack.empty()) { + SerializeFrame& frame = stack.back(); + const pjson* child = nullptr; + const std::string* key = nullptr; + bool hasNext = false; + if (frame.isObject) { + hasNext = frame.objectIt != frame.object->end(); + if (hasNext) { + key = &frame.objectIt->first; + child = frame.objectIt->second; + } + } else { + hasNext = frame.arrayIndex < frame.array->size(); + if (hasNext) + child = (*frame.array)[frame.arrayIndex]; + } + + if (hasNext) { + if (!frame.first) + aOut.put(','); + frame.first = false; + const size_t childDepth = frame.depth + 1; + const bool isObject = frame.isObject; + if (isObject) + ++frame.objectIt; + else + ++frame.arrayIndex; + if (aOpts.pretty) { + aOut.put('\n'); + if (!writeIndent(aOut, childDepth, aOpts)) + return false; + } + if (isObject) { + aOut.put('"'); + if (!aOut || !_writeEscapedTo(aOut, *key, aOpts.escapeNonAscii)) + return false; + if (aOpts.pretty) + aOut.write("\": ", 3); + else + aOut.write("\":", 2); + } + if (!aOut || !_openOrEmit(aOut, child, childDepth, aOpts, stack)) + return false; + } else { + const size_t depth = frame.depth; + const bool isObject = frame.isObject; + stack.pop_back(); + if (aOpts.pretty) { + aOut.put('\n'); + if (!writeIndent(aOut, depth, aOpts)) + return false; + } + aOut.put(isObject ? '}' : ']'); + if (!aOut) + return false; + } + } + return static_cast(aOut); +} + +/*static*/ +// Appends one serialized value to an existing string. +void pjsonImpl::_appendValue(std::string& aOut, const pjson& aValue, + const pjson::SerializeOptions& aOpts) { + StringSink sink(aOut, aOpts.maxOutputBytes); + _writeValueTo(sink, aValue, aOpts); +} + +/*static*/ +// Streams one serialized value and returns the resulting stream health. +bool pjsonImpl::_writeValue(std::ostream& aOut, const pjson& aValue, + const pjson::SerializeOptions& aOpts) { + CountingSink count(aOpts.maxOutputBytes); + if (!_writeValueTo(count, aValue, aOpts)) { + aOut.setstate(std::ios::failbit); + return false; + } + // Preflight owns the configured budget; emission itself is unlimited so a + // successful count cannot fail due to double-accounting. + StreamSink sink(aOut, 0); + return _writeValueTo(sink, aValue, aOpts); +} + +// Serializes with compact default options. +std::string pjson::toString() const { + return toString(SerializeOptions()); +} + +// Serializes this complete DOM to a newly allocated string. +std::string pjson::toString(const SerializeOptions& aOpts) const { + CountingSink count(aOpts.maxOutputBytes); + if (!pjsonImpl::_writeValueTo(count, *this, aOpts)) { + if (count.hasInvalidUtf8()) + throw std::invalid_argument("JSON string contains invalid UTF-8"); + if (count.hasInvalidNumber()) + throw std::invalid_argument("JSON number is not finite"); + throw std::length_error("JSON output exceeds maxOutputBytes or contains invalid data"); + } + std::string result; + result.reserve(count.size()); + pjsonImpl::_appendValue(result, *this, aOpts); + return result; +} + +// Serializes transactionally into a caller-owned string. The caller's prior +// bytes survive every logical, allocation, or internal failure. +bool pjson::toString(std::string& aOut, SerializeError& aError, + const SerializeOptions& aOpts) const noexcept { + aError.reset(); + try { + CountingSink count(aOpts.maxOutputBytes); + if (!pjsonImpl::_writeValueTo(count, *this, aOpts)) { + if (count.hasInvalidUtf8()) { + setSerializeError(aError, SerializeError::InvalidUtf8, + "JSON string contains invalid UTF-8"); + } else if (count.hasInvalidNumber()) { + setSerializeError(aError, SerializeError::NonFiniteNumber, + "JSON number is not finite"); + } else { + setSerializeError(aError, SerializeError::OutputLimit, + "JSON output exceeds maxOutputBytes or representable size"); + } + return false; + } + std::string result; + result.reserve(count.size()); + pjsonImpl::_appendValue(result, *this, aOpts); + aOut.swap(result); + return true; + } catch (const std::bad_alloc&) { + setSerializeError(aError, SerializeError::AllocationFailure, + "JSON serialization ran out of memory"); + } catch (const std::length_error& exception) { + setSerializeError(aError, SerializeError::OutputLimit, exception.what()); + } catch (const std::invalid_argument& exception) { + setSerializeError(aError, SerializeError::InternalError, exception.what()); + } catch (...) { + setSerializeError(aError, SerializeError::InternalError, + "JSON serialization failed with an internal exception"); + } + return false; +} + +// Streams with compact default options. +void pjson::write(std::ostream& aOut) const { + write(aOut, SerializeOptions()); +} + +// Writes this complete DOM incrementally; callers inspect the stream state for +// output errors because the public streaming API reports through std::ostream. +void pjson::write(std::ostream& aOut, const SerializeOptions& aOpts) const { + pjsonImpl::_writeValue(aOut, *this, aOpts); +} + +// Non-throwing stream serialization. Logical failures are detected by the +// existing preflight before emission; only a physical stream failure may have +// emitted a prefix. +bool pjson::write(std::ostream& aOut, SerializeError& aError, + const SerializeOptions& aOpts) const noexcept { + aError.reset(); + try { + CountingSink count(aOpts.maxOutputBytes); + if (!pjsonImpl::_writeValueTo(count, *this, aOpts)) { + if (count.hasInvalidUtf8()) { + setSerializeError(aError, SerializeError::InvalidUtf8, + "JSON string contains invalid UTF-8"); + } else if (count.hasInvalidNumber()) { + setSerializeError(aError, SerializeError::NonFiniteNumber, + "JSON number is not finite"); + } else { + setSerializeError(aError, SerializeError::OutputLimit, + "JSON output exceeds maxOutputBytes or representable size"); + } + try { + aOut.setstate(std::ios::failbit); + } catch (...) { + // Keep the more precise logical SerializeError category even + // when the caller enabled stream exceptions for failbit. + (void)0; + } + return false; + } + StreamSink sink(aOut, 0); + if (!pjsonImpl::_writeValueTo(sink, *this, aOpts)) { + setSerializeError(aError, SerializeError::StreamFailure, + "JSON destination stream write failed"); + return false; + } + return true; + } catch (const std::bad_alloc&) { + setSerializeError(aError, SerializeError::AllocationFailure, + "JSON serialization ran out of memory"); + } catch (const std::ios_base::failure& exception) { + setSerializeError(aError, SerializeError::StreamFailure, exception.what()); + } catch (...) { + setSerializeError(aError, SerializeError::InternalError, + "JSON serialization failed with an internal exception"); + } + try { + aOut.setstate(std::ios::failbit); + } catch (...) { + // The structured result remains authoritative for this noexcept API. + (void)0; + } + return false; +} diff --git a/pjsonlib/src/third_party/json-schema-2020-12/LICENSE.txt b/pjsonlib/src/third_party/json-schema-2020-12/LICENSE.txt new file mode 100644 index 0000000..7d4605f --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/LICENSE.txt @@ -0,0 +1,213 @@ +Copyright (c) 2022 JSON Schema Specification Authors + +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. + +--- + +This Academic Free License (the "License") applies to any original work +of authorship (the "Original Work") whose owner (the "Licensor") has +placed the following licensing notice adjacent to the copyright notice +for the Original Work: + +Licensed under the Academic Free License version 3.0 + +1) Grant of Copyright License. Licensor grants You a worldwide, +royalty-free, non-exclusive, sublicensable license, for the duration of +the copyright, to do the following: + +a) to reproduce the Original Work in copies, either alone or as part of +a collective work; + +b) to translate, adapt, alter, transform, modify, or arrange the +Original Work, thereby creating derivative works ("Derivative Works") +based upon the Original Work; + +c) to distribute or communicate copies of the Original Work and +Derivative Works to the public, under any license of your choice that +does not contradict the terms and conditions, including Licensor's +reserved rights and remedies, in this Academic Free License; + +d) to perform the Original Work publicly; and + +e) to display the Original Work publicly. + +2) Grant of Patent License. Licensor grants You a worldwide, +royalty-free, non-exclusive, sublicensable license, under patent claims +owned or controlled by the Licensor that are embodied in the Original +Work as furnished by the Licensor, for the duration of the patents, to +make, use, sell, offer for sale, have made, and import the Original Work +and Derivative Works. + +3) Grant of Source Code License. The term "Source Code" means the +preferred form of the Original Work for making modifications to it +and all available documentation describing how to modify the Original +Work. Licensor agrees to provide a machine-readable copy of the Source +Code of the Original Work along with each copy of the Original Work +that Licensor distributes. Licensor reserves the right to satisfy this +obligation by placing a machine-readable copy of the Source Code in an +information repository reasonably calculated to permit inexpensive and +convenient access by You for as long as Licensor continues to distribute +the Original Work. + +4) Exclusions From License Grant. Neither the names of Licensor, nor +the names of any contributors to the Original Work, nor any of their +trademarks or service marks, may be used to endorse or promote products +derived from this Original Work without express prior permission of the +Licensor. Except as expressly stated herein, nothing in this License +grants any license to Licensor's trademarks, copyrights, patents, trade +secrets or any other intellectual property. No patent license is granted +to make, use, sell, offer for sale, have made, or import embodiments +of any patent claims other than the licensed claims defined in Section +2. No license is granted to the trademarks of Licensor even if such +marks are included in the Original Work. Nothing in this License shall +be interpreted to prohibit Licensor from licensing under terms different +from this License any Original Work that Licensor otherwise would have a +right to license. + +5) External Deployment. The term "External Deployment" means the use, +distribution, or communication of the Original Work or Derivative +Works in any way such that the Original Work or Derivative Works may +be used by anyone other than You, whether those works are distributed +or communicated to those persons or made available as an application +intended for use over a network. As an express condition for the grants +of license hereunder, You must treat any External Deployment by You of +the Original Work or a Derivative Work as a distribution under section +1(c). + +6) Attribution Rights. You must retain, in the Source Code of any +Derivative Works that You create, all copyright, patent, or trademark +notices from the Source Code of the Original Work, as well as any +notices of licensing and any descriptive text identified therein as an +"Attribution Notice." You must cause the Source Code for any Derivative +Works that You create to carry a prominent Attribution Notice reasonably +calculated to inform recipients that You have modified the Original +Work. + +7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants +that the copyright in and to the Original Work and the patent rights +granted herein by Licensor are owned by the Licensor or are sublicensed +to You under the terms of this License with the permission of the +contributor(s) of those copyrights and patent rights. Except as +expressly stated in the immediately preceding sentence, the Original +Work is provided under this License on an "AS IS" BASIS and WITHOUT +WARRANTY, either express or implied, including, without limitation, +the warranties of non-infringement, merchantability or fitness for a +particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL +WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential +part of this License. No license to the Original Work is granted by this +License except under this disclaimer. + +8) Limitation of Liability. Under no circumstances and under no legal +theory, whether in tort (including negligence), contract, or otherwise, +shall the Licensor be liable to anyone for any indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or the use of the Original Work including, +without limitation, damages for loss of goodwill, work stoppage, +computer failure or malfunction, or any and all other commercial damages +or losses. This limitation of liability shall not apply to the extent +applicable law prohibits such limitation. + +9) Acceptance and Termination. If, at any time, You expressly +assented to this License, that assent indicates your clear and +irrevocable acceptance of this License and all of its terms and +conditions. If You distribute or communicate copies of the Original +Work or a Derivative Work, You must make a reasonable effort under the +circumstances to obtain the express assent of recipients to the terms +of this License. This License conditions your rights to undertake +the activities listed in Section 1, including your right to create +Derivative Works based upon the Original Work, and doing so without +honoring these terms and conditions is prohibited by copyright law and +international treaty. Nothing in this License is intended to affect +copyright exceptions and limitations (including "fair use" or "fair +dealing"). This License shall terminate immediately and You may no +longer exercise any of the rights granted to You by this License upon +your failure to honor the conditions in Section 1(c). + +10) Termination for Patent Action. This License shall terminate +automatically and You may no longer exercise any of the rights granted +to You by this License as of the date You commence an action, including +a cross-claim or counterclaim, against Licensor or any licensee +alleging that the Original Work infringes a patent. This termination +provision shall not apply for an action alleging patent infringement by +combinations of the Original Work with other software or hardware. + +11) Jurisdiction, Venue and Governing Law. Any action or suit relating +to this License may be brought only in the courts of a jurisdiction +wherein the Licensor resides or in which Licensor conducts its primary +business, and under the laws of that jurisdiction excluding its +conflict-of-law provisions. The application of the United Nations +Convention on Contracts for the International Sale of Goods is +expressly excluded. Any use of the Original Work outside the scope +of this License or after its termination shall be subject to the +requirements and penalties of copyright or patent law in the appropriate +jurisdiction. This section shall survive the termination of this +License. + +12) Attorneys' Fees. In any action to enforce the terms of this License +or seeking damages relating thereto, the prevailing party shall +be entitled to recover its costs and expenses, including, without +limitation, reasonable attorneys' fees and costs incurred in connection +with such action, including any appeal of such action. This section +shall survive the termination of this License. + +13) Miscellaneous. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. + +14) Definition of "You" in This License. "You" throughout this License, +whether in upper or lower case, means an individual or a legal entity +exercising rights under, and complying with all of the terms of, this +License. For legal entities, "You" includes any entity that controls, +is controlled by, or is under common control with you. For 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. + +15) Right to Use. You may use the Original Work in all ways not +otherwise restricted or conditioned by this License or by law, and +Licensor promises not to interfere with or be responsible for such uses +by You. + +16) Modification of This License. This License is Copyright © +2005 Lawrence Rosen. Permission is granted to copy, distribute, or +communicate this License without modification. Nothing in this License +permits You to modify this License as applied to the Original Work or +to Derivative Works. However, You may modify the text of this License +and copy, distribute or communicate your modified version (the "Modified +License") and apply it to other original works of authorship subject +to the following conditions: (i) You may not indicate in any way that +your Modified License is the "Academic Free License" or "AFL" and you +may not use those names in the name of your Modified License; (ii) You +must replace the notice specified in the first paragraph above with +the notice "Licensed under " or with a +notice of your own that is not confusingly similar to the notice in +this License; and (iii) You may not claim that your original works are +open source software unless your Modified License has been approved by +Open Source Initiative (OSI) and You comply with its license review and +certification process. diff --git a/pjsonlib/src/third_party/json-schema-2020-12/VERSION.md b/pjsonlib/src/third_party/json-schema-2020-12/VERSION.md new file mode 100644 index 0000000..15bdef1 --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/VERSION.md @@ -0,0 +1,12 @@ + + + +# JSON Schema Draft 2020-12 meta-schemas + +- Canonical source: https://json-schema.org/draft/2020-12/ +- Test-suite revision used for verification: `3c25e5f709192aadf67cf7f2eb19771a57131fec` +- License: see `LICENSE.txt` + +The bundled JSON files are the published Draft 2020-12 schema and vocabulary +meta-schemas. They are private immutable resolver resources and do not trigger +network access. diff --git a/pjsonlib/src/third_party/json-schema-2020-12/meta/applicator.json b/pjsonlib/src/third_party/json-schema-2020-12/meta/applicator.json new file mode 100644 index 0000000..f477597 --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/meta/applicator.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/applicator", + "$dynamicAnchor": "meta", + + "title": "Applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "prefixItems": { "$ref": "#/$defs/schemaArray" }, + "items": { "$dynamicRef": "#meta" }, + "contains": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "#meta" }, + "properties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "propertyNames": { "$dynamicRef": "#meta" }, + "if": { "$dynamicRef": "#meta" }, + "then": { "$dynamicRef": "#meta" }, + "else": { "$dynamicRef": "#meta" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$dynamicRef": "#meta" } + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$dynamicRef": "#meta" } + } + } +} diff --git a/pjsonlib/src/third_party/json-schema-2020-12/meta/content.json b/pjsonlib/src/third_party/json-schema-2020-12/meta/content.json new file mode 100644 index 0000000..76e3760 --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/meta/content.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/content", + "$dynamicAnchor": "meta", + + "title": "Content vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "contentEncoding": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentSchema": { "$dynamicRef": "#meta" } + } +} diff --git a/pjsonlib/src/third_party/json-schema-2020-12/meta/core.json b/pjsonlib/src/third_party/json-schema-2020-12/meta/core.json new file mode 100644 index 0000000..6918622 --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/meta/core.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/core", + "$dynamicAnchor": "meta", + + "title": "Core vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "$id": { + "$ref": "#/$defs/uriReferenceString", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { "$ref": "#/$defs/uriString" }, + "$ref": { "$ref": "#/$defs/uriReferenceString" }, + "$anchor": { "$ref": "#/$defs/anchorString" }, + "$dynamicRef": { "$ref": "#/$defs/uriReferenceString" }, + "$dynamicAnchor": { "$ref": "#/$defs/anchorString" }, + "$vocabulary": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/uriString" }, + "additionalProperties": { + "type": "boolean" + } + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" } + } + }, + "$defs": { + "anchorString": { + "type": "string", + "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$" + }, + "uriString": { + "type": "string", + "format": "uri" + }, + "uriReferenceString": { + "type": "string", + "format": "uri-reference" + } + } +} diff --git a/pjsonlib/src/third_party/json-schema-2020-12/meta/format-annotation.json b/pjsonlib/src/third_party/json-schema-2020-12/meta/format-annotation.json new file mode 100644 index 0000000..3479e66 --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/meta/format-annotation.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation", + "$dynamicAnchor": "meta", + + "title": "Format vocabulary meta-schema for annotation results", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } +} diff --git a/pjsonlib/src/third_party/json-schema-2020-12/meta/format-assertion.json b/pjsonlib/src/third_party/json-schema-2020-12/meta/format-assertion.json new file mode 100644 index 0000000..1a4f106 --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/meta/format-assertion.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/format-assertion", + "$dynamicAnchor": "meta", + + "title": "Format vocabulary meta-schema for assertion results", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } +} diff --git a/pjsonlib/src/third_party/json-schema-2020-12/meta/meta-data.json b/pjsonlib/src/third_party/json-schema-2020-12/meta/meta-data.json new file mode 100644 index 0000000..4049ab2 --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/meta/meta-data.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/meta-data", + "$dynamicAnchor": "meta", + + "title": "Meta-data vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean", + "default": false + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + } + } +} diff --git a/pjsonlib/src/third_party/json-schema-2020-12/meta/unevaluated.json b/pjsonlib/src/third_party/json-schema-2020-12/meta/unevaluated.json new file mode 100644 index 0000000..93779e5 --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/meta/unevaluated.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated", + "$dynamicAnchor": "meta", + + "title": "Unevaluated applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "unevaluatedItems": { "$dynamicRef": "#meta" }, + "unevaluatedProperties": { "$dynamicRef": "#meta" } + } +} diff --git a/pjsonlib/src/third_party/json-schema-2020-12/meta/validation.json b/pjsonlib/src/third_party/json-schema-2020-12/meta/validation.json new file mode 100644 index 0000000..ebb75db --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/meta/validation.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/validation", + "$dynamicAnchor": "meta", + + "title": "Validation vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, + "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } +} diff --git a/pjsonlib/src/third_party/json-schema-2020-12/schema.json b/pjsonlib/src/third_party/json-schema-2020-12/schema.json new file mode 100644 index 0000000..d5e2d31 --- /dev/null +++ b/pjsonlib/src/third_party/json-schema-2020-12/schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/meta-data": true, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + {"$ref": "meta/core"}, + {"$ref": "meta/applicator"}, + {"$ref": "meta/unevaluated"}, + {"$ref": "meta/validation"}, + {"$ref": "meta/meta-data"}, + {"$ref": "meta/format-annotation"}, + {"$ref": "meta/content"} + ], + "type": ["object", "boolean"], + "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", + "properties": { + "definitions": { + "$comment": "\"definitions\" has been replaced by \"$defs\".", + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "deprecated": true, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$dynamicRef": "#meta" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + }, + "deprecated": true, + "default": {} + }, + "$recursiveAnchor": { + "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".", + "$ref": "meta/core#/$defs/anchorString", + "deprecated": true + }, + "$recursiveRef": { + "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".", + "$ref": "meta/core#/$defs/uriReferenceString", + "deprecated": true + } + } +} diff --git a/pjsonlib/src/third_party/ryu/LICENSE.txt b/pjsonlib/src/third_party/ryu/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/pjsonlib/src/third_party/ryu/LICENSE.txt @@ -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 [yyyy] [name of copyright owner] + + 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/pjsonlib/src/third_party/ryu/VERSION.md b/pjsonlib/src/third_party/ryu/VERSION.md new file mode 100644 index 0000000..1545b32 --- /dev/null +++ b/pjsonlib/src/third_party/ryu/VERSION.md @@ -0,0 +1,11 @@ + + + +# Ryu + +- Upstream: https://github.com/ulfjack/ryu +- Commit: `4c0618b0e44f7ef027ebae05d2cc7812048f7c8f` +- License: Apache-2.0; see `LICENSE.txt` +- Vendored scope: shortest binary64-to-decimal formatter and required headers + +The files are unmodified upstream sources and are private implementation inputs. diff --git a/pjsonlib/src/third_party/ryu/ryu/common.h b/pjsonlib/src/third_party/ryu/ryu/common.h new file mode 100644 index 0000000..7dc1309 --- /dev/null +++ b/pjsonlib/src/third_party/ryu/ryu/common.h @@ -0,0 +1,114 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_COMMON_H +#define RYU_COMMON_H + +#include +#include +#include + +#if defined(_M_IX86) || defined(_M_ARM) +#define RYU_32_BIT_PLATFORM +#endif + +// Returns the number of decimal digits in v, which must not contain more than 9 digits. +static inline uint32_t decimalLength9(const uint32_t v) { + // Function precondition: v is not a 10-digit number. + // (f2s: 9 digits are sufficient for round-tripping.) + // (d2fixed: We print 9-digit blocks.) + assert(v < 1000000000); + if (v >= 100000000) { return 9; } + if (v >= 10000000) { return 8; } + if (v >= 1000000) { return 7; } + if (v >= 100000) { return 6; } + if (v >= 10000) { return 5; } + if (v >= 1000) { return 4; } + if (v >= 100) { return 3; } + if (v >= 10) { return 2; } + return 1; +} + +// Returns e == 0 ? 1 : [log_2(5^e)]; requires 0 <= e <= 3528. +static inline int32_t log2pow5(const int32_t e) { + // This approximation works up to the point that the multiplication overflows at e = 3529. + // If the multiplication were done in 64 bits, it would fail at 5^4004 which is just greater + // than 2^9297. + assert(e >= 0); + assert(e <= 3528); + return (int32_t) ((((uint32_t) e) * 1217359) >> 19); +} + +// Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 3528. +static inline int32_t pow5bits(const int32_t e) { + // This approximation works up to the point that the multiplication overflows at e = 3529. + // If the multiplication were done in 64 bits, it would fail at 5^4004 which is just greater + // than 2^9297. + assert(e >= 0); + assert(e <= 3528); + return (int32_t) (((((uint32_t) e) * 1217359) >> 19) + 1); +} + +// Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 3528. +static inline int32_t ceil_log2pow5(const int32_t e) { + return log2pow5(e) + 1; +} + +// Returns floor(log_10(2^e)); requires 0 <= e <= 1650. +static inline uint32_t log10Pow2(const int32_t e) { + // The first value this approximation fails for is 2^1651 which is just greater than 10^297. + assert(e >= 0); + assert(e <= 1650); + return (((uint32_t) e) * 78913) >> 18; +} + +// Returns floor(log_10(5^e)); requires 0 <= e <= 2620. +static inline uint32_t log10Pow5(const int32_t e) { + // The first value this approximation fails for is 5^2621 which is just greater than 10^1832. + assert(e >= 0); + assert(e <= 2620); + return (((uint32_t) e) * 732923) >> 20; +} + +static inline int copy_special_str(char * const result, const bool sign, const bool exponent, const bool mantissa) { + if (mantissa) { + memcpy(result, "NaN", 3); + return 3; + } + if (sign) { + result[0] = '-'; + } + if (exponent) { + memcpy(result + sign, "Infinity", 8); + return sign + 8; + } + memcpy(result + sign, "0E0", 3); + return sign + 3; +} + +static inline uint32_t float_to_bits(const float f) { + uint32_t bits = 0; + memcpy(&bits, &f, sizeof(float)); + return bits; +} + +static inline uint64_t double_to_bits(const double d) { + uint64_t bits = 0; + memcpy(&bits, &d, sizeof(double)); + return bits; +} + +#endif // RYU_COMMON_H diff --git a/pjsonlib/src/third_party/ryu/ryu/d2s.c b/pjsonlib/src/third_party/ryu/ryu/d2s.c new file mode 100644 index 0000000..41de875 --- /dev/null +++ b/pjsonlib/src/third_party/ryu/ryu/d2s.c @@ -0,0 +1,509 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. + +// Runtime compiler options: +// -DRYU_DEBUG Generate verbose debugging output to stdout. +// +// -DRYU_ONLY_64_BIT_OPS Avoid using uint128_t or 64-bit intrinsics. Slower, +// depending on your compiler. +// +// -DRYU_OPTIMIZE_SIZE Use smaller lookup tables. Instead of storing every +// required power of 5, only store every 26th entry, and compute +// intermediate values with a multiplication. This reduces the lookup table +// size by about 10x (only one case, and only double) at the cost of some +// performance. Currently requires MSVC intrinsics. + +#include "ryu/ryu.h" + +#include +#include +#include +#include +#include + +#ifdef RYU_DEBUG +#include +#include +#endif + +#include "ryu/common.h" +#include "ryu/digit_table.h" +#include "ryu/d2s_intrinsics.h" + +// Include either the small or the full lookup tables depending on the mode. +#if defined(RYU_OPTIMIZE_SIZE) +#include "ryu/d2s_small_table.h" +#else +#include "ryu/d2s_full_table.h" +#endif + +#define DOUBLE_MANTISSA_BITS 52 +#define DOUBLE_EXPONENT_BITS 11 +#define DOUBLE_BIAS 1023 + +static inline uint32_t decimalLength17(const uint64_t v) { + // This is slightly faster than a loop. + // The average output length is 16.38 digits, so we check high-to-low. + // Function precondition: v is not an 18, 19, or 20-digit number. + // (17 digits are sufficient for round-tripping.) + assert(v < 100000000000000000L); + if (v >= 10000000000000000L) { return 17; } + if (v >= 1000000000000000L) { return 16; } + if (v >= 100000000000000L) { return 15; } + if (v >= 10000000000000L) { return 14; } + if (v >= 1000000000000L) { return 13; } + if (v >= 100000000000L) { return 12; } + if (v >= 10000000000L) { return 11; } + if (v >= 1000000000L) { return 10; } + if (v >= 100000000L) { return 9; } + if (v >= 10000000L) { return 8; } + if (v >= 1000000L) { return 7; } + if (v >= 100000L) { return 6; } + if (v >= 10000L) { return 5; } + if (v >= 1000L) { return 4; } + if (v >= 100L) { return 3; } + if (v >= 10L) { return 2; } + return 1; +} + +// A floating decimal representing m * 10^e. +typedef struct floating_decimal_64 { + uint64_t mantissa; + // Decimal exponent's range is -324 to 308 + // inclusive, and can fit in a short if needed. + int32_t exponent; +} floating_decimal_64; + +static inline floating_decimal_64 d2d(const uint64_t ieeeMantissa, const uint32_t ieeeExponent) { + int32_t e2; + uint64_t m2; + if (ieeeExponent == 0) { + // We subtract 2 so that the bounds computation has 2 additional bits. + e2 = 1 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS - 2; + m2 = ieeeMantissa; + } else { + e2 = (int32_t) ieeeExponent - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS - 2; + m2 = (1ull << DOUBLE_MANTISSA_BITS) | ieeeMantissa; + } + const bool even = (m2 & 1) == 0; + const bool acceptBounds = even; + +#ifdef RYU_DEBUG + printf("-> %" PRIu64 " * 2^%d\n", m2, e2 + 2); +#endif + + // Step 2: Determine the interval of valid decimal representations. + const uint64_t mv = 4 * m2; + // Implicit bool -> int conversion. True is 1, false is 0. + const uint32_t mmShift = ieeeMantissa != 0 || ieeeExponent <= 1; + // We would compute mp and mm like this: + // uint64_t mp = 4 * m2 + 2; + // uint64_t mm = mv - 1 - mmShift; + + // Step 3: Convert to a decimal power base using 128-bit arithmetic. + uint64_t vr, vp, vm; + int32_t e10; + bool vmIsTrailingZeros = false; + bool vrIsTrailingZeros = false; + if (e2 >= 0) { + // I tried special-casing q == 0, but there was no effect on performance. + // This expression is slightly faster than max(0, log10Pow2(e2) - 1). + const uint32_t q = log10Pow2(e2) - (e2 > 3); + e10 = (int32_t) q; + const int32_t k = DOUBLE_POW5_INV_BITCOUNT + pow5bits((int32_t) q) - 1; + const int32_t i = -e2 + (int32_t) q + k; +#if defined(RYU_OPTIMIZE_SIZE) + uint64_t pow5[2]; + double_computeInvPow5(q, pow5); + vr = mulShiftAll64(m2, pow5, i, &vp, &vm, mmShift); +#else + vr = mulShiftAll64(m2, DOUBLE_POW5_INV_SPLIT[q], i, &vp, &vm, mmShift); +#endif +#ifdef RYU_DEBUG + printf("%" PRIu64 " * 2^%d / 10^%u\n", mv, e2, q); + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); +#endif + if (q <= 21) { + // This should use q <= 22, but I think 21 is also safe. Smaller values + // may still be safe, but it's more difficult to reason about them. + // Only one of mp, mv, and mm can be a multiple of 5, if any. + const uint32_t mvMod5 = ((uint32_t) mv) - 5 * ((uint32_t) div5(mv)); + if (mvMod5 == 0) { + vrIsTrailingZeros = multipleOfPowerOf5(mv, q); + } else if (acceptBounds) { + // Same as min(e2 + (~mm & 1), pow5Factor(mm)) >= q + // <=> e2 + (~mm & 1) >= q && pow5Factor(mm) >= q + // <=> true && pow5Factor(mm) >= q, since e2 >= q. + vmIsTrailingZeros = multipleOfPowerOf5(mv - 1 - mmShift, q); + } else { + // Same as min(e2 + 1, pow5Factor(mp)) >= q. + vp -= multipleOfPowerOf5(mv + 2, q); + } + } + } else { + // This expression is slightly faster than max(0, log10Pow5(-e2) - 1). + const uint32_t q = log10Pow5(-e2) - (-e2 > 1); + e10 = (int32_t) q + e2; + const int32_t i = -e2 - (int32_t) q; + const int32_t k = pow5bits(i) - DOUBLE_POW5_BITCOUNT; + const int32_t j = (int32_t) q - k; +#if defined(RYU_OPTIMIZE_SIZE) + uint64_t pow5[2]; + double_computePow5(i, pow5); + vr = mulShiftAll64(m2, pow5, j, &vp, &vm, mmShift); +#else + vr = mulShiftAll64(m2, DOUBLE_POW5_SPLIT[i], j, &vp, &vm, mmShift); +#endif +#ifdef RYU_DEBUG + printf("%" PRIu64 " * 5^%d / 10^%u\n", mv, -e2, q); + printf("%u %d %d %d\n", q, i, k, j); + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); +#endif + if (q <= 1) { + // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits. + // mv = 4 * m2, so it always has at least two trailing 0 bits. + vrIsTrailingZeros = true; + if (acceptBounds) { + // mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1. + vmIsTrailingZeros = mmShift == 1; + } else { + // mp = mv + 2, so it always has at least one trailing 0 bit. + --vp; + } + } else if (q < 63) { // TODO(ulfjack): Use a tighter bound here. + // We want to know if the full product has at least q trailing zeros. + // We need to compute min(p2(mv), p5(mv) - e2) >= q + // <=> p2(mv) >= q && p5(mv) - e2 >= q + // <=> p2(mv) >= q (because -e2 >= q) + vrIsTrailingZeros = multipleOfPowerOf2(mv, q); +#ifdef RYU_DEBUG + printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false"); +#endif + } + } +#ifdef RYU_DEBUG + printf("e10=%d\n", e10); + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); + printf("vm is trailing zeros=%s\n", vmIsTrailingZeros ? "true" : "false"); + printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false"); +#endif + + // Step 4: Find the shortest decimal representation in the interval of valid representations. + int32_t removed = 0; + uint8_t lastRemovedDigit = 0; + uint64_t output; + // On average, we remove ~2 digits. + if (vmIsTrailingZeros || vrIsTrailingZeros) { + // General case, which happens rarely (~0.7%). + for (;;) { + const uint64_t vpDiv10 = div10(vp); + const uint64_t vmDiv10 = div10(vm); + if (vpDiv10 <= vmDiv10) { + break; + } + const uint32_t vmMod10 = ((uint32_t) vm) - 10 * ((uint32_t) vmDiv10); + const uint64_t vrDiv10 = div10(vr); + const uint32_t vrMod10 = ((uint32_t) vr) - 10 * ((uint32_t) vrDiv10); + vmIsTrailingZeros &= vmMod10 == 0; + vrIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = (uint8_t) vrMod10; + vr = vrDiv10; + vp = vpDiv10; + vm = vmDiv10; + ++removed; + } +#ifdef RYU_DEBUG + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); + printf("d-10=%s\n", vmIsTrailingZeros ? "true" : "false"); +#endif + if (vmIsTrailingZeros) { + for (;;) { + const uint64_t vmDiv10 = div10(vm); + const uint32_t vmMod10 = ((uint32_t) vm) - 10 * ((uint32_t) vmDiv10); + if (vmMod10 != 0) { + break; + } + const uint64_t vpDiv10 = div10(vp); + const uint64_t vrDiv10 = div10(vr); + const uint32_t vrMod10 = ((uint32_t) vr) - 10 * ((uint32_t) vrDiv10); + vrIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = (uint8_t) vrMod10; + vr = vrDiv10; + vp = vpDiv10; + vm = vmDiv10; + ++removed; + } + } +#ifdef RYU_DEBUG + printf("%" PRIu64 " %d\n", vr, lastRemovedDigit); + printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false"); +#endif + if (vrIsTrailingZeros && lastRemovedDigit == 5 && vr % 2 == 0) { + // Round even if the exact number is .....50..0. + lastRemovedDigit = 4; + } + // We need to take vr + 1 if vr is outside bounds or we need to round up. + output = vr + ((vr == vm && (!acceptBounds || !vmIsTrailingZeros)) || lastRemovedDigit >= 5); + } else { + // Specialized for the common case (~99.3%). Percentages below are relative to this. + bool roundUp = false; + const uint64_t vpDiv100 = div100(vp); + const uint64_t vmDiv100 = div100(vm); + if (vpDiv100 > vmDiv100) { // Optimization: remove two digits at a time (~86.2%). + const uint64_t vrDiv100 = div100(vr); + const uint32_t vrMod100 = ((uint32_t) vr) - 100 * ((uint32_t) vrDiv100); + roundUp = vrMod100 >= 50; + vr = vrDiv100; + vp = vpDiv100; + vm = vmDiv100; + removed += 2; + } + // Loop iterations below (approximately), without optimization above: + // 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02% + // Loop iterations below (approximately), with optimization above: + // 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02% + for (;;) { + const uint64_t vpDiv10 = div10(vp); + const uint64_t vmDiv10 = div10(vm); + if (vpDiv10 <= vmDiv10) { + break; + } + const uint64_t vrDiv10 = div10(vr); + const uint32_t vrMod10 = ((uint32_t) vr) - 10 * ((uint32_t) vrDiv10); + roundUp = vrMod10 >= 5; + vr = vrDiv10; + vp = vpDiv10; + vm = vmDiv10; + ++removed; + } +#ifdef RYU_DEBUG + printf("%" PRIu64 " roundUp=%s\n", vr, roundUp ? "true" : "false"); + printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false"); +#endif + // We need to take vr + 1 if vr is outside bounds or we need to round up. + output = vr + (vr == vm || roundUp); + } + const int32_t exp = e10 + removed; + +#ifdef RYU_DEBUG + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); + printf("O=%" PRIu64 "\n", output); + printf("EXP=%d\n", exp); +#endif + + floating_decimal_64 fd; + fd.exponent = exp; + fd.mantissa = output; + return fd; +} + +static inline int to_chars(const floating_decimal_64 v, const bool sign, char* const result) { + // Step 5: Print the decimal representation. + int index = 0; + if (sign) { + result[index++] = '-'; + } + + uint64_t output = v.mantissa; + const uint32_t olength = decimalLength17(output); + +#ifdef RYU_DEBUG + printf("DIGITS=%" PRIu64 "\n", v.mantissa); + printf("OLEN=%u\n", olength); + printf("EXP=%u\n", v.exponent + olength); +#endif + + // Print the decimal digits. + // The following code is equivalent to: + // for (uint32_t i = 0; i < olength - 1; ++i) { + // const uint32_t c = output % 10; output /= 10; + // result[index + olength - i] = (char) ('0' + c); + // } + // result[index] = '0' + output % 10; + + uint32_t i = 0; + // We prefer 32-bit operations, even on 64-bit platforms. + // We have at most 17 digits, and uint32_t can store 9 digits. + // If output doesn't fit into uint32_t, we cut off 8 digits, + // so the rest will fit into uint32_t. + if ((output >> 32) != 0) { + // Expensive 64-bit division. + const uint64_t q = div1e8(output); + uint32_t output2 = ((uint32_t) output) - 100000000 * ((uint32_t) q); + output = q; + + const uint32_t c = output2 % 10000; + output2 /= 10000; + const uint32_t d = output2 % 10000; + const uint32_t c0 = (c % 100) << 1; + const uint32_t c1 = (c / 100) << 1; + const uint32_t d0 = (d % 100) << 1; + const uint32_t d1 = (d / 100) << 1; + memcpy(result + index + olength - 1, DIGIT_TABLE + c0, 2); + memcpy(result + index + olength - 3, DIGIT_TABLE + c1, 2); + memcpy(result + index + olength - 5, DIGIT_TABLE + d0, 2); + memcpy(result + index + olength - 7, DIGIT_TABLE + d1, 2); + i += 8; + } + uint32_t output2 = (uint32_t) output; + while (output2 >= 10000) { +#ifdef __clang__ // https://bugs.llvm.org/show_bug.cgi?id=38217 + const uint32_t c = output2 - 10000 * (output2 / 10000); +#else + const uint32_t c = output2 % 10000; +#endif + output2 /= 10000; + const uint32_t c0 = (c % 100) << 1; + const uint32_t c1 = (c / 100) << 1; + memcpy(result + index + olength - i - 1, DIGIT_TABLE + c0, 2); + memcpy(result + index + olength - i - 3, DIGIT_TABLE + c1, 2); + i += 4; + } + if (output2 >= 100) { + const uint32_t c = (output2 % 100) << 1; + output2 /= 100; + memcpy(result + index + olength - i - 1, DIGIT_TABLE + c, 2); + i += 2; + } + if (output2 >= 10) { + const uint32_t c = output2 << 1; + // We can't use memcpy here: the decimal dot goes between these two digits. + result[index + olength - i] = DIGIT_TABLE[c + 1]; + result[index] = DIGIT_TABLE[c]; + } else { + result[index] = (char) ('0' + output2); + } + + // Print decimal point if needed. + if (olength > 1) { + result[index + 1] = '.'; + index += olength + 1; + } else { + ++index; + } + + // Print the exponent. + result[index++] = 'E'; + int32_t exp = v.exponent + (int32_t) olength - 1; + if (exp < 0) { + result[index++] = '-'; + exp = -exp; + } + + if (exp >= 100) { + const int32_t c = exp % 10; + memcpy(result + index, DIGIT_TABLE + 2 * (exp / 10), 2); + result[index + 2] = (char) ('0' + c); + index += 3; + } else if (exp >= 10) { + memcpy(result + index, DIGIT_TABLE + 2 * exp, 2); + index += 2; + } else { + result[index++] = (char) ('0' + exp); + } + + return index; +} + +static inline bool d2d_small_int(const uint64_t ieeeMantissa, const uint32_t ieeeExponent, + floating_decimal_64* const v) { + const uint64_t m2 = (1ull << DOUBLE_MANTISSA_BITS) | ieeeMantissa; + const int32_t e2 = (int32_t) ieeeExponent - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS; + + if (e2 > 0) { + // f = m2 * 2^e2 >= 2^53 is an integer. + // Ignore this case for now. + return false; + } + + if (e2 < -52) { + // f < 1. + return false; + } + + // Since 2^52 <= m2 < 2^53 and 0 <= -e2 <= 52: 1 <= f = m2 / 2^-e2 < 2^53. + // Test if the lower -e2 bits of the significand are 0, i.e. whether the fraction is 0. + const uint64_t mask = (1ull << -e2) - 1; + const uint64_t fraction = m2 & mask; + if (fraction != 0) { + return false; + } + + // f is an integer in the range [1, 2^53). + // Note: mantissa might contain trailing (decimal) 0's. + // Note: since 2^53 < 10^16, there is no need to adjust decimalLength17(). + v->mantissa = m2 >> -e2; + v->exponent = 0; + return true; +} + +int d2s_buffered_n(double f, char* result) { + // Step 1: Decode the floating-point number, and unify normalized and subnormal cases. + const uint64_t bits = double_to_bits(f); + +#ifdef RYU_DEBUG + printf("IN="); + for (int32_t bit = 63; bit >= 0; --bit) { + printf("%d", (int) ((bits >> bit) & 1)); + } + printf("\n"); +#endif + + // Decode bits into sign, mantissa, and exponent. + const bool ieeeSign = ((bits >> (DOUBLE_MANTISSA_BITS + DOUBLE_EXPONENT_BITS)) & 1) != 0; + const uint64_t ieeeMantissa = bits & ((1ull << DOUBLE_MANTISSA_BITS) - 1); + const uint32_t ieeeExponent = (uint32_t) ((bits >> DOUBLE_MANTISSA_BITS) & ((1u << DOUBLE_EXPONENT_BITS) - 1)); + // Case distinction; exit early for the easy cases. + if (ieeeExponent == ((1u << DOUBLE_EXPONENT_BITS) - 1u) || (ieeeExponent == 0 && ieeeMantissa == 0)) { + return copy_special_str(result, ieeeSign, ieeeExponent, ieeeMantissa); + } + + floating_decimal_64 v; + const bool isSmallInt = d2d_small_int(ieeeMantissa, ieeeExponent, &v); + if (isSmallInt) { + // For small integers in the range [1, 2^53), v.mantissa might contain trailing (decimal) zeros. + // For scientific notation we need to move these zeros into the exponent. + // (This is not needed for fixed-point notation, so it might be beneficial to trim + // trailing zeros in to_chars only if needed - once fixed-point notation output is implemented.) + for (;;) { + const uint64_t q = div10(v.mantissa); + const uint32_t r = ((uint32_t) v.mantissa) - 10 * ((uint32_t) q); + if (r != 0) { + break; + } + v.mantissa = q; + ++v.exponent; + } + } else { + v = d2d(ieeeMantissa, ieeeExponent); + } + + return to_chars(v, ieeeSign, result); +} + +void d2s_buffered(double f, char* result) { + const int index = d2s_buffered_n(f, result); + + // Terminate the string. + result[index] = '\0'; +} + +char* d2s(double f) { + char* const result = (char*) malloc(25); + d2s_buffered(f, result); + return result; +} diff --git a/pjsonlib/src/third_party/ryu/ryu/d2s_full_table.h b/pjsonlib/src/third_party/ryu/ryu/d2s_full_table.h new file mode 100644 index 0000000..c8629ee --- /dev/null +++ b/pjsonlib/src/third_party/ryu/ryu/d2s_full_table.h @@ -0,0 +1,367 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_D2S_FULL_TABLE_H +#define RYU_D2S_FULL_TABLE_H + +// These tables are generated by PrintDoubleLookupTable. +#define DOUBLE_POW5_INV_BITCOUNT 125 +#define DOUBLE_POW5_BITCOUNT 125 + +#define DOUBLE_POW5_INV_TABLE_SIZE 342 +#define DOUBLE_POW5_TABLE_SIZE 326 + +static const uint64_t DOUBLE_POW5_INV_SPLIT[DOUBLE_POW5_INV_TABLE_SIZE][2] = { + { 1u, 2305843009213693952u }, { 11068046444225730970u, 1844674407370955161u }, + { 5165088340638674453u, 1475739525896764129u }, { 7821419487252849886u, 1180591620717411303u }, + { 8824922364862649494u, 1888946593147858085u }, { 7059937891890119595u, 1511157274518286468u }, + { 13026647942995916322u, 1208925819614629174u }, { 9774590264567735146u, 1934281311383406679u }, + { 11509021026396098440u, 1547425049106725343u }, { 16585914450600699399u, 1237940039285380274u }, + { 15469416676735388068u, 1980704062856608439u }, { 16064882156130220778u, 1584563250285286751u }, + { 9162556910162266299u, 1267650600228229401u }, { 7281393426775805432u, 2028240960365167042u }, + { 16893161185646375315u, 1622592768292133633u }, { 2446482504291369283u, 1298074214633706907u }, + { 7603720821608101175u, 2076918743413931051u }, { 2393627842544570617u, 1661534994731144841u }, + { 16672297533003297786u, 1329227995784915872u }, { 11918280793837635165u, 2126764793255865396u }, + { 5845275820328197809u, 1701411834604692317u }, { 15744267100488289217u, 1361129467683753853u }, + { 3054734472329800808u, 2177807148294006166u }, { 17201182836831481939u, 1742245718635204932u }, + { 6382248639981364905u, 1393796574908163946u }, { 2832900194486363201u, 2230074519853062314u }, + { 5955668970331000884u, 1784059615882449851u }, { 1075186361522890384u, 1427247692705959881u }, + { 12788344622662355584u, 2283596308329535809u }, { 13920024512871794791u, 1826877046663628647u }, + { 3757321980813615186u, 1461501637330902918u }, { 10384555214134712795u, 1169201309864722334u }, + { 5547241898389809503u, 1870722095783555735u }, { 4437793518711847602u, 1496577676626844588u }, + { 10928932444453298728u, 1197262141301475670u }, { 17486291911125277965u, 1915619426082361072u }, + { 6610335899416401726u, 1532495540865888858u }, { 12666966349016942027u, 1225996432692711086u }, + { 12888448528943286597u, 1961594292308337738u }, { 17689456452638449924u, 1569275433846670190u }, + { 14151565162110759939u, 1255420347077336152u }, { 7885109000409574610u, 2008672555323737844u }, + { 9997436015069570011u, 1606938044258990275u }, { 7997948812055656009u, 1285550435407192220u }, + { 12796718099289049614u, 2056880696651507552u }, { 2858676849947419045u, 1645504557321206042u }, + { 13354987924183666206u, 1316403645856964833u }, { 17678631863951955605u, 2106245833371143733u }, + { 3074859046935833515u, 1684996666696914987u }, { 13527933681774397782u, 1347997333357531989u }, + { 10576647446613305481u, 2156795733372051183u }, { 15840015586774465031u, 1725436586697640946u }, + { 8982663654677661702u, 1380349269358112757u }, { 18061610662226169046u, 2208558830972980411u }, + { 10759939715039024913u, 1766847064778384329u }, { 12297300586773130254u, 1413477651822707463u }, + { 15986332124095098083u, 2261564242916331941u }, { 9099716884534168143u, 1809251394333065553u }, + { 14658471137111155161u, 1447401115466452442u }, { 4348079280205103483u, 1157920892373161954u }, + { 14335624477811986218u, 1852673427797059126u }, { 7779150767507678651u, 1482138742237647301u }, + { 2533971799264232598u, 1185710993790117841u }, { 15122401323048503126u, 1897137590064188545u }, + { 12097921058438802501u, 1517710072051350836u }, { 5988988032009131678u, 1214168057641080669u }, + { 16961078480698431330u, 1942668892225729070u }, { 13568862784558745064u, 1554135113780583256u }, + { 7165741412905085728u, 1243308091024466605u }, { 11465186260648137165u, 1989292945639146568u }, + { 16550846638002330379u, 1591434356511317254u }, { 16930026125143774626u, 1273147485209053803u }, + { 4951948911778577463u, 2037035976334486086u }, { 272210314680951647u, 1629628781067588869u }, + { 3907117066486671641u, 1303703024854071095u }, { 6251387306378674625u, 2085924839766513752u }, + { 16069156289328670670u, 1668739871813211001u }, { 9165976216721026213u, 1334991897450568801u }, + { 7286864317269821294u, 2135987035920910082u }, { 16897537898041588005u, 1708789628736728065u }, + { 13518030318433270404u, 1367031702989382452u }, { 6871453250525591353u, 2187250724783011924u }, + { 9186511415162383406u, 1749800579826409539u }, { 11038557946871817048u, 1399840463861127631u }, + { 10282995085511086630u, 2239744742177804210u }, { 8226396068408869304u, 1791795793742243368u }, + { 13959814484210916090u, 1433436634993794694u }, { 11267656730511734774u, 2293498615990071511u }, + { 5324776569667477496u, 1834798892792057209u }, { 7949170070475892320u, 1467839114233645767u }, + { 17427382500606444826u, 1174271291386916613u }, { 5747719112518849781u, 1878834066219066582u }, + { 15666221734240810795u, 1503067252975253265u }, { 12532977387392648636u, 1202453802380202612u }, + { 5295368560860596524u, 1923926083808324180u }, { 4236294848688477220u, 1539140867046659344u }, + { 7078384693692692099u, 1231312693637327475u }, { 11325415509908307358u, 1970100309819723960u }, + { 9060332407926645887u, 1576080247855779168u }, { 14626963555825137356u, 1260864198284623334u }, + { 12335095245094488799u, 2017382717255397335u }, { 9868076196075591040u, 1613906173804317868u }, + { 15273158586344293478u, 1291124939043454294u }, { 13369007293925138595u, 2065799902469526871u }, + { 7005857020398200553u, 1652639921975621497u }, { 16672732060544291412u, 1322111937580497197u }, + { 11918976037903224966u, 2115379100128795516u }, { 5845832015580669650u, 1692303280103036413u }, + { 12055363241948356366u, 1353842624082429130u }, { 841837113407818570u, 2166148198531886609u }, + { 4362818505468165179u, 1732918558825509287u }, { 14558301248600263113u, 1386334847060407429u }, + { 12225235553534690011u, 2218135755296651887u }, { 2401490813343931363u, 1774508604237321510u }, + { 1921192650675145090u, 1419606883389857208u }, { 17831303500047873437u, 2271371013423771532u }, + { 6886345170554478103u, 1817096810739017226u }, { 1819727321701672159u, 1453677448591213781u }, + { 16213177116328979020u, 1162941958872971024u }, { 14873036941900635463u, 1860707134196753639u }, + { 15587778368262418694u, 1488565707357402911u }, { 8780873879868024632u, 1190852565885922329u }, + { 2981351763563108441u, 1905364105417475727u }, { 13453127855076217722u, 1524291284333980581u }, + { 7073153469319063855u, 1219433027467184465u }, { 11317045550910502167u, 1951092843947495144u }, + { 12742985255470312057u, 1560874275157996115u }, { 10194388204376249646u, 1248699420126396892u }, + { 1553625868034358140u, 1997919072202235028u }, { 8621598323911307159u, 1598335257761788022u }, + { 17965325103354776697u, 1278668206209430417u }, { 13987124906400001422u, 2045869129935088668u }, + { 121653480894270168u, 1636695303948070935u }, { 97322784715416134u, 1309356243158456748u }, + { 14913111714512307107u, 2094969989053530796u }, { 8241140556867935363u, 1675975991242824637u }, + { 17660958889720079260u, 1340780792994259709u }, { 17189487779326395846u, 2145249268790815535u }, + { 13751590223461116677u, 1716199415032652428u }, { 18379969808252713988u, 1372959532026121942u }, + { 14650556434236701088u, 2196735251241795108u }, { 652398703163629901u, 1757388200993436087u }, + { 11589965406756634890u, 1405910560794748869u }, { 7475898206584884855u, 2249456897271598191u }, + { 2291369750525997561u, 1799565517817278553u }, { 9211793429904618695u, 1439652414253822842u }, + { 18428218302589300235u, 2303443862806116547u }, { 7363877012587619542u, 1842755090244893238u }, + { 13269799239553916280u, 1474204072195914590u }, { 10615839391643133024u, 1179363257756731672u }, + { 2227947767661371545u, 1886981212410770676u }, { 16539753473096738529u, 1509584969928616540u }, + { 13231802778477390823u, 1207667975942893232u }, { 6413489186596184024u, 1932268761508629172u }, + { 16198837793502678189u, 1545815009206903337u }, { 5580372605318321905u, 1236652007365522670u }, + { 8928596168509315048u, 1978643211784836272u }, { 18210923379033183008u, 1582914569427869017u }, + { 7190041073742725760u, 1266331655542295214u }, { 436019273762630246u, 2026130648867672343u }, + { 7727513048493924843u, 1620904519094137874u }, { 9871359253537050198u, 1296723615275310299u }, + { 4726128361433549347u, 2074757784440496479u }, { 7470251503888749801u, 1659806227552397183u }, + { 13354898832594820487u, 1327844982041917746u }, { 13989140502667892133u, 2124551971267068394u }, + { 14880661216876224029u, 1699641577013654715u }, { 11904528973500979224u, 1359713261610923772u }, + { 4289851098633925465u, 2175541218577478036u }, { 18189276137874781665u, 1740432974861982428u }, + { 3483374466074094362u, 1392346379889585943u }, { 1884050330976640656u, 2227754207823337509u }, + { 5196589079523222848u, 1782203366258670007u }, { 15225317707844309248u, 1425762693006936005u }, + { 5913764258841343181u, 2281220308811097609u }, { 8420360221814984868u, 1824976247048878087u }, + { 17804334621677718864u, 1459980997639102469u }, { 17932816512084085415u, 1167984798111281975u }, + { 10245762345624985047u, 1868775676978051161u }, { 4507261061758077715u, 1495020541582440929u }, + { 7295157664148372495u, 1196016433265952743u }, { 7982903447895485668u, 1913626293225524389u }, + { 10075671573058298858u, 1530901034580419511u }, { 4371188443704728763u, 1224720827664335609u }, + { 14372599139411386667u, 1959553324262936974u }, { 15187428126271019657u, 1567642659410349579u }, + { 15839291315758726049u, 1254114127528279663u }, { 3206773216762499739u, 2006582604045247462u }, + { 13633465017635730761u, 1605266083236197969u }, { 14596120828850494932u, 1284212866588958375u }, + { 4907049252451240275u, 2054740586542333401u }, { 236290587219081897u, 1643792469233866721u }, + { 14946427728742906810u, 1315033975387093376u }, { 16535586736504830250u, 2104054360619349402u }, + { 5849771759720043554u, 1683243488495479522u }, { 15747863852001765813u, 1346594790796383617u }, + { 10439186904235184007u, 2154551665274213788u }, { 15730047152871967852u, 1723641332219371030u }, + { 12584037722297574282u, 1378913065775496824u }, { 9066413911450387881u, 2206260905240794919u }, + { 10942479943902220628u, 1765008724192635935u }, { 8753983955121776503u, 1412006979354108748u }, + { 10317025513452932081u, 2259211166966573997u }, { 874922781278525018u, 1807368933573259198u }, + { 8078635854506640661u, 1445895146858607358u }, { 13841606313089133175u, 1156716117486885886u }, + { 14767872471458792434u, 1850745787979017418u }, { 746251532941302978u, 1480596630383213935u }, + { 597001226353042382u, 1184477304306571148u }, { 15712597221132509104u, 1895163686890513836u }, + { 8880728962164096960u, 1516130949512411069u }, { 10793931984473187891u, 1212904759609928855u }, + { 17270291175157100626u, 1940647615375886168u }, { 2748186495899949531u, 1552518092300708935u }, + { 2198549196719959625u, 1242014473840567148u }, { 18275073973719576693u, 1987223158144907436u }, + { 10930710364233751031u, 1589778526515925949u }, { 12433917106128911148u, 1271822821212740759u }, + { 8826220925580526867u, 2034916513940385215u }, { 7060976740464421494u, 1627933211152308172u }, + { 16716827836597268165u, 1302346568921846537u }, { 11989529279587987770u, 2083754510274954460u }, + { 9591623423670390216u, 1667003608219963568u }, { 15051996368420132820u, 1333602886575970854u }, + { 13015147745246481542u, 2133764618521553367u }, { 3033420566713364587u, 1707011694817242694u }, + { 6116085268112601993u, 1365609355853794155u }, { 9785736428980163188u, 2184974969366070648u }, + { 15207286772667951197u, 1747979975492856518u }, { 1097782973908629988u, 1398383980394285215u }, + { 1756452758253807981u, 2237414368630856344u }, { 5094511021344956708u, 1789931494904685075u }, + { 4075608817075965366u, 1431945195923748060u }, { 6520974107321544586u, 2291112313477996896u }, + { 1527430471115325346u, 1832889850782397517u }, { 12289990821117991246u, 1466311880625918013u }, + { 17210690286378213644u, 1173049504500734410u }, { 9090360384495590213u, 1876879207201175057u }, + { 18340334751822203140u, 1501503365760940045u }, { 14672267801457762512u, 1201202692608752036u }, + { 16096930852848599373u, 1921924308174003258u }, { 1809498238053148529u, 1537539446539202607u }, + { 12515645034668249793u, 1230031557231362085u }, { 1578287981759648052u, 1968050491570179337u }, + { 12330676829633449412u, 1574440393256143469u }, { 13553890278448669853u, 1259552314604914775u }, + { 3239480371808320148u, 2015283703367863641u }, { 17348979556414297411u, 1612226962694290912u }, + { 6500486015647617283u, 1289781570155432730u }, { 10400777625036187652u, 2063650512248692368u }, + { 15699319729512770768u, 1650920409798953894u }, { 16248804598352126938u, 1320736327839163115u }, + { 7551343283653851484u, 2113178124542660985u }, { 6041074626923081187u, 1690542499634128788u }, + { 12211557331022285596u, 1352433999707303030u }, { 1091747655926105338u, 2163894399531684849u }, + { 4562746939482794594u, 1731115519625347879u }, { 7339546366328145998u, 1384892415700278303u }, + { 8053925371383123274u, 2215827865120445285u }, { 6443140297106498619u, 1772662292096356228u }, + { 12533209867169019542u, 1418129833677084982u }, { 5295740528502789974u, 2269007733883335972u }, + { 15304638867027962949u, 1815206187106668777u }, { 4865013464138549713u, 1452164949685335022u }, + { 14960057215536570740u, 1161731959748268017u }, { 9178696285890871890u, 1858771135597228828u }, + { 14721654658196518159u, 1487016908477783062u }, { 4398626097073393881u, 1189613526782226450u }, + { 7037801755317430209u, 1903381642851562320u }, { 5630241404253944167u, 1522705314281249856u }, + { 814844308661245011u, 1218164251424999885u }, { 1303750893857992017u, 1949062802279999816u }, + { 15800395974054034906u, 1559250241823999852u }, { 5261619149759407279u, 1247400193459199882u }, + { 12107939454356961969u, 1995840309534719811u }, { 5997002748743659252u, 1596672247627775849u }, + { 8486951013736837725u, 1277337798102220679u }, { 2511075177753209390u, 2043740476963553087u }, + { 13076906586428298482u, 1634992381570842469u }, { 14150874083884549109u, 1307993905256673975u }, + { 4194654460505726958u, 2092790248410678361u }, { 18113118827372222859u, 1674232198728542688u }, + { 3422448617672047318u, 1339385758982834151u }, { 16543964232501006678u, 2143017214372534641u }, + { 9545822571258895019u, 1714413771498027713u }, { 15015355686490936662u, 1371531017198422170u }, + { 5577825024675947042u, 2194449627517475473u }, { 11840957649224578280u, 1755559702013980378u }, + { 16851463748863483271u, 1404447761611184302u }, { 12204946739213931940u, 2247116418577894884u }, + { 13453306206113055875u, 1797693134862315907u }, { 3383947335406624054u, 1438154507889852726u }, + { 16482362180876329456u, 2301047212623764361u }, { 9496540929959153242u, 1840837770099011489u }, + { 11286581558709232917u, 1472670216079209191u }, { 5339916432225476010u, 1178136172863367353u }, + { 4854517476818851293u, 1885017876581387765u }, { 3883613981455081034u, 1508014301265110212u }, + { 14174937629389795797u, 1206411441012088169u }, { 11611853762797942306u, 1930258305619341071u }, + { 5600134195496443521u, 1544206644495472857u }, { 15548153800622885787u, 1235365315596378285u }, + { 6430302007287065643u, 1976584504954205257u }, { 16212288050055383484u, 1581267603963364205u }, + { 12969830440044306787u, 1265014083170691364u }, { 9683682259845159889u, 2024022533073106183u }, + { 15125643437359948558u, 1619218026458484946u }, { 8411165935146048523u, 1295374421166787957u }, + { 17147214310975587960u, 2072599073866860731u }, { 10028422634038560045u, 1658079259093488585u }, + { 8022738107230848036u, 1326463407274790868u }, { 9147032156827446534u, 2122341451639665389u }, + { 11006974540203867551u, 1697873161311732311u }, { 5116230817421183718u, 1358298529049385849u }, + { 15564666937357714594u, 2173277646479017358u }, { 1383687105660440706u, 1738622117183213887u }, + { 12174996128754083534u, 1390897693746571109u }, { 8411947361780802685u, 2225436309994513775u }, + { 6729557889424642148u, 1780349047995611020u }, { 5383646311539713719u, 1424279238396488816u }, + { 1235136468979721303u, 2278846781434382106u }, { 15745504434151418335u, 1823077425147505684u }, + { 16285752362063044992u, 1458461940118004547u }, { 5649904260166615347u, 1166769552094403638u }, + { 5350498001524674232u, 1866831283351045821u }, { 591049586477829062u, 1493465026680836657u }, + { 11540886113407994219u, 1194772021344669325u }, { 18673707743239135u, 1911635234151470921u }, + { 14772334225162232601u, 1529308187321176736u }, { 8128518565387875758u, 1223446549856941389u }, + { 1937583260394870242u, 1957514479771106223u }, { 8928764237799716840u, 1566011583816884978u }, + { 14521709019723594119u, 1252809267053507982u }, { 8477339172590109297u, 2004494827285612772u }, + { 17849917782297818407u, 1603595861828490217u }, { 6901236596354434079u, 1282876689462792174u }, + { 18420676183650915173u, 2052602703140467478u }, { 3668494502695001169u, 1642082162512373983u }, + { 10313493231639821582u, 1313665730009899186u }, { 9122891541139893884u, 2101865168015838698u }, + { 14677010862395735754u, 1681492134412670958u }, { 673562245690857633u, 1345193707530136767u } +}; + +static const uint64_t DOUBLE_POW5_SPLIT[DOUBLE_POW5_TABLE_SIZE][2] = { + { 0u, 1152921504606846976u }, { 0u, 1441151880758558720u }, + { 0u, 1801439850948198400u }, { 0u, 2251799813685248000u }, + { 0u, 1407374883553280000u }, { 0u, 1759218604441600000u }, + { 0u, 2199023255552000000u }, { 0u, 1374389534720000000u }, + { 0u, 1717986918400000000u }, { 0u, 2147483648000000000u }, + { 0u, 1342177280000000000u }, { 0u, 1677721600000000000u }, + { 0u, 2097152000000000000u }, { 0u, 1310720000000000000u }, + { 0u, 1638400000000000000u }, { 0u, 2048000000000000000u }, + { 0u, 1280000000000000000u }, { 0u, 1600000000000000000u }, + { 0u, 2000000000000000000u }, { 0u, 1250000000000000000u }, + { 0u, 1562500000000000000u }, { 0u, 1953125000000000000u }, + { 0u, 1220703125000000000u }, { 0u, 1525878906250000000u }, + { 0u, 1907348632812500000u }, { 0u, 1192092895507812500u }, + { 0u, 1490116119384765625u }, { 4611686018427387904u, 1862645149230957031u }, + { 9799832789158199296u, 1164153218269348144u }, { 12249790986447749120u, 1455191522836685180u }, + { 15312238733059686400u, 1818989403545856475u }, { 14528612397897220096u, 2273736754432320594u }, + { 13692068767113150464u, 1421085471520200371u }, { 12503399940464050176u, 1776356839400250464u }, + { 15629249925580062720u, 2220446049250313080u }, { 9768281203487539200u, 1387778780781445675u }, + { 7598665485932036096u, 1734723475976807094u }, { 274959820560269312u, 2168404344971008868u }, + { 9395221924704944128u, 1355252715606880542u }, { 2520655369026404352u, 1694065894508600678u }, + { 12374191248137781248u, 2117582368135750847u }, { 14651398557727195136u, 1323488980084844279u }, + { 13702562178731606016u, 1654361225106055349u }, { 3293144668132343808u, 2067951531382569187u }, + { 18199116482078572544u, 1292469707114105741u }, { 8913837547316051968u, 1615587133892632177u }, + { 15753982952572452864u, 2019483917365790221u }, { 12152082354571476992u, 1262177448353618888u }, + { 15190102943214346240u, 1577721810442023610u }, { 9764256642163156992u, 1972152263052529513u }, + { 17631875447420442880u, 1232595164407830945u }, { 8204786253993389888u, 1540743955509788682u }, + { 1032610780636961552u, 1925929944387235853u }, { 2951224747111794922u, 1203706215242022408u }, + { 3689030933889743652u, 1504632769052528010u }, { 13834660704216955373u, 1880790961315660012u }, + { 17870034976990372916u, 1175494350822287507u }, { 17725857702810578241u, 1469367938527859384u }, + { 3710578054803671186u, 1836709923159824231u }, { 26536550077201078u, 2295887403949780289u }, + { 11545800389866720434u, 1434929627468612680u }, { 14432250487333400542u, 1793662034335765850u }, + { 8816941072311974870u, 2242077542919707313u }, { 17039803216263454053u, 1401298464324817070u }, + { 12076381983474541759u, 1751623080406021338u }, { 5872105442488401391u, 2189528850507526673u }, + { 15199280947623720629u, 1368455531567204170u }, { 9775729147674874978u, 1710569414459005213u }, + { 16831347453020981627u, 2138211768073756516u }, { 1296220121283337709u, 1336382355046097823u }, + { 15455333206886335848u, 1670477943807622278u }, { 10095794471753144002u, 2088097429759527848u }, + { 6309871544845715001u, 1305060893599704905u }, { 12499025449484531656u, 1631326116999631131u }, + { 11012095793428276666u, 2039157646249538914u }, { 11494245889320060820u, 1274473528905961821u }, + { 532749306367912313u, 1593091911132452277u }, { 5277622651387278295u, 1991364888915565346u }, + { 7910200175544436838u, 1244603055572228341u }, { 14499436237857933952u, 1555753819465285426u }, + { 8900923260467641632u, 1944692274331606783u }, { 12480606065433357876u, 1215432671457254239u }, + { 10989071563364309441u, 1519290839321567799u }, { 9124653435777998898u, 1899113549151959749u }, + { 8008751406574943263u, 1186945968219974843u }, { 5399253239791291175u, 1483682460274968554u }, + { 15972438586593889776u, 1854603075343710692u }, { 759402079766405302u, 1159126922089819183u }, + { 14784310654990170340u, 1448908652612273978u }, { 9257016281882937117u, 1811135815765342473u }, + { 16182956370781059300u, 2263919769706678091u }, { 7808504722524468110u, 1414949856066673807u }, + { 5148944884728197234u, 1768687320083342259u }, { 1824495087482858639u, 2210859150104177824u }, + { 1140309429676786649u, 1381786968815111140u }, { 1425386787095983311u, 1727233711018888925u }, + { 6393419502297367043u, 2159042138773611156u }, { 13219259225790630210u, 1349401336733506972u }, + { 16524074032238287762u, 1686751670916883715u }, { 16043406521870471799u, 2108439588646104644u }, + { 803757039314269066u, 1317774742903815403u }, { 14839754354425000045u, 1647218428629769253u }, + { 4714634887749086344u, 2059023035787211567u }, { 9864175832484260821u, 1286889397367007229u }, + { 16941905809032713930u, 1608611746708759036u }, { 2730638187581340797u, 2010764683385948796u }, + { 10930020904093113806u, 1256727927116217997u }, { 18274212148543780162u, 1570909908895272496u }, + { 4396021111970173586u, 1963637386119090621u }, { 5053356204195052443u, 1227273366324431638u }, + { 15540067292098591362u, 1534091707905539547u }, { 14813398096695851299u, 1917614634881924434u }, + { 13870059828862294966u, 1198509146801202771u }, { 12725888767650480803u, 1498136433501503464u }, + { 15907360959563101004u, 1872670541876879330u }, { 14553786618154326031u, 1170419088673049581u }, + { 4357175217410743827u, 1463023860841311977u }, { 10058155040190817688u, 1828779826051639971u }, + { 7961007781811134206u, 2285974782564549964u }, { 14199001900486734687u, 1428734239102843727u }, + { 13137066357181030455u, 1785917798878554659u }, { 11809646928048900164u, 2232397248598193324u }, + { 16604401366885338411u, 1395248280373870827u }, { 16143815690179285109u, 1744060350467338534u }, + { 10956397575869330579u, 2180075438084173168u }, { 6847748484918331612u, 1362547148802608230u }, + { 17783057643002690323u, 1703183936003260287u }, { 17617136035325974999u, 2128979920004075359u }, + { 17928239049719816230u, 1330612450002547099u }, { 17798612793722382384u, 1663265562503183874u }, + { 13024893955298202172u, 2079081953128979843u }, { 5834715712847682405u, 1299426220705612402u }, + { 16516766677914378815u, 1624282775882015502u }, { 11422586310538197711u, 2030353469852519378u }, + { 11750802462513761473u, 1268970918657824611u }, { 10076817059714813937u, 1586213648322280764u }, + { 12596021324643517422u, 1982767060402850955u }, { 5566670318688504437u, 1239229412751781847u }, + { 2346651879933242642u, 1549036765939727309u }, { 7545000868343941206u, 1936295957424659136u }, + { 4715625542714963254u, 1210184973390411960u }, { 5894531928393704067u, 1512731216738014950u }, + { 16591536947346905892u, 1890914020922518687u }, { 17287239619732898039u, 1181821263076574179u }, + { 16997363506238734644u, 1477276578845717724u }, { 2799960309088866689u, 1846595723557147156u }, + { 10973347230035317489u, 1154122327223216972u }, { 13716684037544146861u, 1442652909029021215u }, + { 12534169028502795672u, 1803316136286276519u }, { 11056025267201106687u, 2254145170357845649u }, + { 18439230838069161439u, 1408840731473653530u }, { 13825666510731675991u, 1761050914342066913u }, + { 3447025083132431277u, 2201313642927583642u }, { 6766076695385157452u, 1375821026829739776u }, + { 8457595869231446815u, 1719776283537174720u }, { 10571994836539308519u, 2149720354421468400u }, + { 6607496772837067824u, 1343575221513417750u }, { 17482743002901110588u, 1679469026891772187u }, + { 17241742735199000331u, 2099336283614715234u }, { 15387775227926763111u, 1312085177259197021u }, + { 5399660979626290177u, 1640106471573996277u }, { 11361262242960250625u, 2050133089467495346u }, + { 11712474920277544544u, 1281333180917184591u }, { 10028907631919542777u, 1601666476146480739u }, + { 7924448521472040567u, 2002083095183100924u }, { 14176152362774801162u, 1251301934489438077u }, + { 3885132398186337741u, 1564127418111797597u }, { 9468101516160310080u, 1955159272639746996u }, + { 15140935484454969608u, 1221974545399841872u }, { 479425281859160394u, 1527468181749802341u }, + { 5210967620751338397u, 1909335227187252926u }, { 17091912818251750210u, 1193334516992033078u }, + { 12141518985959911954u, 1491668146240041348u }, { 15176898732449889943u, 1864585182800051685u }, + { 11791404716994875166u, 1165365739250032303u }, { 10127569877816206054u, 1456707174062540379u }, + { 8047776328842869663u, 1820883967578175474u }, { 836348374198811271u, 2276104959472719343u }, + { 7440246761515338900u, 1422565599670449589u }, { 13911994470321561530u, 1778206999588061986u }, + { 8166621051047176104u, 2222758749485077483u }, { 2798295147690791113u, 1389224218428173427u }, + { 17332926989895652603u, 1736530273035216783u }, { 17054472718942177850u, 2170662841294020979u }, + { 8353202440125167204u, 1356664275808763112u }, { 10441503050156459005u, 1695830344760953890u }, + { 3828506775840797949u, 2119787930951192363u }, { 86973725686804766u, 1324867456844495227u }, + { 13943775212390669669u, 1656084321055619033u }, { 3594660960206173375u, 2070105401319523792u }, + { 2246663100128858359u, 1293815875824702370u }, { 12031700912015848757u, 1617269844780877962u }, + { 5816254103165035138u, 2021587305976097453u }, { 5941001823691840913u, 1263492066235060908u }, + { 7426252279614801142u, 1579365082793826135u }, { 4671129331091113523u, 1974206353492282669u }, + { 5225298841145639904u, 1233878970932676668u }, { 6531623551432049880u, 1542348713665845835u }, + { 3552843420862674446u, 1927935892082307294u }, { 16055585193321335241u, 1204959932551442058u }, + { 10846109454796893243u, 1506199915689302573u }, { 18169322836923504458u, 1882749894611628216u }, + { 11355826773077190286u, 1176718684132267635u }, { 9583097447919099954u, 1470898355165334544u }, + { 11978871809898874942u, 1838622943956668180u }, { 14973589762373593678u, 2298278679945835225u }, + { 2440964573842414192u, 1436424174966147016u }, { 3051205717303017741u, 1795530218707683770u }, + { 13037379183483547984u, 2244412773384604712u }, { 8148361989677217490u, 1402757983365377945u }, + { 14797138505523909766u, 1753447479206722431u }, { 13884737113477499304u, 2191809349008403039u }, + { 15595489723564518921u, 1369880843130251899u }, { 14882676136028260747u, 1712351053912814874u }, + { 9379973133180550126u, 2140438817391018593u }, { 17391698254306313589u, 1337774260869386620u }, + { 3292878744173340370u, 1672217826086733276u }, { 4116098430216675462u, 2090272282608416595u }, + { 266718509671728212u, 1306420176630260372u }, { 333398137089660265u, 1633025220787825465u }, + { 5028433689789463235u, 2041281525984781831u }, { 10060300083759496378u, 1275800953740488644u }, + { 12575375104699370472u, 1594751192175610805u }, { 1884160825592049379u, 1993438990219513507u }, + { 17318501580490888525u, 1245899368887195941u }, { 7813068920331446945u, 1557374211108994927u }, + { 5154650131986920777u, 1946717763886243659u }, { 915813323278131534u, 1216698602428902287u }, + { 14979824709379828129u, 1520873253036127858u }, { 9501408849870009354u, 1901091566295159823u }, + { 12855909558809837702u, 1188182228934474889u }, { 2234828893230133415u, 1485227786168093612u }, + { 2793536116537666769u, 1856534732710117015u }, { 8663489100477123587u, 1160334207943823134u }, + { 1605989338741628675u, 1450417759929778918u }, { 11230858710281811652u, 1813022199912223647u }, + { 9426887369424876662u, 2266277749890279559u }, { 12809333633531629769u, 1416423593681424724u }, + { 16011667041914537212u, 1770529492101780905u }, { 6179525747111007803u, 2213161865127226132u }, + { 13085575628799155685u, 1383226165704516332u }, { 16356969535998944606u, 1729032707130645415u }, + { 15834525901571292854u, 2161290883913306769u }, { 2979049660840976177u, 1350806802445816731u }, + { 17558870131333383934u, 1688508503057270913u }, { 8113529608884566205u, 2110635628821588642u }, + { 9682642023980241782u, 1319147268013492901u }, { 16714988548402690132u, 1648934085016866126u }, + { 11670363648648586857u, 2061167606271082658u }, { 11905663298832754689u, 1288229753919426661u }, + { 1047021068258779650u, 1610287192399283327u }, { 15143834390605638274u, 2012858990499104158u }, + { 4853210475701136017u, 1258036869061940099u }, { 1454827076199032118u, 1572546086327425124u }, + { 1818533845248790147u, 1965682607909281405u }, { 3442426662494187794u, 1228551629943300878u }, + { 13526405364972510550u, 1535689537429126097u }, { 3072948650933474476u, 1919611921786407622u }, + { 15755650962115585259u, 1199757451116504763u }, { 15082877684217093670u, 1499696813895630954u }, + { 9630225068416591280u, 1874621017369538693u }, { 8324733676974063502u, 1171638135855961683u }, + { 5794231077790191473u, 1464547669819952104u }, { 7242788847237739342u, 1830684587274940130u }, + { 18276858095901949986u, 2288355734093675162u }, { 16034722328366106645u, 1430222333808546976u }, + { 1596658836748081690u, 1787777917260683721u }, { 6607509564362490017u, 2234722396575854651u }, + { 1823850468512862308u, 1396701497859909157u }, { 6891499104068465790u, 1745876872324886446u }, + { 17837745916940358045u, 2182346090406108057u }, { 4231062170446641922u, 1363966306503817536u }, + { 5288827713058302403u, 1704957883129771920u }, { 6611034641322878003u, 2131197353912214900u }, + { 13355268687681574560u, 1331998346195134312u }, { 16694085859601968200u, 1664997932743917890u }, + { 11644235287647684442u, 2081247415929897363u }, { 4971804045566108824u, 1300779634956185852u }, + { 6214755056957636030u, 1625974543695232315u }, { 3156757802769657134u, 2032468179619040394u }, + { 6584659645158423613u, 1270292612261900246u }, { 17454196593302805324u, 1587865765327375307u }, + { 17206059723201118751u, 1984832206659219134u }, { 6142101308573311315u, 1240520129162011959u }, + { 3065940617289251240u, 1550650161452514949u }, { 8444111790038951954u, 1938312701815643686u }, + { 665883850346957067u, 1211445438634777304u }, { 832354812933696334u, 1514306798293471630u }, + { 10263815553021896226u, 1892883497866839537u }, { 17944099766707154901u, 1183052186166774710u }, + { 13206752671529167818u, 1478815232708468388u }, { 16508440839411459773u, 1848519040885585485u }, + { 12623618533845856310u, 1155324400553490928u }, { 15779523167307320387u, 1444155500691863660u }, + { 1277659885424598868u, 1805194375864829576u }, { 1597074856780748586u, 2256492969831036970u }, + { 5609857803915355770u, 1410308106144398106u }, { 16235694291748970521u, 1762885132680497632u }, + { 1847873790976661535u, 2203606415850622041u }, { 12684136165428883219u, 1377254009906638775u }, + { 11243484188358716120u, 1721567512383298469u }, { 219297180166231438u, 2151959390479123087u }, + { 7054589765244976505u, 1344974619049451929u }, { 13429923224983608535u, 1681218273811814911u }, + { 12175718012802122765u, 2101522842264768639u }, { 14527352785642408584u, 1313451776415480399u }, + { 13547504963625622826u, 1641814720519350499u }, { 12322695186104640628u, 2052268400649188124u }, + { 16925056528170176201u, 1282667750405742577u }, { 7321262604930556539u, 1603334688007178222u }, + { 18374950293017971482u, 2004168360008972777u }, { 4566814905495150320u, 1252605225005607986u }, + { 14931890668723713708u, 1565756531257009982u }, { 9441491299049866327u, 1957195664071262478u }, + { 1289246043478778550u, 1223247290044539049u }, { 6223243572775861092u, 1529059112555673811u }, + { 3167368447542438461u, 1911323890694592264u }, { 1979605279714024038u, 1194577431684120165u }, + { 7086192618069917952u, 1493221789605150206u }, { 18081112809442173248u, 1866527237006437757u }, + { 13606538515115052232u, 1166579523129023598u }, { 7784801107039039482u, 1458224403911279498u }, + { 507629346944023544u, 1822780504889099373u }, { 5246222702107417334u, 2278475631111374216u }, + { 3278889188817135834u, 1424047269444608885u }, { 8710297504448807696u, 1780059086805761106u } +}; + +#endif // RYU_D2S_FULL_TABLE_H diff --git a/pjsonlib/src/third_party/ryu/ryu/d2s_intrinsics.h b/pjsonlib/src/third_party/ryu/ryu/d2s_intrinsics.h new file mode 100644 index 0000000..426ed8f --- /dev/null +++ b/pjsonlib/src/third_party/ryu/ryu/d2s_intrinsics.h @@ -0,0 +1,357 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_D2S_INTRINSICS_H +#define RYU_D2S_INTRINSICS_H + +#include +#include + +// Defines RYU_32_BIT_PLATFORM if applicable. +#include "ryu/common.h" + +// ABSL avoids uint128_t on Win32 even if __SIZEOF_INT128__ is defined. +// Let's do the same for now. +#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER) && !defined(RYU_ONLY_64_BIT_OPS) +#define HAS_UINT128 +#elif defined(_MSC_VER) && !defined(RYU_ONLY_64_BIT_OPS) && defined(_M_X64) +#define HAS_64_BIT_INTRINSICS +#endif + +#if defined(HAS_UINT128) +typedef __uint128_t uint128_t; +#endif + +#if defined(HAS_64_BIT_INTRINSICS) + +#include + +static inline uint64_t umul128(const uint64_t a, const uint64_t b, uint64_t* const productHi) { + return _umul128(a, b, productHi); +} + +// Returns the lower 64 bits of (hi*2^64 + lo) >> dist, with 0 < dist < 64. +static inline uint64_t shiftright128(const uint64_t lo, const uint64_t hi, const uint32_t dist) { + // For the __shiftright128 intrinsic, the shift value is always + // modulo 64. + // In the current implementation of the double-precision version + // of Ryu, the shift value is always < 64. (In the case + // RYU_OPTIMIZE_SIZE == 0, the shift value is in the range [49, 58]. + // Otherwise in the range [2, 59].) + // However, this function is now also called by s2d, which requires supporting + // the larger shift range (TODO: what is the actual range?). + // Check this here in case a future change requires larger shift + // values. In this case this function needs to be adjusted. + assert(dist < 64); + return __shiftright128(lo, hi, (unsigned char) dist); +} + +#else // defined(HAS_64_BIT_INTRINSICS) + +static inline uint64_t umul128(const uint64_t a, const uint64_t b, uint64_t* const productHi) { + // The casts here help MSVC to avoid calls to the __allmul library function. + const uint32_t aLo = (uint32_t)a; + const uint32_t aHi = (uint32_t)(a >> 32); + const uint32_t bLo = (uint32_t)b; + const uint32_t bHi = (uint32_t)(b >> 32); + + const uint64_t b00 = (uint64_t)aLo * bLo; + const uint64_t b01 = (uint64_t)aLo * bHi; + const uint64_t b10 = (uint64_t)aHi * bLo; + const uint64_t b11 = (uint64_t)aHi * bHi; + + const uint32_t b00Lo = (uint32_t)b00; + const uint32_t b00Hi = (uint32_t)(b00 >> 32); + + const uint64_t mid1 = b10 + b00Hi; + const uint32_t mid1Lo = (uint32_t)(mid1); + const uint32_t mid1Hi = (uint32_t)(mid1 >> 32); + + const uint64_t mid2 = b01 + mid1Lo; + const uint32_t mid2Lo = (uint32_t)(mid2); + const uint32_t mid2Hi = (uint32_t)(mid2 >> 32); + + const uint64_t pHi = b11 + mid1Hi + mid2Hi; + const uint64_t pLo = ((uint64_t)mid2Lo << 32) | b00Lo; + + *productHi = pHi; + return pLo; +} + +static inline uint64_t shiftright128(const uint64_t lo, const uint64_t hi, const uint32_t dist) { + // We don't need to handle the case dist >= 64 here (see above). + assert(dist < 64); + assert(dist > 0); + return (hi << (64 - dist)) | (lo >> dist); +} + +#endif // defined(HAS_64_BIT_INTRINSICS) + +#if defined(RYU_32_BIT_PLATFORM) + +// Returns the high 64 bits of the 128-bit product of a and b. +static inline uint64_t umulh(const uint64_t a, const uint64_t b) { + // Reuse the umul128 implementation. + // Optimizers will likely eliminate the instructions used to compute the + // low part of the product. + uint64_t hi; + umul128(a, b, &hi); + return hi; +} + +// On 32-bit platforms, compilers typically generate calls to library +// functions for 64-bit divisions, even if the divisor is a constant. +// +// E.g.: +// https://bugs.llvm.org/show_bug.cgi?id=37932 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=17958 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37443 +// +// The functions here perform division-by-constant using multiplications +// in the same way as 64-bit compilers would do. +// +// NB: +// The multipliers and shift values are the ones generated by clang x64 +// for expressions like x/5, x/10, etc. + +static inline uint64_t div5(const uint64_t x) { + return umulh(x, 0xCCCCCCCCCCCCCCCDu) >> 2; +} + +static inline uint64_t div10(const uint64_t x) { + return umulh(x, 0xCCCCCCCCCCCCCCCDu) >> 3; +} + +static inline uint64_t div100(const uint64_t x) { + return umulh(x >> 2, 0x28F5C28F5C28F5C3u) >> 2; +} + +static inline uint64_t div1e8(const uint64_t x) { + return umulh(x, 0xABCC77118461CEFDu) >> 26; +} + +static inline uint64_t div1e9(const uint64_t x) { + return umulh(x >> 9, 0x44B82FA09B5A53u) >> 11; +} + +static inline uint32_t mod1e9(const uint64_t x) { + // Avoid 64-bit math as much as possible. + // Returning (uint32_t) (x - 1000000000 * div1e9(x)) would + // perform 32x64-bit multiplication and 64-bit subtraction. + // x and 1000000000 * div1e9(x) are guaranteed to differ by + // less than 10^9, so their highest 32 bits must be identical, + // so we can truncate both sides to uint32_t before subtracting. + // We can also simplify (uint32_t) (1000000000 * div1e9(x)). + // We can truncate before multiplying instead of after, as multiplying + // the highest 32 bits of div1e9(x) can't affect the lowest 32 bits. + return ((uint32_t) x) - 1000000000 * ((uint32_t) div1e9(x)); +} + +#else // defined(RYU_32_BIT_PLATFORM) + +static inline uint64_t div5(const uint64_t x) { + return x / 5; +} + +static inline uint64_t div10(const uint64_t x) { + return x / 10; +} + +static inline uint64_t div100(const uint64_t x) { + return x / 100; +} + +static inline uint64_t div1e8(const uint64_t x) { + return x / 100000000; +} + +static inline uint64_t div1e9(const uint64_t x) { + return x / 1000000000; +} + +static inline uint32_t mod1e9(const uint64_t x) { + return (uint32_t) (x - 1000000000 * div1e9(x)); +} + +#endif // defined(RYU_32_BIT_PLATFORM) + +static inline uint32_t pow5Factor(uint64_t value) { + const uint64_t m_inv_5 = 14757395258967641293u; // 5 * m_inv_5 = 1 (mod 2^64) + const uint64_t n_div_5 = 3689348814741910323u; // #{ n | n = 0 (mod 2^64) } = 2^64 / 5 + uint32_t count = 0; + for (;;) { + assert(value != 0); + value *= m_inv_5; + if (value > n_div_5) + break; + ++count; + } + return count; +} + +// Returns true if value is divisible by 5^p. +static inline bool multipleOfPowerOf5(const uint64_t value, const uint32_t p) { + // I tried a case distinction on p, but there was no performance difference. + return pow5Factor(value) >= p; +} + +// Returns true if value is divisible by 2^p. +static inline bool multipleOfPowerOf2(const uint64_t value, const uint32_t p) { + assert(value != 0); + assert(p < 64); + // __builtin_ctzll doesn't appear to be faster here. + return (value & ((1ull << p) - 1)) == 0; +} + +// We need a 64x128-bit multiplication and a subsequent 128-bit shift. +// Multiplication: +// The 64-bit factor is variable and passed in, the 128-bit factor comes +// from a lookup table. We know that the 64-bit factor only has 55 +// significant bits (i.e., the 9 topmost bits are zeros). The 128-bit +// factor only has 124 significant bits (i.e., the 4 topmost bits are +// zeros). +// Shift: +// In principle, the multiplication result requires 55 + 124 = 179 bits to +// represent. However, we then shift this value to the right by j, which is +// at least j >= 115, so the result is guaranteed to fit into 179 - 115 = 64 +// bits. This means that we only need the topmost 64 significant bits of +// the 64x128-bit multiplication. +// +// There are several ways to do this: +// 1. Best case: the compiler exposes a 128-bit type. +// We perform two 64x64-bit multiplications, add the higher 64 bits of the +// lower result to the higher result, and shift by j - 64 bits. +// +// We explicitly cast from 64-bit to 128-bit, so the compiler can tell +// that these are only 64-bit inputs, and can map these to the best +// possible sequence of assembly instructions. +// x64 machines happen to have matching assembly instructions for +// 64x64-bit multiplications and 128-bit shifts. +// +// 2. Second best case: the compiler exposes intrinsics for the x64 assembly +// instructions mentioned in 1. +// +// 3. We only have 64x64 bit instructions that return the lower 64 bits of +// the result, i.e., we have to use plain C. +// Our inputs are less than the full width, so we have three options: +// a. Ignore this fact and just implement the intrinsics manually. +// b. Split both into 31-bit pieces, which guarantees no internal overflow, +// but requires extra work upfront (unless we change the lookup table). +// c. Split only the first factor into 31-bit pieces, which also guarantees +// no internal overflow, but requires extra work since the intermediate +// results are not perfectly aligned. +#if defined(HAS_UINT128) + +// Best case: use 128-bit type. +static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) { + const uint128_t b0 = ((uint128_t) m) * mul[0]; + const uint128_t b2 = ((uint128_t) m) * mul[1]; + return (uint64_t) (((b0 >> 64) + b2) >> (j - 64)); +} + +static inline uint64_t mulShiftAll64(const uint64_t m, const uint64_t* const mul, const int32_t j, + uint64_t* const vp, uint64_t* const vm, const uint32_t mmShift) { +// m <<= 2; +// uint128_t b0 = ((uint128_t) m) * mul[0]; // 0 +// uint128_t b2 = ((uint128_t) m) * mul[1]; // 64 +// +// uint128_t hi = (b0 >> 64) + b2; +// uint128_t lo = b0 & 0xffffffffffffffffull; +// uint128_t factor = (((uint128_t) mul[1]) << 64) + mul[0]; +// uint128_t vpLo = lo + (factor << 1); +// *vp = (uint64_t) ((hi + (vpLo >> 64)) >> (j - 64)); +// uint128_t vmLo = lo - (factor << mmShift); +// *vm = (uint64_t) ((hi + (vmLo >> 64) - (((uint128_t) 1ull) << 64)) >> (j - 64)); +// return (uint64_t) (hi >> (j - 64)); + *vp = mulShift64(4 * m + 2, mul, j); + *vm = mulShift64(4 * m - 1 - mmShift, mul, j); + return mulShift64(4 * m, mul, j); +} + +#elif defined(HAS_64_BIT_INTRINSICS) + +static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) { + // m is maximum 55 bits + uint64_t high1; // 128 + const uint64_t low1 = umul128(m, mul[1], &high1); // 64 + uint64_t high0; // 64 + umul128(m, mul[0], &high0); // 0 + const uint64_t sum = high0 + low1; + if (sum < high0) { + ++high1; // overflow into high1 + } + return shiftright128(sum, high1, j - 64); +} + +static inline uint64_t mulShiftAll64(const uint64_t m, const uint64_t* const mul, const int32_t j, + uint64_t* const vp, uint64_t* const vm, const uint32_t mmShift) { + *vp = mulShift64(4 * m + 2, mul, j); + *vm = mulShift64(4 * m - 1 - mmShift, mul, j); + return mulShift64(4 * m, mul, j); +} + +#else // !defined(HAS_UINT128) && !defined(HAS_64_BIT_INTRINSICS) + +static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) { + // m is maximum 55 bits + uint64_t high1; // 128 + const uint64_t low1 = umul128(m, mul[1], &high1); // 64 + uint64_t high0; // 64 + umul128(m, mul[0], &high0); // 0 + const uint64_t sum = high0 + low1; + if (sum < high0) { + ++high1; // overflow into high1 + } + return shiftright128(sum, high1, j - 64); +} + +// This is faster if we don't have a 64x64->128-bit multiplication. +static inline uint64_t mulShiftAll64(uint64_t m, const uint64_t* const mul, const int32_t j, + uint64_t* const vp, uint64_t* const vm, const uint32_t mmShift) { + m <<= 1; + // m is maximum 55 bits + uint64_t tmp; + const uint64_t lo = umul128(m, mul[0], &tmp); + uint64_t hi; + const uint64_t mid = tmp + umul128(m, mul[1], &hi); + hi += mid < tmp; // overflow into hi + + const uint64_t lo2 = lo + mul[0]; + const uint64_t mid2 = mid + mul[1] + (lo2 < lo); + const uint64_t hi2 = hi + (mid2 < mid); + *vp = shiftright128(mid2, hi2, (uint32_t) (j - 64 - 1)); + + if (mmShift == 1) { + const uint64_t lo3 = lo - mul[0]; + const uint64_t mid3 = mid - mul[1] - (lo3 > lo); + const uint64_t hi3 = hi - (mid3 > mid); + *vm = shiftright128(mid3, hi3, (uint32_t) (j - 64 - 1)); + } else { + const uint64_t lo3 = lo + lo; + const uint64_t mid3 = mid + mid + (lo3 < lo); + const uint64_t hi3 = hi + hi + (mid3 < mid); + const uint64_t lo4 = lo3 - mul[0]; + const uint64_t mid4 = mid3 - mul[1] - (lo4 > lo3); + const uint64_t hi4 = hi3 - (mid4 > mid3); + *vm = shiftright128(mid4, hi4, (uint32_t) (j - 64)); + } + + return shiftright128(mid, hi, (uint32_t) (j - 64 - 1)); +} + +#endif // HAS_64_BIT_INTRINSICS + +#endif // RYU_D2S_INTRINSICS_H diff --git a/pjsonlib/src/third_party/ryu/ryu/digit_table.h b/pjsonlib/src/third_party/ryu/ryu/digit_table.h new file mode 100644 index 0000000..02219bc --- /dev/null +++ b/pjsonlib/src/third_party/ryu/ryu/digit_table.h @@ -0,0 +1,35 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_DIGIT_TABLE_H +#define RYU_DIGIT_TABLE_H + +// A table of all two-digit numbers. This is used to speed up decimal digit +// generation by copying pairs of digits into the final output. +static const char DIGIT_TABLE[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' +}; + +#endif // RYU_DIGIT_TABLE_H diff --git a/pjsonlib/src/third_party/ryu/ryu/ryu.h b/pjsonlib/src/third_party/ryu/ryu/ryu.h new file mode 100644 index 0000000..558822a --- /dev/null +++ b/pjsonlib/src/third_party/ryu/ryu/ryu.h @@ -0,0 +1,46 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_H +#define RYU_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +int d2s_buffered_n(double f, char* result); +void d2s_buffered(double f, char* result); +char* d2s(double f); + +int f2s_buffered_n(float f, char* result); +void f2s_buffered(float f, char* result); +char* f2s(float f); + +int d2fixed_buffered_n(double d, uint32_t precision, char* result); +void d2fixed_buffered(double d, uint32_t precision, char* result); +char* d2fixed(double d, uint32_t precision); + +int d2exp_buffered_n(double d, uint32_t precision, char* result); +void d2exp_buffered(double d, uint32_t precision, char* result); +char* d2exp(double d, uint32_t precision); + +#ifdef __cplusplus +} +#endif + +#endif // RYU_H diff --git a/pjsonlib/src/third_party/srell/LICENSE.txt b/pjsonlib/src/third_party/srell/LICENSE.txt new file mode 100644 index 0000000..e30b3bd --- /dev/null +++ b/pjsonlib/src/third_party/srell/LICENSE.txt @@ -0,0 +1,32 @@ +/***************************************************************************** +** +** SRELL (std::regex-like library) version 2026.06 +** +** Copyright (c) 2012-2026, Nozomu Katoo. 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. +** +** 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. +** +****************************************************************************** +*/ + diff --git a/pjsonlib/src/third_party/srell/VERSION.md b/pjsonlib/src/third_party/srell/VERSION.md new file mode 100644 index 0000000..2af24f9 --- /dev/null +++ b/pjsonlib/src/third_party/srell/VERSION.md @@ -0,0 +1,12 @@ + + + +# SRELL 2026.06 + +- Upstream: https://github.com/upa-url/srell +- Commit: `766f5c8aca5a524ed88d06b653c77ebadc13cc04` +- License: BSD-2-Clause; see `LICENSE.txt` +- Vendored files: `srell.hpp`, `srell_ucfdata2.h`, `srell_updata3.h` + +These are unmodified upstream release files used only by the private JSON Schema +regular-expression adapter. Update all three generated/data files together. diff --git a/pjsonlib/src/third_party/srell/srell.hpp b/pjsonlib/src/third_party/srell/srell.hpp new file mode 100644 index 0000000..b630d6e --- /dev/null +++ b/pjsonlib/src/third_party/srell/srell.hpp @@ -0,0 +1,11752 @@ +/***************************************************************************** +** +** SRELL (std::regex-like library) version 2026.06 +** +** Copyright (c) 2012-2026, Nozomu Katoo. 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. +** +** 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. +** +****************************************************************************** +*/ + +#ifndef SRELL_HPP_ +#define SRELL_HPP_ 202606 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(SIZE_MAX) +// MSVC has SIZE_MAX also in limits.h. +#include +// Skips for MSVC 2005 and 2008 which do not have stdint.h. +#endif + +#if !defined(UINT_MAX) || (UINT_MAX < 0xFFFFFFFFL) +#error UINT_MAX >= 0xFFFFFFFF required. +#endif + +#if !defined(SIZE_MAX) || (SIZE_MAX < 0xFFFFFFFFL) +#error SIZE_MAX >= 0xFFFFFFFF required. +#endif + +#if !defined(SRELL_NO_UNISTACK) && (defined(__cplusplus) && (__cplusplus >= 201103L)) || (defined(_MSC_VER) && (_MSC_VER >= 1900)) +#include +#define SRELL_HAS_TYPE_TRAITS +#endif + +#if !defined(SRELL_NO_SIMD) +#if (defined(_M_X64) && !defined(_M_ARM64EC)) || defined(__x86_64__) || defined(_M_IX86) || defined(__i386__) +#if defined(__SSE4_2__) + #define SRELL_HAS_SSE42 1 +#elif defined(__clang__) + #if ((__clang_major__ + 0) >= 4) || (((__clang_major__ + 0) == 3) && ((__clang_minor__ + 0) >= 8)) + #define SRELL_HAS_SSE42 1 + #endif +#elif defined(__GNUC__) + #if ((__GNUC__ >= 5) || ((__GNUC__ == 4) && ((__GNUC_MINOR__ + 0) >= 9))) + #define SRELL_HAS_SSE42 1 + #endif +#elif defined(_MSC_VER) && (_MSC_VER >= 1500) + #include + #define SRELL_HAS_SSE42 2 +#endif // sse 4.2. +#endif // x86/x64. +#endif + +#define SRELL_AT_SSE42 +#if defined(SRELL_HAS_SSE42) && (SRELL_HAS_SSE42 == 1) + #include + #if !defined(__SSE4_2__) + #undef SRELL_AT_SSE42 + #define SRELL_AT_SSE42 __attribute__((target("sse4.2"))) + #endif +#endif + +// The following SRELL_NO_* macros would be useful for reducing the +// size of an executable file by turning off some feature(s). + +#ifdef SRELL_NO_UNICODE_DATA + +// Prevents Unicode case folding data used for icase (case-insensitive) +// matching from being output into the executable file. In this case, +// only the ASCII characters are case-folded when icase matching is +// performed (i.e., [A-Z] -> [a-z] only). +#define SRELL_NO_UNICODE_ICASE + +// Prevents Unicode property data from being output into the executable +// file. In this case, \p{...} and \P{...} become unavailable. +#define SRELL_NO_UNICODE_PROPERTY +#endif + +// This macro might be removed in the future. +#ifdef SRELL_V1_COMPATIBLE +#define SRELL_NO_UNICODE_PROPERTY +#define SRELL_NO_NAMEDCAPTURE +#define SRELL_NO_SINGLELINE +//#define SRELL_FIXEDWIDTHLOOKBEHIND +// Since version 4.019, SRELL highly depends on the variable-length +// lookbehind feature. Uncommenting this line is not recommended. +#endif + +namespace srell +{ + +#if defined(_MSC_VER) +#define SRELL_NO_VCWARNING(n) \ + __pragma(warning(push)) \ + __pragma(warning(disable:n)) +#define SRELL_NO_VCWARNING_END __pragma(warning(pop)) +#else +#define SRELL_NO_VCWARNING(n) +#define SRELL_NO_VCWARNING_END +#endif + +#if defined(__cpp_rvalue_references) && (!defined(_MSC_VER) || (_MSC_VER >= 1900)) +#define SRELL_NOEXCEPT noexcept +#else +#define SRELL_NOEXCEPT +#endif + +#if defined(__cpp_constexpr) +#define SRELL_STACON static constexpr +#else +#define SRELL_STACON static const +#endif + +#if defined(__cpp_if_constexpr) +#define SRELL_IFCE constexpr +#else +#define SRELL_IFCE +#endif + +// ["rei_type.h" ... + + namespace re_detail + { + +#if defined(__cpp_unicode_characters) + typedef char32_t ui_l32; // uint_least32. +#else + typedef unsigned int ui_l32; +#endif + + typedef std::size_t cntr_type; + + } // namespace re_detail + +// ... "rei_type.h"] +// ["regex_constants.h" ... + + namespace regex_constants + { + enum syntax_option_type + { + icase = 1 << 1, + nosubs = 1 << 2, + optimize = 1 << 3, + collate = 0, + ECMAScript = 1 << 0, + multiline = 1 << 4, + basic = 0, + extended = 0, + awk = 0, + grep = 0, + egrep = 0, + + // SRELL's extensions. + sticky = 1 << 5, // == match_continuous below. + dotall = 1 << 6, // singleline. + unicodesets = 1 << 7, + vmode = unicodesets, + quiet = 1 << 8, + + // For internal use. + back_ = 1 << 9, + pflagsmask_ = (1 << 9) - 1 + }; + + inline syntax_option_type operator&(const syntax_option_type left, const syntax_option_type right) + { + return static_cast(static_cast(left) & static_cast(right)); + } + inline syntax_option_type operator|(const syntax_option_type left, const syntax_option_type right) + { + return static_cast(static_cast(left) | static_cast(right)); + } + inline syntax_option_type operator^(const syntax_option_type left, const syntax_option_type right) + { + return static_cast(static_cast(left) ^ static_cast(right)); + } + inline syntax_option_type operator~(const syntax_option_type b) + { + return static_cast(~static_cast(b)); + } + inline syntax_option_type &operator&=(syntax_option_type &left, const syntax_option_type right) + { + left = left & right; + return left; + } + inline syntax_option_type &operator|=(syntax_option_type &left, const syntax_option_type right) + { + left = left | right; + return left; + } + inline syntax_option_type &operator^=(syntax_option_type &left, const syntax_option_type right) + { + left = left ^ right; + return left; + } + } + // namespace regex_constants + + namespace regex_constants + { + enum match_flag_type + { + match_default = 0, + match_not_bol = 1 << 0, + match_not_eol = 1 << 1, + match_not_bow = 1 << 2, + match_not_eow = 1 << 3, + match_any = 0, + match_not_null = 1 << 4, + match_continuous = 1 << 5, // == sticky above. + match_prev_avail = 1 << 6, + + format_default = 0, + format_sed = 0, + format_no_copy = 1 << 7, + format_first_only = 1 << 8, + + // For internal use. + match_match_ = 1 << 9 + }; + + inline match_flag_type operator&(const match_flag_type left, const match_flag_type right) + { + return static_cast(static_cast(left) & static_cast(right)); + } + inline match_flag_type operator|(const match_flag_type left, const match_flag_type right) + { + return static_cast(static_cast(left) | static_cast(right)); + } + inline match_flag_type operator^(const match_flag_type left, const match_flag_type right) + { + return static_cast(static_cast(left) ^ static_cast(right)); + } + inline match_flag_type operator~(const match_flag_type b) + { + return static_cast(~static_cast(b)); + } + inline match_flag_type &operator&=(match_flag_type &left, const match_flag_type right) + { + left = left & right; + return left; + } + inline match_flag_type &operator|=(match_flag_type &left, const match_flag_type right) + { + left = left | right; + return left; + } + inline match_flag_type &operator^=(match_flag_type &left, const match_flag_type right) + { + left = left ^ right; + return left; + } + } + // namespace regex_constants + + namespace regex_constants + { + typedef unsigned int error_type; + +#define SRELLTMP_ET(n,v) SRELL_STACON error_type n = v; + + SRELLTMP_ET(error_collate, 100) + SRELLTMP_ET(error_ctype, 101) + SRELLTMP_ET(error_escape, 102) + SRELLTMP_ET(error_backref, 103) + SRELLTMP_ET(error_brack, 104) + SRELLTMP_ET(error_paren, 105) + SRELLTMP_ET(error_brace, 106) + SRELLTMP_ET(error_badbrace, 107) + SRELLTMP_ET(error_range, 108) + SRELLTMP_ET(error_space, 109) + SRELLTMP_ET(error_badrepeat, 110) + SRELLTMP_ET(error_complexity, 111) + SRELLTMP_ET(error_stack, 112) + + // SRELL's extensions. + SRELLTMP_ET(error_utf8, 113) + // The expression contained an invalid UTF-8 sequence. + + SRELLTMP_ET(error_property, 114) + // The expression contained an invalid Unicode property name or value. + + SRELLTMP_ET(error_noescape, 115) + // (Only in v-mode) ( ) [ ] { } / - \ | need to be escaped in a character class. + + SRELLTMP_ET(error_operator, 116) + // (Only in v-mode) A character class contained a reserved double punctuation + // operator or different types of operators at the same level, such as [ab--cd]. + + SRELLTMP_ET(error_complement, 117) + // (Only in v-mode) \P or a negated character class contained a property of strings. + + SRELLTMP_ET(error_modifier, 118) + // A specific flag modifier appeared more then once, or the un-bounded form + // ((?ism-ism)) appeared at a position other than the beginning of the expression. + + SRELLTMP_ET(error_first_, error_collate) + SRELLTMP_ET(error_last_, error_modifier) + +#if defined(SRELL_FIXEDWIDTHLOOKBEHIND) + SRELLTMP_ET(error_lookbehind, 200) +#endif + + SRELLTMP_ET(error_internal, 999) + +#undef SRELLTMP_ET + + } + // namespace regex_constants + +// ... "regex_constants.h"] +// ["rei_constants.h" ... + + namespace re_detail + { + enum re_state_type + { + st_character, // 0x00 + st_characteri, // 0x01 + st_character_class, // 0x02 + + st_epsilon, // 0x03 + + st_check_counter, // 0x04 + st_increment_counter, // 0x05 + st_decrement_counter, // 0x06 + st_save_and_reset_counter, // 0x07 + st_restore_counter, // 0x08 + + st_roundbracket_open, // 0x09 + st_roundbracket_pop, // 0x0a + st_roundbracket_close, // 0x0b + + st_repeat_in_push, // 0x0c + st_repeat_in_pop, // 0x0d + st_check_0_width_repeat, // 0x0e + + st_backreference, // 0x0f + + st_lookaround_open, // 0x10 + + st_lookaround_pop, // 0x11 + + st_bol, // 0x12 + st_eol, // 0x13 + st_boundary, // 0x14 + + st_success, // 0x15 + +#if defined(SRELLTEST_NEXTPOS_OPT) + st_move_nextpos, // 0x16 +#endif + + st_lookaround_close = st_success, + st_zero_width_boundary = st_lookaround_open + }; + // re_state_type + +#define SRELLTMP_UIL32(n,v) SRELL_STACON ui_l32 n = v; + + namespace constants + { + SRELLTMP_UIL32(unicode_max_codepoint, 0x10ffff) + SRELLTMP_UIL32(invalid_u32value, static_cast(-1)) + SRELLTMP_UIL32(max_u32value, static_cast(-2)) + SRELLTMP_UIL32(ccstr_empty, static_cast(-1)) + SRELLTMP_UIL32(infinity, static_cast(-1)) + SRELLTMP_UIL32(errshift, 24) + } + // constants + + namespace masks + { + SRELLTMP_UIL32(asc_icase, 0x20) + SRELLTMP_UIL32(cfolded, 0x200000) // 1 << 21. + SRELLTMP_UIL32(cf_char, 0x1fffff) + SRELLTMP_UIL32(errmask, 0xff000000) + SRELLTMP_UIL32(somask, 0xffffff) + } + // masks + + namespace sflags + { + SRELLTMP_UIL32(is_not, 1) + SRELLTMP_UIL32(icase, 1) + SRELLTMP_UIL32(multiline, 1) + SRELLTMP_UIL32(backrefno_unresolved, 1 << 1) + SRELLTMP_UIL32(hooking, 1 << 2) + SRELLTMP_UIL32(hookedlast, 1 << 3) + SRELLTMP_UIL32(byn2, 1 << 4) + SRELLTMP_UIL32(clrn2, 1 << 5) + } + // sflags + + namespace meta_char + { + SRELLTMP_UIL32(mc_exclam, 0x21) // '!' + SRELLTMP_UIL32(mc_sharp , 0x23) // '#' + SRELLTMP_UIL32(mc_dollar, 0x24) // '$' + SRELLTMP_UIL32(mc_rbraop, 0x28) // '(' + SRELLTMP_UIL32(mc_rbracl, 0x29) // ')' + SRELLTMP_UIL32(mc_astrsk, 0x2a) // '*' + SRELLTMP_UIL32(mc_plus , 0x2b) // '+' + SRELLTMP_UIL32(mc_comma , 0x2c) // ',' + SRELLTMP_UIL32(mc_minus , 0x2d) // '-' + SRELLTMP_UIL32(mc_period, 0x2e) // '.' + SRELLTMP_UIL32(mc_colon , 0x3a) // ':' + SRELLTMP_UIL32(mc_lt, 0x3c) // '<' + SRELLTMP_UIL32(mc_eq, 0x3d) // '=' + SRELLTMP_UIL32(mc_gt, 0x3e) // '>' + SRELLTMP_UIL32(mc_query , 0x3f) // '?' + SRELLTMP_UIL32(mc_sbraop, 0x5b) // '[' + SRELLTMP_UIL32(mc_escape, 0x5c) // '\\' + SRELLTMP_UIL32(mc_sbracl, 0x5d) // ']' + SRELLTMP_UIL32(mc_caret , 0x5e) // '^' + SRELLTMP_UIL32(mc_cbraop, 0x7b) // '{' + SRELLTMP_UIL32(mc_bar , 0x7c) // '|' + SRELLTMP_UIL32(mc_cbracl, 0x7d) // '}' + } + // meta_char + + namespace char_ctrl + { + SRELLTMP_UIL32(cc_nul , 0x00) // '\0' //0x00:NUL + SRELLTMP_UIL32(cc_bs , 0x08) // '\b' //0x08:BS + SRELLTMP_UIL32(cc_htab, 0x09) // '\t' //0x09:HT + SRELLTMP_UIL32(cc_nl , 0x0a) // '\n' //0x0a:LF + SRELLTMP_UIL32(cc_vtab, 0x0b) // '\v' //0x0b:VT + SRELLTMP_UIL32(cc_ff , 0x0c) // '\f' //0x0c:FF + SRELLTMP_UIL32(cc_cr , 0x0d) // '\r' //0x0d:CR + } + // char_ctrl + + namespace char_alnum + { + SRELLTMP_UIL32(ch_0, 0x30) // '0' + SRELLTMP_UIL32(ch_1, 0x31) // '1' + SRELLTMP_UIL32(ch_9, 0x39) // '9' + SRELLTMP_UIL32(ch_A, 0x41) // 'A' + SRELLTMP_UIL32(ch_B, 0x42) // 'B' + SRELLTMP_UIL32(ch_D, 0x44) // 'D' + SRELLTMP_UIL32(ch_P, 0x50) // 'P' + SRELLTMP_UIL32(ch_S, 0x53) // 'S' + SRELLTMP_UIL32(ch_W, 0x57) // 'W' + SRELLTMP_UIL32(ch_Z, 0x5a) // 'Z' + SRELLTMP_UIL32(ch_a, 0x61) // 'a' + SRELLTMP_UIL32(ch_b, 0x62) // 'b' + SRELLTMP_UIL32(ch_c, 0x63) // 'c' + SRELLTMP_UIL32(ch_d, 0x64) // 'd' + SRELLTMP_UIL32(ch_f, 0x66) // 'f' + SRELLTMP_UIL32(ch_i, 0x69) // 'i' + SRELLTMP_UIL32(ch_k, 0x6b) // 'k' + SRELLTMP_UIL32(ch_m, 0x6d) // 'm' + SRELLTMP_UIL32(ch_n, 0x6e) // 'n' + SRELLTMP_UIL32(ch_p, 0x70) // 'p' + SRELLTMP_UIL32(ch_q, 0x71) // 'q' + SRELLTMP_UIL32(ch_r, 0x72) // 'r' + SRELLTMP_UIL32(ch_s, 0x73) // 's' + SRELLTMP_UIL32(ch_t, 0x74) // 't' + SRELLTMP_UIL32(ch_u, 0x75) // 'u' + SRELLTMP_UIL32(ch_v, 0x76) // 'v' + SRELLTMP_UIL32(ch_w, 0x77) // 'w' + SRELLTMP_UIL32(ch_x, 0x78) // 'x' + SRELLTMP_UIL32(ch_y, 0x79) // 'y' + SRELLTMP_UIL32(ch_z, 0x7a) // 'z' + } + // char_alnum + + namespace char_other + { + SRELLTMP_UIL32(co_perc , 0x25) // '%' + SRELLTMP_UIL32(co_amp , 0x26) // '&' + SRELLTMP_UIL32(co_apos , 0x27) // '\'' + SRELLTMP_UIL32(co_slash, 0x2f) // '/' + SRELLTMP_UIL32(co_smcln, 0x3b) // ';' + SRELLTMP_UIL32(co_atmrk, 0x40) // '@' + SRELLTMP_UIL32(co_ll , 0x5f) // '_' + SRELLTMP_UIL32(co_grav , 0x60) // '`' + SRELLTMP_UIL32(co_tilde, 0x7e) // '~' + } + // char_other + + namespace epsilon_type // Used only in the pattern compiler. + { + SRELLTMP_UIL32(et_dfastrsk, 0x40) // '@' + SRELLTMP_UIL32(et_ccastrsk, 0x2a) // '*' + SRELLTMP_UIL32(et_alt , 0x7c) // '|' + SRELLTMP_UIL32(et_ncgopen , 0x3a) // ':' + SRELLTMP_UIL32(et_ncgclose, 0x3b) // ';' + SRELLTMP_UIL32(et_jmpinlp , 0x2b) // '+' + SRELLTMP_UIL32(et_brnchend, 0x2f) // '/' + SRELLTMP_UIL32(et_fmrbckrf, 0x5c) // '\\' + SRELLTMP_UIL32(et_bo1fmrbr, 0x31) // '1' + SRELLTMP_UIL32(et_bo2fmrbr, 0x32) // '2' + SRELLTMP_UIL32(et_bo2skpd , 0x21) // '!' + SRELLTMP_UIL32(et_rvfmrcg , 0x28) // '(' + SRELLTMP_UIL32(et_mfrfmrcg, 0x29) // ')' + SRELLTMP_UIL32(et_aofmrast, 0x78) // 'x' + } + // epsilon_type + +#undef SRELLTMP_UIL32 + + } + // namespace re_detail + +// ... "rei_constants.h"] +// ["regex_error.hpp" ... + +class regex_error : public std::runtime_error +{ +public: + + explicit regex_error(const regex_constants::error_type ecode) + : std::runtime_error(what_(ecode)) + , ecode_(ecode) + { + } + + regex_constants::error_type code() const + { + return ecode_; + } + +private: + + static const char *what_(const regex_constants::error_type e) + { + static const char *enames[] = { + "error_collate", "error_ctype", "error_escape", "error_backref", "error_brack" + , "error_paren", "error_brace", "error_badbrace", "error_range", "error_space" + , "error_badrepeat", "error_complexity", "error_stack" // 13. + , "error_utf8", "error_property", "error_noescape", "error_operator", "error_complement" + , "error_modifier" // +6. + , "", "error_internal", "error_lookbehind" + }; + const regex_constants::error_type num = regex_constants::error_last_ - regex_constants::error_first_ + 1; + + return enames[e == 0 + ? num + : ((e - regex_constants::error_first_) < num + ? (e - regex_constants::error_first_) + : (num + (e == 200 ? 2 : 1)))]; + } + + regex_constants::error_type ecode_; +}; + +// ... "regex_error.hpp"] +// ["rei_utf_traits.hpp" ... + + namespace re_detail + { + +#if defined(_MSC_VER) +#define SRELL_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define SRELL_FORCEINLINE __attribute__((always_inline)) +#else +#define SRELL_FORCEINLINE +#endif + +template +struct utf_traits_core +{ +public: + + typedef charT char_type; + + enum + { + maxseqlen = 1, + cb_ = sizeof (charT) == 1 ? CHAR_BIT : std::numeric_limits::digits, + charbit = cb_ < 21 ? cb_ : 21, + bitsetsize = 1 << charbit, + bitsetmask = bitsetsize - 1, + maxcpvalue = charbit < 21 ? bitsetmask : 0x10ffff, + ecmask = charbit < 21 ? (bitsetsize << 1) - 1 : bitsetmask + }; + + // *iter++ + template + static ui_l32 codepoint_inc(ForwardIterator &begin, const ForwardIterator /* end */) + { + return static_cast(*begin++); + // Caller is responsible for begin != end. + } + + // *--iter + template + static ui_l32 dec_codepoint(BidirectionalIterator &cur, const BidirectionalIterator /* begin */) + { + return static_cast(*--cur); + // Caller is responsible for cur != begin. + } + + template // ui_l32 or char_type2. + static bool is_mculeading(const I) + { + return false; + } + + template + static bool is_trailing(const charT2 /* cu */) + { + return false; + } + + static ui_l32 to_codeunits(charT out[maxseqlen], ui_l32 cp) + { + out[0] = static_cast(cp); + return 1; + } + + static ui_l32 seqlen(const ui_l32) + { + return 1; + } + + static ui_l32 firstcodeunit(const ui_l32 cp) + { + return cp; + } + + static ui_l32 nextlengthchange(const ui_l32) + { + return static_cast(maxcpvalue + 1); + } +}; +// utf_traits_core + +// common and utf-32. +template +struct utf_traits : public utf_traits_core +{ +}; +// utf_traits + +// utf-8 specific. +template +struct utf8_traits : public utf_traits_core +{ +public: + + enum + { + maxseqlen = 4, + bitsetsize = 0x100, + bitsetmask = 0xff, + maxcpvalue = 0x10ffff + }; + + template + static SRELL_FORCEINLINE ui_l32 codepoint_inc(ForwardIterator &begin, const ForwardIterator end) + { + ui_l32 codeunits = static_cast(*begin++ & 0xff); + + if ((codeunits & 0x80) == 0) + return codeunits; + + if (begin != end) + { +// codeunits = static_cast((codeunits << 6) | _pdep_u32(*begin, 0xc03f)); + codeunits = static_cast((*begin & 0x3f) | ((*begin & 0xc0) << 8) | (codeunits << 6)); + ++begin; + + // 1011 0aaa aabb bbbb? + if ((codeunits - 0xb080) < 0x780) + return static_cast(codeunits & 0x7ff); + + if (begin != end) + { + codeunits = static_cast((*begin & 0x3f) | ((*begin & 0xc0) << 16) | (codeunits << 6)); + ++begin; + + // 1010 1110 aaaa bbbb bbcc cccc? + if ((codeunits - 0xae0800) < 0xf800) + return static_cast(codeunits & 0xffff); + + if (begin != end) + { + codeunits = static_cast((*begin & 0x3f) | ((*begin & 0xc0) << 24) | (codeunits << 6)); + ++begin; + + // 1010 1011 110a aabb bbbb cccc ccdd dddd? + if ((codeunits - 0xabc10000) < 0x100000) + return static_cast(codeunits & 0x1fffff); + } + } + } + return re_detail::constants::invalid_u32value; + } + + template + static SRELL_FORCEINLINE ui_l32 dec_codepoint(BidirectionalIterator &cur, const BidirectionalIterator begin) + { + ui_l32 codeunits = static_cast(*--cur); + + if ((codeunits & 0x80) == 0) + return static_cast(codeunits & 0xff); + + if (cur != begin) + { + codeunits = static_cast((codeunits & 0x3f) | ((codeunits & 0xc0) << 8) | ((*--cur & 0xff) << 6)); + + // 1011 0bbb bbaa aaaa? + if ((codeunits - 0xb080) < 0x780) + return static_cast(codeunits & 0x7ff); + + if (cur != begin) + { + codeunits = static_cast((codeunits & 0xfff) | ((codeunits & 0xf000) << 8) | ((*--cur & 0xff) << 12)); + + // 1010 1110 cccc bbbb bbaa aaaa? + if ((codeunits - 0xae0800) < 0xf800) + return static_cast(codeunits & 0xffff); + + if (cur != begin) + { + codeunits = static_cast((codeunits & 0x3ffff) | ((codeunits & 0xfc0000) << 8) | ((*--cur & 0xff) << 18)); + + // 1010 1011 110d ddcc cccc bbbb bbaa aaaa? + if ((codeunits - 0xabc10000) < 0x100000) + return static_cast(codeunits & 0x1fffff); + } + } + } + return re_detail::constants::invalid_u32value; + } + + template + static bool is_mculeading(const I c) + { + return (c & 0x80) ? true : false; + } + + template + static bool is_trailing(const charT2 cu) + { + return (cu & 0xc0) == 0x80; + } + + static ui_l32 to_codeunits(charT out[maxseqlen], ui_l32 cp) + { + if (cp < 0x80) + { + out[0] = static_cast(cp); + return 1; + } + else if (cp < 0x800) + { + out[0] = static_cast(((cp >> 6) & 0x1f) | 0xc0); + out[1] = static_cast((cp & 0x3f) | 0x80); + return 2; + } + else if (cp < 0x10000) + { + out[0] = static_cast(((cp >> 12) & 0x0f) | 0xe0); + out[1] = static_cast(((cp >> 6) & 0x3f) | 0x80); + out[2] = static_cast((cp & 0x3f) | 0x80); + return 3; + } + + out[0] = static_cast(((cp >> 18) & 0x07) | 0xf0); + out[1] = static_cast(((cp >> 12) & 0x3f) | 0x80); + out[2] = static_cast(((cp >> 6) & 0x3f) | 0x80); + out[3] = static_cast((cp & 0x3f) | 0x80); + return 4; + } + + static ui_l32 seqlen(const ui_l32 cp) + { + return (cp < 0x80) ? 1 : ((cp < 0x800) ? 2 : ((cp < 0x10000) ? 3 : 4)); + } + + static ui_l32 firstcodeunit(const ui_l32 cp) + { + if (cp < 0x80) + return cp; + + if (cp < 0x800) + return static_cast(((cp >> 6) & 0x1f) | 0xc0); + + if (cp < 0x10000) + return static_cast(((cp >> 12) & 0x0f) | 0xe0); + + return static_cast(((cp >> 18) & 0x07) | 0xf0); + } + + static ui_l32 nextlengthchange(const ui_l32 cp) + { + return (cp < 0x80) ? 0x80 : ((cp < 0x800) ? 0x800 : ((cp < 0x10000) ? 0x10000 : 0x110000)); + } +}; +// utf8_traits + +// utf-16 specific. +template +struct utf16_traits : public utf_traits_core +{ +public: + + enum + { + maxseqlen = 2, + bitsetsize = 0x10000, + bitsetmask = 0xffff, + maxcpvalue = 0x10ffff + }; + + template + static SRELL_FORCEINLINE ui_l32 codepoint_inc(ForwardIterator &begin, const ForwardIterator end) + { + const ui_l32 codeunit = static_cast(*begin++); + + if ((codeunit & 0xfc00) != 0xd800) + return static_cast(codeunit & 0xffff); + + if (begin != end && (*begin & 0xfc00) == 0xdc00) + return static_cast((((codeunit & 0x3ff) << 10) | (*begin++ & 0x3ff)) + 0x10000); + + return static_cast(codeunit & 0xffff); + } + + template + static SRELL_FORCEINLINE ui_l32 dec_codepoint(BidirectionalIterator &cur, const BidirectionalIterator begin) + { + const ui_l32 codeunit = static_cast(*--cur); + + if ((codeunit & 0xfc00) != 0xdc00 || cur == begin) + return static_cast(codeunit & 0xffff); + + if ((*--cur & 0xfc00) == 0xd800) + return static_cast((((*cur & 0x3ff) << 10) | (codeunit & 0x3ff)) + 0x10000); + + ++cur; + + return static_cast(codeunit & 0xffff); + } + + template + static bool is_mculeading(const I c) + { + return (c & 0xfc00) == 0xd800; + } + + template + static bool is_trailing(const charT2 cu) + { + return (cu & 0xfc00) == 0xdc00; + } + + static ui_l32 to_codeunits(charT out[maxseqlen], ui_l32 cp) + { + if (cp < 0x10000) + { + out[0] = static_cast(cp); + return 1; + } + + cp -= 0x10000; + out[0] = static_cast(((cp >> 10) & 0x3ff) | 0xd800); + out[1] = static_cast((cp & 0x3ff) | 0xdc00); + return 2; + } + + static ui_l32 seqlen(const ui_l32 cp) + { + return (cp < 0x10000) ? 1 : 2; + } + + static ui_l32 firstcodeunit(const ui_l32 cp) + { + if (cp < 0x10000) + return cp; + + return static_cast((cp >> 10) + 0xd7c0); + // aaaaa bbbbcccc ddddeeee -> AA AAbb bbcc/cc dddd eeee where AAAA = aaaaa - 1. + } + + static ui_l32 nextlengthchange(const ui_l32 cp) + { + return (cp < 0x10000) ? 0x10000 : 0x110000; + } +}; +// utf16_traits + +// specialisation for char. +template <> +struct utf_traits : public utf_traits_core +{ +public: + + template + static ui_l32 codepoint_inc(ForwardIterator &begin, const ForwardIterator /* end */) + { + return static_cast(static_cast(*begin++)); + } + + template + static ui_l32 dec_codepoint(BidirectionalIterator &cur, const BidirectionalIterator /* begin */) + { + return static_cast(static_cast(*--cur)); + } +}; // utf_traits + +// specialisation for signed char. +template <> +struct utf_traits : public utf_traits +{ +}; + +// (signed) short, (signed) int, (signed) long, (signed) long long, ... + +#if defined(__cpp_unicode_characters) +template <> +struct utf_traits : public utf16_traits +{ +}; +#endif + +#if defined(__cpp_char8_t) +template <> +struct utf_traits : public utf8_traits +{ +}; +#endif + + } // re_detail + +// ... "rei_utf_traits.hpp"] +// ["regex_traits.hpp" ... + +template +struct regex_traits +{ +public: + + typedef charT char_type; + typedef std::basic_string string_type; + typedef int locale_type; + typedef int char_class_type; + + typedef re_detail::utf_traits utf_traits; +}; // regex_traits + +template +struct u8regex_traits : public regex_traits +{ + typedef re_detail::utf8_traits utf_traits; +}; + +template +struct u16regex_traits : public regex_traits +{ + typedef re_detail::utf16_traits utf_traits; +}; + +// ... "regex_traits.hpp"] +// ["rei_memory.hpp" ... + + namespace re_detail + { + +template +struct concon_view +{ + typedef std::size_t size_type; + + const charT *data_; + size_type size_; + + template + concon_view(const ContiguousContainer &c) +// requires std::contiguous_iterator + : data_(c.data()), size_(c.size()) {} + + concon_view() : data_(NULL), size_(0) {} + concon_view(const charT *const p, const size_type s) : data_(p), size_(s) {} + + const charT *data() const + { + return data_; + } + size_type size() const + { + return size_; + } +}; +// concon_view + +template +class simple_array +{ +public: + + typedef ElemT value_type; + typedef std::size_t size_type; + typedef ElemT &reference; + typedef const ElemT &const_reference; + typedef ElemT *pointer; + typedef const ElemT *const_pointer; + typedef concon_view sa_view; + + static const size_type npos = static_cast(-1); + +public: + + simple_array() + : buffer_(NULL), size_(0), capacity_(0) + { + } + + simple_array(const size_type initsize) + : buffer_(static_cast(std::malloc(initsize * sizeof (ElemT)))), size_(initsize), capacity_(initsize) + { + if (buffer_ == NULL) + throw std::bad_alloc(); + } + + simple_array(const simple_array &right) + : buffer_(NULL), size_(0), capacity_(0) + { + operator=(right); + } + + simple_array(const sa_view &v) + : buffer_(NULL), size_(0), capacity_(0) + { + operator=(v); + } + + simple_array &operator=(const simple_array &right) + { + if (this != &right) + { + resize(right.size_); + if (right.size_) + std::memmove(buffer_, right.buffer_, right.size_ * sizeof (ElemT)); + } + return *this; + } + + simple_array &operator=(const sa_view &v) + { + if (buffer_ != v.data_) + { + resize(v.size_); + if (v.size_) + std::memmove(buffer_, v.data_, v.size_ * sizeof (ElemT)); + } + return *this; + } + +#if defined(__cpp_rvalue_references) + simple_array(simple_array &&right) SRELL_NOEXCEPT + : buffer_(right.buffer_) + , size_(right.size_) + , capacity_(right.capacity_) + { + right.size_ = 0; + right.capacity_ = 0; + right.buffer_ = NULL; + } + + simple_array &operator=(simple_array &&right) SRELL_NOEXCEPT + { + if (this != &right) + { + if (this->buffer_ != NULL) + std::free(this->buffer_); + + this->size_ = right.size_; + this->capacity_ = right.capacity_; + this->buffer_ = right.buffer_; + + right.size_ = 0; + right.capacity_ = 0; + right.buffer_ = NULL; + } + return *this; + } +#endif + + ~simple_array() + { + if (buffer_ != NULL) + std::free(buffer_); + } + + size_type size() const + { + return size_; + } + + bool operator==(const simple_array &right) const + { + if (this->size_ != right.size_) + return false; + + for (size_type i = 0; i < size_; ++i) + if (this->buffer_[i] != right[i]) + return false; + + return true; + } + + void clear() + { + size_ = 0; + } + + void reset() + { + if (size_) + std::memset(buffer_, 0, size_ * sizeof (ElemT)); + } + + void resize(const size_type newsize) + { + if (newsize > capacity_) + reserve_<16>(newsize); + + size_ = newsize; + } + + void resize(const size_type newsize, const ElemT &type) + { + size_type oldsize = size_; + + resize(newsize); + for (; oldsize < size_; ++oldsize) + buffer_[oldsize] = type; + } + + void shrink(const size_type newsize) + { + size_ = newsize; + } + + reference operator[](const size_type pos) + { + return buffer_[pos]; + } + + const_reference operator[](const size_type pos) const + { + return buffer_[pos]; + } + + void push_back(const_reference n) + { + const size_type oldsize = size_; + + if (++size_ > capacity_) + reserve_<16>(size_); + + buffer_[oldsize] = n; + } + + void push_back_c(const ElemT e) + { + push_back(e); + } + + const_reference back() const + { + return buffer_[size_ - 1]; + } + + reference back() + { + return buffer_[size_ - 1]; + } + + void pop_back() + { + --size_; + } + + void assign(const const_pointer p, const size_type len) + { + if (p != buffer_) + { + resize(len); + if (len) + std::memmove(buffer_, p, len * sizeof (ElemT)); + } + } + + simple_array &append(const size_type size, const ElemT &type) + { + resize(size_ + size, type); + return *this; + } + + simple_array &append(const const_pointer p, const size_type size) + { + if (size) + { + resize(size_ + size); + std::memmove(buffer_ + size_ - size, p, size * sizeof (value_type)); + } + return *this; + } + + simple_array &append(const simple_array &right) + { + const size_type rightsize = right.size_; + + if (rightsize) + { + const size_type oldsize = size_; + + resize(size_ + right.size_); + std::memmove(buffer_ + oldsize, right.buffer_, rightsize * sizeof (ElemT)); + } + return *this; + } + + simple_array &append(const simple_array &right, const size_type pos, size_type len) + { + if (len) + { + const size_type oldsize = size_; + + resize(size_ + len); + std::memmove(buffer_ + oldsize, right.buffer_ + pos, len * sizeof (ElemT)); + } + return *this; + } + + void erase(const size_type pos) + { + if (pos < size_) + { + std::memmove(buffer_ + pos, buffer_ + pos + 1, (size_ - pos - 1) * sizeof (ElemT)); + --size_; + } + } + void erase(const size_type pos, const size_type len) + { + if (pos < size_) + { + size_type rmndr = size_ - pos; + + if (rmndr > len) + { + rmndr -= len; + std::memmove(buffer_ + pos, buffer_ + pos + len, rmndr * sizeof (ElemT)); + size_ -= len; + } + else + size_ = pos; + } + } + + // For rei_compiler class. + void insert(const size_type pos, const ElemT &type) + { + move_forwards_(pos, 1); + buffer_[pos] = type; + } + + void insert(const size_type pos, const simple_array &right) + { + if (right.size_) + { + move_forwards_(pos, right.size_); + std::memmove(buffer_ + pos, right.buffer_, right.size_ * sizeof (ElemT)); + } + } + + void insert(const size_type destpos, const simple_array &right, size_type srcpos, size_type srclen = npos) + { + { + const size_type len2 = right.size_ - srcpos; + if (srclen > len2) + srclen = len2; + } + + if (srclen) + { + move_forwards_(destpos, srclen); + std::memmove(buffer_ + destpos, right.buffer_ + srcpos, srclen * sizeof (ElemT)); + } + } + + simple_array &replace(const size_type pos, size_type count, const simple_array &right) + { + if (count < right.size_) + move_forwards_(pos + count, right.size_ - count); + else if (count > right.size_) + { + const size_type rmndr = size_ - pos - count; + + if (rmndr) + { + const pointer base = buffer_ + pos; + + std::memmove(base + right.size_, base + count, rmndr * sizeof (ElemT)); + } + size_ -= count - right.size_; + } + + if (right.size_) + std::memmove(buffer_ + pos, right.buffer_, right.size_ * sizeof (ElemT)); + return *this; + } + + size_type find(const value_type c, size_type pos = 0) const + { + for (; pos <= size_; ++pos) + if (buffer_[pos] == c) + return pos; + + return npos; + } + + void reverse() + { + const size_type half = size_ >> 1; + + for (size_type i = 0; i < half; ++i) + { + const ElemT tmp = buffer_[i]; + ElemT &r = buffer_[size_ - i - 1]; + buffer_[i] = r; + r = tmp; + } + } + + size_type max_size() const + { + return maxsize_; + } + + const_pointer data() const + { + return buffer_; + } + + void swap(simple_array &right) + { + if (this != &right) + { + const pointer tmpbuffer = this->buffer_; + const size_type tmpsize = this->size_; + const size_type tmpcapacity = this->capacity_; + + this->buffer_ = right.buffer_; + this->size_ = right.size_; + this->capacity_ = right.capacity_; + + right.buffer_ = tmpbuffer; + right.size_ = tmpsize; + right.capacity_ = tmpcapacity; + } + } + +protected: + + template + void reserve_(size_type newsize) + { + if (newsize <= maxsize_) + { + const pointer oldbuffer = buffer_; + const size_type capa2 = newsize >= minsize ? capacity_ << 1 : minsize; + + if (newsize < capa2) + { + newsize = capa2; + if (newsize > maxsize_) + newsize = maxsize_; + } + + buffer_ = static_cast(std::realloc(buffer_, newsize * sizeof (ElemT))); + capacity_ = newsize; + + if (buffer_ != NULL) + return; + + std::free(oldbuffer); +// buffer_ = NULL; + size_ = capacity_ = 0; + } + throw std::bad_alloc(); + } + + void move_forwards_(const size_type pos, const size_type count) + { + const size_type oldsize = size_; + + resize(size_ + count); + + if (pos < oldsize) + { + const pointer base = buffer_ + pos; + + std::memmove(base + count, base, (oldsize - pos) * sizeof (ElemT)); + } + } + +protected: + + pointer buffer_; + size_type size_; + size_type capacity_; + + SRELL_STACON size_type maxsize_ = npos / sizeof (ElemT) / 2; +}; +template +const typename simple_array::size_type simple_array::npos; +// simple_array + +typedef simple_array u32array; +typedef concon_view u32view; +typedef u32array::size_type u32size_type; + +struct simple_stack : protected simple_array +{ + using simple_array::size_type; + using simple_array::clear; + using simple_array::size; + using simple_array::shrink; + + template + void push_back_t_nc(const T &n) + { + std::memcpy(buffer_ + size_, &n, sizeof (T)); + size_ += sizeof (T); + } + + template + void push_back_t(const T &n) + { + const size_type newsize = size_ + sizeof (T); + + if (newsize > capacity_) + reserve_<256>(newsize); + + std::memcpy(buffer_ + size_, &n, sizeof (T)); + size_ = newsize; + } + + template + void pop_back_t(T &t) + { + size_ -= sizeof (T); + std::memcpy(&t, buffer_ + size_, sizeof (T)); + } + + void expand(const size_type add) + { + const size_type newsize = size_ + add; + + if (newsize > capacity_) + reserve_<256>(newsize); + } +}; +// simple_stack + + } // namespace re_detail + +// ... "rei_memory.hpp"] +// ["rei_bitset.hpp" ... + + namespace re_detail + { + +template +struct bitsetbase +{ + typedef std::size_t array_type; + +#if defined(__cpp_constexpr) + static constexpr std::size_t find_maxpow2(const array_type v, const std::size_t p2) + { + return v == 0 ? (p2 >> 1) : find_maxpow2((v << (p2 - 1)) << 1, p2 << 1); + } + static constexpr std::size_t bits_per_elem_ = find_maxpow2(0x80000000, 32); +#else + SRELL_STACON array_type maxval_ = static_cast(-1); + SRELL_STACON std::size_t bits_per_elem_ = (((maxval_ >> 31) >> 31) >> 1) ? 64 : 32; +#endif +}; + +template <> +struct bitsetbase<256> +{ + typedef unsigned char array_type; + + SRELL_STACON std::size_t bits_per_elem_ = 1; +}; + +template +class bitset : private bitsetbase +{ + typedef bitsetbase base_type; + typedef typename base_type::array_type array_type; + +public: + + bitset() + : buffer_(static_cast(std::malloc(size_in_byte_))) + { + if (buffer_ != NULL) + { + reset(); + return; + } + throw std::bad_alloc(); + } + + bitset(const bitset &right) + : buffer_(static_cast(std::malloc(size_in_byte_))) + { + if (buffer_ != NULL) + { + operator=(right); + return; + } + throw std::bad_alloc(); + } + +#if defined(__cpp_rvalue_references) + bitset(bitset &&right) SRELL_NOEXCEPT + : buffer_(right.buffer_) + { + right.buffer_ = NULL; + } +#endif + + bitset &operator=(const bitset &right) + { + if (this != &right) + { + std::memcpy(buffer_, right.buffer_, size_in_byte_); + } + return *this; + } + +#if defined(__cpp_rvalue_references) + bitset &operator=(bitset &&right) SRELL_NOEXCEPT + { + if (this != &right) + { + if (this->buffer_ != NULL) + std::free(this->buffer_); + + this->buffer_ = right.buffer_; + right.buffer_ = NULL; + } + return *this; + } +#endif + + ~bitset() + { + if (buffer_ != NULL) + std::free(buffer_); + } + + bitset &reset() + { + std::memset(buffer_, 0, size_in_byte_); + return *this; + } + + bitset &reset(const std::size_t bit) + { + buffer_[bit / base_type::bits_per_elem_] &= ~(static_cast(1) << (bit & bitmask_)); + return *this; + } + + bitset &set(const std::size_t bit) + { + buffer_[bit / base_type::bits_per_elem_] |= (static_cast(1) << (bit & bitmask_)); + return *this; + } + + bool test(const std::size_t bit) const + { + return ((buffer_[bit / base_type::bits_per_elem_] >> (bit & bitmask_)) & 1) != 0; + } + + void swap(bitset &right) + { + if (this != &right) + { + array_type *const tmpbuffer = this->buffer_; + this->buffer_ = right.buffer_; + right.buffer_ = tmpbuffer; + } + } + +private: + + SRELL_STACON std::size_t bitmask_ = base_type::bits_per_elem_ - 1; + SRELL_STACON std::size_t arraylength_ = (Bits + bitmask_) / base_type::bits_per_elem_; + SRELL_STACON std::size_t size_in_byte_ = arraylength_ * sizeof (array_type); + + array_type *buffer_; +}; + + } // namespace re_detail + +// ... "rei_bitset.hpp"] +// ["rei_ucf.hpp" ... + + namespace re_detail + { + +#if !defined(SRELL_NO_UNICODE_ICASE) + + namespace ucf_constants + { + +#include "srell_ucfdata2.h" + + } // namespace ucf_constants + + namespace ucf_internal + { + +typedef ucf_constants::unicode_casefolding ucfdata; + + } // namespace ucf_internal +#endif // !defined(SRELL_NO_UNICODE_ICASE) + + namespace ucf_constants + { +#if !defined(SRELL_NO_UNICODE_ICASE) + static const ui_l32 rev_maxset = ucf_internal::ucfdata::rev_maxset; + static const ui_l32 rev_maxcp = ucf_internal::ucfdata::rev_maxcodepoint; +#else + static const ui_l32 rev_maxset = 2; + static const ui_l32 rev_maxcp = char_alnum::ch_z; +#endif + } // namespace ucf_constants + +class unicode_case_folding +{ +public: + + static ui_l32 do_casefolding(const ui_l32 cp) + { +#if !defined(SRELL_NO_UNICODE_ICASE) + if (cp <= ucf_internal::ucfdata::ucf_maxcodepoint) + return cp + ucf_internal::ucfdata::ucf_deltatable[ucf_internal::ucfdata::ucf_segmenttable[cp >> 8] + (cp & 0xff)]; +#else + if (cp >= char_alnum::ch_A && cp <= char_alnum::ch_Z) // 'A' && 'Z' + return static_cast(cp - char_alnum::ch_A + char_alnum::ch_a); // - 'A' + 'a' +#endif + return cp; + } + + static ui_l32 do_caseunfolding(ui_l32 out[ucf_constants::rev_maxset], const ui_l32 cp) + { +#if !defined(SRELL_NO_UNICODE_ICASE) + ui_l32 count = 0u; + + if (cp <= ucf_internal::ucfdata::rev_maxcodepoint) + { + const ui_l32 offset_of_charset = ucf_internal::ucfdata::rev_indextable[ucf_internal::ucfdata::rev_segmenttable[cp >> 8] + (cp & 0xff)]; + const ui_l32 *ptr = &ucf_internal::ucfdata::rev_charsettable[offset_of_charset]; + + for (; *ptr != cfcharset_eos_ && count < ucf_constants::rev_maxset; ++ptr, ++count) + out[count] = *ptr; + } + if (count == 0u) + out[count++] = cp; + + return count; +#else + const ui_l32 nocase = static_cast(cp | masks::asc_icase); + + out[0] = cp; + if (nocase >= char_alnum::ch_a && nocase <= char_alnum::ch_z) + { + out[1] = static_cast(cp ^ masks::asc_icase); + return 2u; + } + return 1u; +#endif + } + + static ui_l32 try_casefolding(const ui_l32 cp) + { +#if !defined(SRELL_NO_UNICODE_ICASE) + if (cp <= ucf_internal::ucfdata::rev_maxcodepoint) + { + const ui_l32 offset_of_charset = ucf_internal::ucfdata::rev_indextable[ucf_internal::ucfdata::rev_segmenttable[cp >> 8] + (cp & 0xff)]; + const ui_l32 uf0 = ucf_internal::ucfdata::rev_charsettable[offset_of_charset]; + + return uf0 != cfcharset_eos_ ? uf0 : constants::invalid_u32value; + } +#else + const ui_l32 nocase = static_cast(cp | masks::asc_icase); + + if (nocase >= char_alnum::ch_a && nocase <= char_alnum::ch_z) + return nocase; +#endif + return constants::invalid_u32value; + } + +private: + +#if !defined(SRELL_NO_UNICODE_ICASE) + static const ui_l32 cfcharset_eos_ = ucf_internal::ucfdata::eos; +#endif + +public: // For debug. + + void print_tables() const; +}; +// unicode_case_folding + + } // namespace re_detail + +// ... "rei_ucf.hpp"] +// ["rei_up.hpp" ... + + namespace re_detail + { + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + + namespace up_constants + { + +#include "srell_updata3.h" + + SRELL_STACON ui_l32 error_property = static_cast(-1); + } // namespace up_constants + + namespace up_internal + { + typedef int up_type; + typedef const char *pname_type; + + struct pnameno_map_type + { + pname_type name; + up_type pno; + }; + + struct posinfo + { + ui_l32 offset; + ui_l32 numofpairs; + }; + + typedef up_constants::unicode_property_data< + pnameno_map_type, + posinfo, + ui_l32 + > updata; + + } // namespace up_internal + +class unicode_property +{ +public: + + typedef simple_array pstring; + + static ui_l32 lookup_property(const u32view name, const u32view value) + { + up_type ptype = name.size_ > 0 ? lookup_property_name(name) : up_constants::uptype_gc; + const posinfo *pos = &updata::positiontable[ptype]; + ui_l32 pno = lookup_property_value(value, pos->offset, pos->numofpairs); + + if (pno == upid_error && name.size() < 2) + { + ptype = up_constants::uptype_bp; + pos = &updata::positiontable[ptype]; + pno = lookup_property_value(value, pos->offset, pos->numofpairs); + } + + return pno != upid_error ? pno : up_constants::error_property; + } + + static ui_l32 ranges_offset(const ui_l32 property_number) + { + return updata::positiontable[property_number].offset; + } + + static ui_l32 number_of_ranges(const ui_l32 property_number) + { + return updata::positiontable[property_number].numofpairs; + } + + static const ui_l32 *ranges_address(const ui_l32 pno) + { + return &updata::rangetable[ranges_offset(pno) << 1]; + } + + static bool is_valid_pno(const ui_l32 pno) + { + return pno != up_constants::error_property && pno <= max_property_number; + } + + static bool is_pos(const ui_l32 pno) + { + return pno > max_property_number && pno <= max_pos_number; + } + +private: + + typedef up_internal::up_type up_type; + typedef up_internal::pname_type pname_type; + typedef up_internal::pnameno_map_type pnameno_map_type; + typedef up_internal::posinfo posinfo; + typedef up_internal::updata updata; + + static up_type lookup_property_name(const u32view name) + { + return lookup_property_value(name, 1, updata::propertynumbertable[0].pno); + } + + static ui_l32 lookup_property_value(const u32view value, const ui_l32 offset, ui_l32 count) + { + const pnameno_map_type *base = &updata::propertynumbertable[offset]; + + while (count) + { + ui_l32 mid = count >> 1; + const pnameno_map_type &map = base[mid]; + const int cmp = compare(value, map.name); + + if (cmp < 0) + { + count = mid; + } + else if (cmp > 0) + { + ++mid; + count -= mid; + base += mid; + } + else //if (cmp == 0) + return static_cast(map.pno); + } + return upid_error; + } + + static int compare(const u32view value, pname_type pname) + { + for (u32view::size_type i = 0;; ++i, ++pname) + { + if (i == value.size_) + return (*pname == 0) ? 0 : -1; + + if (value.data_[i] != static_cast(*pname)) + return value.data_[i] < static_cast(*pname) ? -1 : 1; + } + } + +private: + + static const ui_l32 max_property_number = static_cast(up_constants::upid_max_property_number); + static const ui_l32 max_pos_number = static_cast(up_constants::upid_max_pos_number); +#if (SRELL_UPDATA_VERSION > 300) + static const ui_l32 upid_error = static_cast(up_constants::upid_error); +#else + static const ui_l32 upid_error = static_cast(up_constants::upid_unknown); +#endif +}; +// unicode_property + +#endif // !defined(SRELL_NO_UNICODE_PROPERTY) + } // namespace re_detail + +// ... "rei_up.hpp"] +// ["rei_range_pair.hpp" ... + + namespace re_detail + { + +struct range_pair +{ + ui_l32 first; + ui_l32 second; + + void set(const ui_l32 min, const ui_l32 max) + { + this->first = min; + this->second = max; + } + + void set(const ui_l32 minmax) + { + this->first = minmax; + this->second = minmax; + } + + bool is_range_valid() const + { + return first <= second; + } + + bool operator==(const range_pair &right) const + { + return this->first == right.first && this->second == right.second; + } + + bool operator<(const range_pair &right) const + { + return this->second < right.first; + } + + void swap(range_pair &right) + { + const range_pair tmp = *this; + *this = right; + right = tmp; + } +}; +// range_pair + +struct range_pair_helper : public range_pair +{ + range_pair_helper(const ui_l32 min, const ui_l32 max) + { + this->first = min; + this->second = max; + } + + range_pair_helper(const ui_l32 minmax) + { + this->first = minmax; + this->second = minmax; + } +}; +// range_pair_helper + +struct range_pairs : public simple_array +{ +public: + + typedef simple_array array_type; + typedef array_type::size_type size_type; + typedef array_type::sa_view view_type; + + range_pairs() + { + } + + range_pairs(const range_pairs &rp) : array_type(rp) + { + } + + range_pairs(const view_type &v) : array_type(v) + { + } + + range_pairs &operator=(const range_pairs &rp) + { + array_type::operator=(rp); + return *this; + } + +#if defined(__cpp_rvalue_references) + range_pairs(range_pairs &&rp) SRELL_NOEXCEPT + : array_type(std::move(rp)) + { + } + + range_pairs &operator=(range_pairs &&rp) SRELL_NOEXCEPT + { + array_type::operator=(std::move(rp)); + return *this; + } +#endif + + void set_solerange(const range_pair &right) + { + this->resize(1); + (*this)[0] = right; + } + + void append_newclass(const range_pairs &right) + { + this->append(right); + } + + void append_newpair(const range_pair &right) + { + this->push_back(right); + } + + void append_newpairs(const range_pair *const p, const ui_l32 n) + { + this->append(p, n); + } + + void join(const range_pair &right) + { + size_type count = this->size(); + + if (count == 0) + { + this->push_back(right); + return; + } + + range_pair *base = &(*this)[0]; + + do + { + size_type mid = count / 2; + range_pair *cp = &base[mid]; + + if (cp->first && (right.second < cp->first - 1)) + { + count = mid; + } + else if (right.first && (cp->second < right.first - 1)) + { + ++mid; + base += mid; + count -= mid; + } + else + { + if (cp->first > right.first) + cp->first = right.first; + + if (cp->second < right.second) + cp->second = right.second; + + range_pair *lw = cp; + + if (cp->first > 0u) + { + for (--cp->first; lw != &(*this)[0];) + { + if ((--lw)->second < cp->first) + { + ++lw; + break; + } + } + ++cp->first; + } + else + lw = &(*this)[0]; + + if (lw != cp) + { + if (cp->first > lw->first) + cp->first = lw->first; + + this->erase(lw - &(*this)[0], cp - lw); + cp = lw; + } + + range_pair *const rend = &(*this)[0] + this->size(); + range_pair *rw = cp; + + if (++cp->second > 0u) + { + for (; ++rw != rend;) + { + if (cp->second < rw->first) + break; + } + --rw; + } + else + rw = rend - 1; + + --cp->second; + + if (rw != cp) + { + if (rw->second < cp->second) + rw->second = cp->second; + + rw->first = cp->first; + this->erase(cp - &(*this)[0], rw - cp); + } + return; + } + } + while (count); + this->insert(base - &(*this)[0], right); + } + + void merge(const range_pairs &right) + { + for (size_type i = 0; i < right.size(); ++i) + join(right[i]); + } + + void merge(const view_type &v) + { + for (size_type i = 0; i < v.size_; ++i) + join(v.data_[i]); + } + + bool same(ui_l32 pos, const ui_l32 count, const range_pairs &right) const + { + if (count != right.size()) + return false; + + for (ui_l32 i = 0; i < count; ++i, ++pos) + if (!((*this)[pos] == right[i])) + return false; + + return true; + } + + int relationship(const range_pairs &right) const + { + if (this->size() == right.size()) + { + for (size_type i = 0; i < this->size(); ++i) + { + if (!((*this)[i] == right[i])) + { + if (i == 0) + goto check_overlap; + + return 1; // Overlapped. + } + } + return 0; // Same. + } + check_overlap: + return is_overlap(right) ? 1 : 2; // Overlapped or exclusive. + } + + void negation() + { + ui_l32 begin = 0; + size_type wpos = 0; + + for (size_type rpos = 0; rpos < this->size(); ++rpos) + { + const range_pair &rrange = (*this)[rpos]; + const ui_l32 nextbegin = rrange.second + 1; + + if (begin < rrange.first) + { + const ui_l32 prev2 = rrange.first - 1; + range_pair &wrange = (*this)[wpos]; + + wrange.second = prev2; + wrange.first = begin; + ++wpos; + } + begin = nextbegin; + } + + if (begin <= constants::unicode_max_codepoint) + { + if (wpos >= this->size()) + this->resize(wpos + 1); + + (*this)[wpos].set(begin, constants::unicode_max_codepoint); + } + else + this->shrink(wpos); + } + + bool is_overlap(const range_pairs &right) const + { + for (size_type i = 0; i < this->size(); ++i) + { + const range_pair &leftrange = (*this)[i]; + + for (size_type j = 0; j < right.size(); ++j) + { + const range_pair &rightrange = right[j]; + + if (rightrange.first <= leftrange.second) // Excludes l1 l2 < r1 r2. + if (leftrange.first <= rightrange.second) // Excludes r1 r2 < l1 l2. + return true; + } + } + return false; + } + + void load_from_memory(const ui_l32 *array, ui_l32 number_of_pairs) + { + for (; number_of_pairs; --number_of_pairs, array += 2) + join(range_pair_helper(array[0], array[1])); + } + + void make_caseunfoldedcharset() + { + ui_l32 table[ucf_constants::rev_maxset] = {}; + range_pairs newranges; + + for (size_type i = 0; i < this->size(); ++i) + { + const range_pair &range = (*this)[i]; + + for (ui_l32 ucp = range.first; ucp <= range.second && ucp <= ucf_constants::rev_maxcp; ++ucp) + { + const ui_l32 setnum = unicode_case_folding::do_caseunfolding(table, ucp); + + for (ui_l32 j = 0; j < setnum; ++j) + { + if (table[j] != ucp) + newranges.join(range_pair_helper(table[j])); + } + } + } + merge(newranges); + } + + // For updataout.hpp. + void remove_range(const range_pair &right) + { + for (size_type pos = 0; pos < this->size();) + { + range_pair &left = (*this)[pos]; + + if (right.first <= left.first) // r1 <= l1 + { + if (left.first <= right.second) // r1 <= l1 <= r2. + { + if (right.second < left.second) // r1 <= l1 <= r2 < l2. + { + left.first = right.second + 1; + return; + } + else // r1 <= l1 <= l2 <= r2. + this->erase(pos); + } + else // r1 <= r2 < l1 + return; + } + //else // l1 < r1 + else if (right.first <= left.second) // l1 < r1 <= l2. + { + if (left.second <= right.second) // l1 < r1 <= l2 <= r2. + { + left.second = right.first - 1; + ++pos; + } + else // l1 < r1 <= r2 < l2 + { + range_pair newrange(left); + + left.second = right.first - 1; + newrange.first = right.second + 1; + this->insert(++pos, newrange); + return; + } + } + else // l1 <= l2 < r1 + ++pos; + } + } + + ui_l32 consists_of_one_character() const + { + if (this->size() == 1 && (*this)[0].first == (*this)[0].second) + return (*this)[0].first; + + if (this->size()) + { + ui_l32 found[ucf_constants::rev_maxset] = {}; + ui_l32 uf[ucf_constants::rev_maxset]; + const ui_l32 setnum = unicode_case_folding::do_caseunfolding(uf, (*this)[0].first); + + for (size_type i = 0; i < this->size(); ++i) + { + const range_pair &cr = (*this)[i]; + + for (ui_l32 ucp = cr.first;; ++ucp) + { + for (ui_l32 j = 0;; ++j) + { + if (j == setnum) + return constants::invalid_u32value; + + if (ucp == uf[j]) + { + found[j] = 1; + break; + } + } + + if (ucp == cr.second) + break; + } + } + for (ui_l32 i = 0; i < setnum; ++i) + { + if (found[i] == 0) + return constants::invalid_u32value; + } + return uf[0] | masks::cfolded; + } + return constants::invalid_u32value; + } + + void split_ranges(range_pairs &removed, const range_pairs &rightranges) + { + range_pairs &kept = *this; // Subtraction set. + size_type prevolj = 0; + range_pair newpair; + + removed.clear(); // Intersection set. + + for (size_type i = 0;; ++i) + { + RETRY_SAMEINDEXNO: + if (i >= kept.size()) + break; + + range_pair &left = kept[i]; + + for (size_type j = prevolj; j < rightranges.size(); ++j) + { + const range_pair &right = rightranges[j]; + + if (left.second < right.first) // Excludes l1 l2 < r1 r2. + break; + + if (left.first <= right.second) // Excludes r1 r2 < l1 l2. + { + prevolj = j; + + if (left.first < right.first) // l1 < r1 <= r2. + { + if (right.second < left.second) // l1 < r1 <= r2 < l2. + { + removed.join(range_pair_helper(right.first, right.second)); + + newpair.set(right.second + 1, left.second); + left.second = right.first - 1; + kept.insert(i + 1, newpair); + } + else // l1 < r1 <= l2 <= r2. + { + removed.join(range_pair_helper(right.first, left.second)); + left.second = right.first - 1; + } + } + //else // r1 <= l1. + else if (right.second < left.second) // r1 <= l1 <= r2 < l2. + { + removed.join(range_pair_helper(left.first, right.second)); + left.first = right.second + 1; + } + else // r1 <= l1 <= l2 <= r2. + { + removed.join(range_pair_helper(left.first, left.second)); + kept.erase(i); + goto RETRY_SAMEINDEXNO; + } + } + } + } + } + +#if defined(SRELLDBG_NO_BITSET) + bool is_included(const ui_l32 ch) const + { + const range_pair *const end = this->data() + this->size(); + + for (const range_pair *cur = this->data(); cur != end; ++cur) + { + if (ch <= cur->second) + return ch >= cur->first; + } + return false; + } +#endif // defined(SRELLDBG_NO_BITSET) + + bool is_included(const ui_l32 pos, ui_l32 count, const ui_l32 c) const + { + const range_pair *base = &(*this)[pos]; + + while (count) + { + ui_l32 mid = count >> 1; + const range_pair &rp = base[mid]; + + if (c <= rp.second) + { + if (c >= rp.first) + return true; + + count = mid; + } + else + { + ++mid; + count -= mid; + base += mid; + } + } + return false; + } + +#if !defined(SRELLDBG_NO_CCPOS) + + // For Eytzinger layout functions. + + bool is_included_el(ui_l32 pos, const ui_l32 len, const ui_l32 c) const + { + const range_pair *const base = &(*this)[pos]; + +#if defined(__GNUC__) + __builtin_prefetch(base); +#endif + for (pos = 0; pos < len;) + { + const range_pair &rp = base[pos]; + + if (c < rp.first) + pos = (pos << 1) + 1; + else if (c > rp.second) + pos = (pos << 1) + 2; + else + return true; + } + return false; + } + + ui_l32 create_el(const range_pair *srcbase, const ui_l32 srcsize) + { + const ui_l32 basepos = static_cast(this->size()); + + this->resize(basepos + srcsize); + set_eytzinger_layout(0, srcbase, srcsize, &(*this)[basepos], 0); + + return srcsize; + } + +#endif // !defined(SRELLDBG_NO_CCPOS) + + template + ui_l32 num_codeunits() const + { + ui_l32 prev2 = constants::invalid_u32value; + ui_l32 num = 0; + + for (size_type no = 0; no < this->size(); ++no) + { + const range_pair &cr = (*this)[no]; + + for (ui_l32 first = cr.first; first <= static_cast(utf_traits::maxcpvalue);) + { + const ui_l32 nlc = utf_traits::nextlengthchange(first); + const ui_l32 second = cr.second < nlc ? cr.second : (nlc - 1); + const ui_l32 cu1 = utf_traits::firstcodeunit(first); + const ui_l32 cu2 = utf_traits::firstcodeunit(second); + + num += cu2 - cu1 + (prev2 == cu1 ? 0 : 1); + + prev2 = cu2; + if (second == cr.second) + break; + + first = second + 1; + } + } + return num; + } + +private: + + using array_type::push_back; + using array_type::append; + +#if !defined(SRELLDBG_NO_CCPOS) + + ui_l32 set_eytzinger_layout(ui_l32 srcpos, const range_pair *const srcbase, const ui_l32 srclen, + range_pair *const destbase, const ui_l32 destpos) + { + if (destpos < srclen) + { + const ui_l32 nextpos = (destpos << 1) + 1; + + srcpos = set_eytzinger_layout(srcpos, srcbase, srclen, destbase, nextpos); + destbase[destpos] = srcbase[srcpos++]; + srcpos = set_eytzinger_layout(srcpos, srcbase, srclen, destbase, nextpos + 1); + } + return srcpos; + } + +#endif // !defined(SRELLDBG_NO_CCPOS) + +public: // For debug. + + void print_pairs(const int, const char *const = NULL, const char *const = NULL) const; +}; +// range_pairs + + } // namespace re_detail + +// ... "rei_range_pair.hpp"] +// ["rei_char_class.hpp" ... + + namespace re_detail + { + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + +// For RegExpIdentifierStart and RegExpIdentifierPart +struct identifier_charclass +{ +public: + + void clear() + { + char_class_.clear(); + char_class_pos_.clear(); + } + + void setup() + { + if (char_class_pos_.size() == 0) + { + static const ui_l32 additions[] = { + // reg_exp_identifier_start, reg_exp_identifier_part. + 0x24, 0x24, 0x5f, 0x5f, 0x200c, 0x200d // '$' '_' - + }; + range_pairs ranges; + + // For reg_exp_identifier_start. + { + const ui_l32 *const IDs_address = unicode_property::ranges_address(upid_bp_ID_Start); + const ui_l32 IDs_number = unicode_property::number_of_ranges(upid_bp_ID_Start); + ranges.load_from_memory(IDs_address, IDs_number); + } + ranges.load_from_memory(&additions[0], 2); + append_charclass(ranges); + + // For reg_exp_identifier_part. + ranges.clear(); + { + const ui_l32 *const IDc_address = unicode_property::ranges_address(upid_bp_ID_Continue); + const ui_l32 IDc_number = unicode_property::number_of_ranges(upid_bp_ID_Continue); + ranges.load_from_memory(IDc_address, IDc_number); + } + ranges.load_from_memory(&additions[0], 3); + append_charclass(ranges); + } + } + + bool is_identifier(const ui_l32 ch, const bool part) const + { + const range_pair &rp = char_class_pos_[part ? 1 : 0]; + + return char_class_.is_included(rp.first, rp.second, ch); + } + +private: + + void append_charclass(const range_pairs &rps) + { + char_class_pos_.push_back(range_pair_helper(static_cast(char_class_.size()), static_cast(rps.size()))); + char_class_.append_newclass(rps); + } + + range_pairs char_class_; + range_pairs::array_type char_class_pos_; + +// UnicodeIDStart:: +// any Unicode code point with the Unicode property "ID_Start" +// UnicodeIDContinue:: +// any Unicode code point with the Unicode property "ID_Continue" + static const ui_l32 upid_bp_ID_Start = static_cast(up_constants::bp_ID_Start); + static const ui_l32 upid_bp_ID_Continue = static_cast(up_constants::bp_ID_Continue); +}; +// identifier_charclass +#endif // !defined(SRELL_NO_UNICODE_PROPERTY) + +class re_character_class +{ +public: + + enum + { // 0 1 2 3 4 5 + newline, dotall, space, digit, word, icase_word, + // 6 + number_of_predefcls + }; + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + typedef unicode_property::pstring pstring; +#endif + + re_character_class() + { + setup_predefinedclass(); + } + + re_character_class &operator=(const re_character_class &that) + { + if (this != &that) + { +#if !defined(SRELLDBG_NO_CCPOS) + this->char_class_el_ = that.char_class_el_; + this->char_class_pos_el_ = that.char_class_pos_el_; +#endif + this->char_class_ = that.char_class_; + this->char_class_pos_ = that.char_class_pos_; + } + return *this; + } + +#if defined(__cpp_rvalue_references) + re_character_class &operator=(re_character_class &&that) SRELL_NOEXCEPT + { + if (this != &that) + { +#if !defined(SRELLDBG_NO_CCPOS) + this->char_class_el_ = std::move(that.char_class_el_); + this->char_class_pos_el_ = std::move(that.char_class_pos_el_); +#endif + this->char_class_ = std::move(that.char_class_); + this->char_class_pos_ = std::move(that.char_class_pos_); + } + return *this; + } +#endif + + bool is_included(const ui_l32 class_number, const ui_l32 c) const + { +// return char_class_.is_included(char_class_pos_[class_number], c); + const range_pair &rp = char_class_pos_[class_number]; + + return char_class_.is_included(rp.first, rp.second, c); + } + +#if !defined(SRELLDBG_NO_CCPOS) + bool is_included(const ui_l32 pos, const ui_l32 len, const ui_l32 c) const + { + return char_class_el_.is_included_el(pos, len, c); + } +#endif + + void reset() + { + char_class_.shrink(20); + char_class_pos_.shrink(number_of_predefcls); + +#if !defined(SRELLDBG_NO_CCPOS) + char_class_el_.clear(); + char_class_pos_el_.clear(); +#endif + } + + ui_l32 register_newclass(const range_pairs &rps) + { + for (range_pairs::size_type no = 0; no < char_class_pos_.size(); ++no) + { + const range_pair &rp = char_class_pos_[no]; + + if (char_class_.same(rp.first, rp.second, rps)) + return static_cast(no); + } + + append_charclass(rps); + return static_cast(char_class_pos_.size() - 1); + } + + void copy_to(range_pairs &out, const ui_l32 no) const + { + const range_pair &ccpos = char_class_pos_[no]; + + out.assign(&char_class_[ccpos.first], ccpos.second); + } + range_pairs::view_type view(const ui_l32 no) const + { + const range_pair &ccpos = char_class_pos_[no]; + + return range_pairs::view_type(&char_class_[ccpos.first], ccpos.second); + } + +#if !defined(SRELLDBG_NO_CCPOS) + + const range_pair &charclasspos(const ui_l32 no) // const + { + range_pair &elpos = char_class_pos_el_[no]; + + if (elpos.second == 0) + { + const range_pair &posinfo = char_class_pos_[no]; + + if (posinfo.second > 0) + { + elpos.first = static_cast(char_class_el_.size()); + elpos.second = char_class_el_.create_el(&char_class_[posinfo.first], posinfo.second); + } + } + return elpos; + } + + void finalise() + { + char_class_el_.clear(); + char_class_pos_el_.resize(char_class_pos_.size()); + std::memset(&char_class_pos_el_[0], 0, char_class_pos_el_.size() * sizeof (range_pairs::array_type::value_type)); + } + +#endif // #if !defined(SRELLDBG_NO_CCPOS) + + void optimise() + { + } + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + + ui_l32 get_propertynumber(const u32view pname, const u32view pvalue) const + { + const ui_l32 pno = unicode_property::lookup_property(pname, pvalue); + + return (pno != up_constants::error_property) ? pno : up_constants::error_property; + } + + bool load_upranges(range_pairs &newranges, const ui_l32 property_number) const + { + newranges.clear(); + + if (unicode_property::is_valid_pno(property_number)) + { + if (property_number == upid_bp_Assigned) + { + load_updata(newranges, upid_gc_Cn); + newranges.negation(); + } + else + load_updata(newranges, property_number); + + return true; + } + return false; + } + + // Properties of strings. + bool is_pos(const ui_l32 pno) const + { + return unicode_property::is_pos(pno); + } + + bool get_prawdata(u32array &seq, ui_l32 property_number) + { + if (property_number != up_constants::error_property) + { + if (property_number == upid_bp_Assigned) + property_number = upid_gc_Cn; + + const ui_l32 *const address = unicode_property::ranges_address(property_number); +// const ui_l32 offset = unicode_property::ranges_offset(property_number); + const ui_l32 number = unicode_property::number_of_ranges(property_number) * 2; + + seq.resize(number); + for (ui_l32 i = 0; i < number; ++i) + seq[i] = address[i]; + + return true; + } + seq.clear(); + return false; + } + +#endif // !defined(SRELL_NO_UNICODE_PROPERTY) + + void swap(re_character_class &right) + { + if (this != &right) + { +#if !defined(SRELLDBG_NO_CCPOS) + this->char_class_el_.swap(right.char_class_el_); + this->char_class_pos_el_.swap(right.char_class_pos_el_); +#endif + this->char_class_.swap(right.char_class_); + this->char_class_pos_.swap(right.char_class_pos_); + } + } + +private: + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + + void load_updata(range_pairs &newranges, const ui_l32 property_number) const + { + const ui_l32 *const address = unicode_property::ranges_address(property_number); +// const ui_l32 offset = unicode_property::ranges_offset(property_number); + const ui_l32 number = unicode_property::number_of_ranges(property_number); + + newranges.load_from_memory(address, number); + } + +#endif // !defined(SRELL_NO_UNICODE_PROPERTY) + + void append_charclass(const range_pairs &rps) + { + char_class_pos_.push_back(range_pair_helper(static_cast(char_class_.size()), static_cast(rps.size()))); + char_class_.append_newclass(rps); + } + +// The production CharacterClassEscape::s evaluates as follows: +// Return the set of characters containing the characters that are on the right-hand side of the WhiteSpace or LineTerminator productions. +// WhiteSpace:: +// 0009 000B 000C 0020 00A0 FEFF Zs +// LineTerminator:: +// 000A 000D 2028 2029 +// +// gc=Space_Separator:Zs +// 0x0020, 0x0020, 0x00A0, 0x00A0, 0x1680, 0x1680, 0x2000, 0x200A, +// 0x202F, 0x202F, 0x205F, 0x205F, 0x3000, 0x3000, + + void setup_predefinedclass() + { + static const range_pair allranges[] = { + // newline. + { 0x0a, 0x0a }, { 0x0d, 0x0d }, { 0x2028, 0x2029 }, // \n \r + // dotall. + { 0x0000, 0x10ffff }, + // space. + { 0x09, 0x0d }, // \t \n \v \f \r + { 0x20, 0x20 }, // ' ' + { 0xa0, 0xa0 }, // + { 0x1680, 0x1680 }, { 0x2000, 0x200a }, { 0x2028, 0x2029 }, + { 0x202f, 0x202f }, { 0x205f, 0x205f }, { 0x3000, 0x3000 }, + { 0xfeff, 0xfeff }, // + // digit, word. word-icase. + { 0x30, 0x39 }, // '0'-'9' + { 0x41, 0x5a }, { 0x5f, 0x5f }, { 0x61, 0x7a }, // 'A'-'Z' '_' 'a'-'z' + { 0x017f, 0x017f }, { 0x212a, 0x212a } + }; + static const range_pair offsets[] = { + { 0, 3 }, // newline. + { 3, 1 }, // dotall. + { 4, 10 }, // space. + { 14, 1 }, // digit. + { 14, 4 }, // word. + { 14, 6 } // icase_word. + }; + + char_class_.append_newpairs(allranges, sizeof allranges / sizeof (range_pair)); + char_class_pos_.append(offsets, sizeof offsets / sizeof (range_pair)); + } + +private: + +#if !defined(SRELLDBG_NO_CCPOS) + range_pairs char_class_el_; + range_pairs::array_type char_class_pos_el_; +#endif + + range_pairs char_class_; + range_pairs::array_type char_class_pos_; + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + static const ui_l32 upid_gc_Zs = static_cast(up_constants::gc_Space_Separator); + static const ui_l32 upid_gc_Cn = static_cast(up_constants::gc_Unassigned); + static const ui_l32 upid_bp_Assigned = static_cast(up_constants::bp_Assigned); +#endif + +public: // For debug. + + void print_classes(const int) const; +}; +// re_character_class + + } // namespace re_detail + +// ... "rei_char_class.hpp"] +// ["rei_groupname_mapper.hpp" ... + + namespace re_detail + { + +#if !defined(SRELL_NO_NAMEDCAPTURE) + +template +class groupname_mapper +{ +public: + + typedef simple_array gname_string; + typedef typename gname_string::sa_view view_type; + typedef std::size_t size_type; + static const ui_l32 notfound = 0u; + + groupname_mapper() + { + } + + groupname_mapper(const groupname_mapper &right) + : names_(right.names_), keysize_classno_(right.keysize_classno_) + { + } + +#if defined(__cpp_rvalue_references) + groupname_mapper(groupname_mapper &&right) SRELL_NOEXCEPT + : names_(std::move(right.names_)), keysize_classno_(std::move(right.keysize_classno_)) + { + } +#endif + + groupname_mapper &operator=(const groupname_mapper &right) + { + if (this != &right) + { + names_ = right.names_; + keysize_classno_ = right.keysize_classno_; + } + return *this; + } + +#if defined(__cpp_rvalue_references) + groupname_mapper &operator=(groupname_mapper &&right) SRELL_NOEXCEPT + { + if (this != &right) + { + names_ = std::move(right.names_); + keysize_classno_ = std::move(right.keysize_classno_); + } + return *this; + } +#endif + + void clear() + { + names_.clear(); + keysize_classno_.clear(); + } + + const ui_l32 *operator[](const view_type &v) const + { + ui_l32 pos = 0; + + for (std::size_t i = 1; i < static_cast(keysize_classno_.size());) + { + const ui_l32 keysize = keysize_classno_[i]; + const ui_l32 keynum = keysize_classno_[++i]; + + if (keysize == v.size_ && sameseq_(pos, v)) + return &keysize_classno_[i]; + + pos += keysize; + i += keynum + 1; + } + return NULL; + } + + view_type operator[](const ui_l32 indexno) const + { + ui_l32 pos = 0; + + for (std::size_t i = 1; i < static_cast(keysize_classno_.size()); ++i) + { + const ui_l32 keysize = keysize_classno_[i]; + + for (ui_l32 keynum = keysize_classno_[++i]; keynum; --keynum) + { + if (keysize_classno_[++i] == indexno) + return view_type(&names_[pos], keysize); + } + pos += keysize; + } + return view_type(); + } + + size_type size() const + { + return keysize_classno_.size() ? keysize_classno_[0] : 0; + } + + bool push_back(const gname_string &gname, const ui_l32 gno, const u32array &dupranges) + { + const ui_l32 *list = operator[](gname); + + if (list == NULL) + { + size_type curpos = keysize_classno_.size(); + + names_.append(gname); + keysize_classno_.resize(curpos ? (curpos + 3) : 4); + if (curpos) + ++keysize_classno_[0]; + else + keysize_classno_[curpos++] = 1; + keysize_classno_[curpos++] = static_cast(gname.size()); + keysize_classno_[curpos++] = 1; + keysize_classno_[curpos] = gno; + return true; + } + + const size_type offset = list - keysize_classno_.data(); + const size_type keynum = list[0]; + + for (size_type i = 1; i <= keynum; ++i) + { + const ui_l32 no = list[i]; + + for (u32size_type j = 0;; ++j) + { + if (j >= dupranges.size()) + return false; + + if (no < dupranges[j]) + { + if (j & 1) + break; + + return false; + } + } + } + + const size_type newkeynum = ++keysize_classno_[offset]; + + keysize_classno_.insert(offset + newkeynum, gno); + + return true; + } + + ui_l32 assign_number(const gname_string &gname, const ui_l32 gno) + { + const ui_l32 *list = operator[](gname); + + if (list == NULL) + { + size_type curpos = keysize_classno_.size(); + + names_.append(gname); + keysize_classno_.resize(curpos ? (curpos + 3) : 4); + if (curpos) + ++keysize_classno_[0]; + else + keysize_classno_[curpos++] = 1; + keysize_classno_[curpos++] = static_cast(gname.size()); + keysize_classno_[curpos++] = 1; + keysize_classno_[curpos] = gno; + return gno; + } + return list[1]; + } + + void swap(groupname_mapper &right) + { + this->names_.swap(right.names_); + keysize_classno_.swap(right.keysize_classno_); + } + +private: + + bool sameseq_(size_type pos, const view_type &v) const + { + for (size_type i = 0; i < v.size_; ++i, ++pos) + if (pos >= names_.size() || names_[pos] != v.data_[i]) + return false; + + return true; + } + + gname_string names_; + u32array keysize_classno_; + +public: // For debug. + + void print_mappings(const int) const; +}; +template +const ui_l32 groupname_mapper::notfound; +// groupname_mapper + +#endif // !defined(SRELL_NO_NAMEDCAPTURE) + + } // namespace re_detail + +// ... "rei_groupname_mapper.hpp"] +// ["rei_state.hpp" ... + + namespace re_detail + { + +struct re_quantifier +{ + // atleast and atmost: for check_counter and roundbracket_close. + // (Special case 1) in charcter_class, bol, eol, boundary, represents the offset and length + // of the range in the array of character classes. + // (Special case 1+) in NFA_states[0] holds a character class for one character lookahead. + // (Special case 2) in roundbracket_open and roundbracket_pop atleast and atmost represent + // the minimum and maximum bracket numbers respectively inside the brackets itself. + // (Special case 3) in repeat_in_push and repeat_in_pop atleast and atmost represent the + // minimum and maximum bracket numbers respectively inside the repetition. + // (Special case 4) in lookaround_open and lookaround_pop atleast and atmost represent the + // minimum and maximum bracket numbers respectively inside the lookaround. + + ui_l32 atleast; + ui_l32 atmost; + ui_l32 is_greedy; + // (Special case 1: v1) in lookaround_open represents the number of characters to be rewound. + // (Special case 2: v2) in lookaround_open represents: 0=lookaheads, 1=lookbehinds, + // 2=matchpointrewinder, 3=rewinder+rerun. + + void reset(const ui_l32 len = 1) + { + atleast = atmost = len; + is_greedy = 1; + } + + void set(const ui_l32 min, const ui_l32 max) + { + atleast = min; + atmost = max; + } + + void set(const ui_l32 min, const ui_l32 max, const ui_l32 greedy) + { + atleast = min; + atmost = max; + is_greedy = greedy; + } + + bool is_valid() const + { + return atleast <= atmost; + } + + void set_infinity() + { + atmost = constants::infinity; + } + + bool is_infinity() const + { + return atmost == constants::infinity; + } + + bool is_same() const + { + return atleast == atmost; + } + + bool is_default() const + { + return atleast == 1 && atmost == 1; + } + + bool is_question() const + { + return atleast == 0 && atmost == 1; + } + bool is_asterisk() const + { + return atleast == 0 && atmost == constants::infinity; + } + bool is_plus() const + { + return atleast == 1 && atmost == constants::infinity; + } + bool is_asterisk_or_plus() const + { + return atleast <= 1 && atmost == constants::infinity; + } + + bool has_simple_equivalence() const + { + return (atleast <= 1 && atmost <= 3) || (atleast == 2 && atmost <= 4) || (atleast == atmost && atmost <= 6); + } + + void multiply(const re_quantifier &q) + { + const ui_l32 newal = atleast * q.atleast; + + atleast = (newal == 0 || (atleast != constants::infinity && q.atleast != constants::infinity && newal >= atleast)) ? newal : constants::infinity; + + const ui_l32 newam = atmost * q.atmost; + + atmost = (newam == 0 || (atmost != constants::infinity && q.atmost != constants::infinity && newam >= atmost)) ? newam : constants::infinity; + } + + void add(const re_quantifier &q) + { + if (atleast != constants::infinity) + { + if (q.atleast != constants::infinity && (atleast + q.atleast) >= atleast) + atleast += q.atleast; + else + atleast = constants::infinity; + } + + if (atmost != constants::infinity) + { + if (q.atmost != constants::infinity && (atmost + q.atmost) >= atmost) + atmost += q.atmost; + else + atmost = constants::infinity; + } + } +}; +// re_quantifier + +struct re_state +{ + re_state_type type; + + ui_l32 char_num; + // character: for character. + // number: for character_class, brackets, counter, repeat, backreference. + // (Special case) in [0] represents a code unit for finding an entry point if + // the firstchar class consists of a single code unit; otherwise invalid_u32value. + + re_quantifier quantifier; // For check_counter, roundbrackets, repeasts, (?<=...) and (?', + return type < st_zero_width_boundary || (type == st_lookaround_open && char_num == meta_char::mc_gt); +#endif + } + + bool is_ncgroup_open() const + { + return type == st_epsilon && char_num == epsilon_type::et_ncgopen; + } + + bool is_ncgroup_open_or_close() const + { + return type == st_epsilon && next2 == 0 && (char_num == epsilon_type::et_ncgopen || char_num == epsilon_type::et_ncgclose); + } + + bool is_alt() const + { + return type == st_epsilon && next2 != 0 && char_num == epsilon_type::et_alt; // '|' + } + + bool is_question_or_asterisk_before_corcc() const + { + return type == st_epsilon && char_num == epsilon_type::et_ccastrsk; + } + + bool is_asterisk_or_plus_for_onelen_atom() const + { + return type == st_epsilon && ((next1 == 1 && next2 == 2) || (next1 == 2 && next2 == 1)) && quantifier.is_asterisk_or_plus(); + } + + bool is_same_character_or_charclass(const re_state &right) const + { + return type == right.type && char_num == right.char_num + && (type != st_character || !((flags ^ right.flags) & regex_constants::icase)); + } + + std::ptrdiff_t nearnext() const + { + return quantifier.is_greedy ? next1 : next2; + } + + std::ptrdiff_t farnext() const + { + return quantifier.is_greedy ? next2 : next1; + } +}; +// re_state + +template +struct re_compiler_state +{ + const ui_l32 *begin; + ui_l32 soflags; + ui_l32 depth; + + bool backref_used; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + groupname_mapper unresolved_gnames; + u32array dupranges; +#endif + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + identifier_charclass idchecker; +#endif + + void reset(const regex_constants::syntax_option_type f, const ui_l32 *const b) + { + begin = b; + soflags = f; + depth = 0; + backref_used = false; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + unresolved_gnames.clear(); + dupranges.clear(); +#endif + +// idchecker.clear(); // Keeps data once created. + } + + bool is_back() const + { + return (soflags & regex_constants::back_) ? true : false; + } + + bool is_icase() const + { + return (soflags & regex_constants::icase) ? true : false; + } + + bool is_multiline() const + { + return (soflags & regex_constants::multiline) ? true : false; + } + + bool is_dotall() const + { + return (soflags & regex_constants::dotall) ? true : false; + } + + bool is_vmode() const + { +#if !defined(SRELL_NO_VMODE) && !defined(SRELL_NO_UNICODE_PROPERTY) + return (soflags & regex_constants::unicodesets) ? true : false; +#else + return false; +#endif + } + + bool is_nosubs() const + { + return (soflags & regex_constants::nosubs) ? true : false; + } +}; +// re_compiler_state + + } // namespace re_detail + +// ... "rei_state.hpp"] +// ["rei_search_state.hpp" ... + + namespace re_detail + { + +template +struct re_search_state_core +{ + const re_state *state; + BidirectionalIterator iter; +}; + +template +struct re_submatch_core +{ + BidirectionalIterator open_at; + BidirectionalIterator close_at; +}; + +template +struct re_submatch_type +{ + re_submatch_core core; + cntr_type counter; + + void init(const BidirectionalIterator b) + { + core.open_at = core.close_at = b; + counter = 0; + } +}; + +#if defined(SRELL_HAS_TYPE_TRAITS) +template +#else +template +#endif // defined(SRELL_HAS_TYPE_TRAITS) +struct re_search_state_types +{ + typedef re_submatch_core submatch_core; + typedef re_submatch_type submatch_type; + typedef cntr_type counter_type; + typedef BidirectionalIterator position_type; + + typedef std::vector submatch_array; + + typedef re_search_state_core search_state_core; + + typedef std::vector backtracking_array; + typedef std::vector capture_array; + typedef simple_array counter_array; + typedef std::vector repeat_array; + + typedef typename backtracking_array::size_type btstack_size_type; + +private: + + backtracking_array bt_stack; + capture_array capture_stack; + counter_array counter_stack; + repeat_array repeat_stack; + +public: + + void clear_stacks() + { + bt_stack.clear(); + capture_stack.clear(); + repeat_stack.clear(); + counter_stack.clear(); + } + + btstack_size_type bt_size() const + { + return bt_stack.size(); + } + void bt_resize(const btstack_size_type s) + { + bt_stack.resize(s); + } + + void expand(const btstack_size_type /* addlen */) + { + } + void push_bt_wc(const search_state_core &ssc) + { + bt_stack.push_back(ssc); + } + + void push_bt(const search_state_core &ssc) + { + bt_stack.push_back(ssc); + } + void push_sm(const submatch_core &smc) + { + capture_stack.push_back(smc); + } + void push_c(const counter_type c) + { + counter_stack.push_back(c); + } + void push_rp(const position_type p) + { + repeat_stack.push_back(p); + } + + void pop_bt(search_state_core &ssc) + { + ssc = bt_stack.back(); + bt_stack.pop_back(); + } + void pop_sm(submatch_core &smc) + { + smc = capture_stack.back(); + capture_stack.pop_back(); + } + void pop_c(counter_type &c) + { + c = counter_stack.back(); + counter_stack.pop_back(); + } + void pop_rp(position_type &p) + { + p = repeat_stack.back(); + repeat_stack.pop_back(); + } + +public: + + struct bottom_state + { + btstack_size_type btstack_size; + typename capture_array::size_type capturestack_size; + typename counter_array::size_type counterstack_size; + typename repeat_array::size_type repeatstack_size; + + bottom_state(const btstack_size_type bt, const re_search_state_types &ss) + : btstack_size(bt) + , capturestack_size(ss.capture_stack.size()) + , counterstack_size(ss.counter_stack.size()) + , repeatstack_size(ss.repeat_stack.size()) + { + } + void restore(btstack_size_type &bt, re_search_state_types &ss) const + { + bt = btstack_size; + ss.capture_stack.resize(capturestack_size); + ss.counter_stack.resize(counterstack_size); + ss.repeat_stack.resize(repeatstack_size); + } + }; +}; + +#if !defined(SRELL_NO_UNISTACK) +#if defined(SRELL_HAS_TYPE_TRAITS) +template +struct re_search_state_types +{ +#else +template +struct re_search_state_types +{ + typedef const charT *BidirectionalIterator; +#endif + typedef re_submatch_core submatch_core; + typedef re_submatch_type submatch_type; + typedef cntr_type counter_type; + typedef BidirectionalIterator position_type; + + typedef simple_array submatch_array; + + typedef re_search_state_core search_state_core; + + typedef simple_stack backtracking_array; + typedef simple_array counter_array; + typedef simple_array repeat_array; + + typedef typename backtracking_array::size_type btstack_size_type; + +private: + + backtracking_array bt_stack; + +public: + + void clear_stacks() + { + bt_stack.clear(); + } + + btstack_size_type bt_size() const + { + return bt_stack.size(); + } + void bt_resize(const btstack_size_type s) + { + bt_stack.shrink(s); + } + + void expand(const btstack_size_type addlen) + { + bt_stack.expand(addlen); + } + void push_bt_wc(const search_state_core &ssc) + { + bt_stack.push_back_t(ssc); + } + + void push_bt(const search_state_core &ssc) + { + bt_stack.push_back_t_nc(ssc); + } + void push_sm(const submatch_core &smc) + { + bt_stack.push_back_t_nc(smc); + } + void push_c(const counter_type c) + { + bt_stack.push_back_t_nc(c); + } + void push_rp(const position_type p) + { + bt_stack.push_back_t_nc(p); + } + + void pop_bt(search_state_core &ssc) + { + bt_stack.pop_back_t(ssc); + } + void pop_sm(submatch_core &smc) + { + bt_stack.pop_back_t(smc); + } + void pop_c(counter_type &c) + { + bt_stack.pop_back_t(c); + } + void pop_rp(position_type &p) + { + bt_stack.pop_back_t(p); + } + +public: + + struct bottom_state + { + btstack_size_type btstack_size; + + bottom_state(const btstack_size_type bt, const re_search_state_types &) + : btstack_size(bt) + { + } + void restore(btstack_size_type &bt, re_search_state_types &) const + { + bt = btstack_size; + } + }; +}; +#endif // !defined(SRELL_NO_UNISTACK) +// re_search_state_types + +#if defined(SRELL_HAS_TYPE_TRAITS) + +template +class re_search_state : public re_search_state_types::value> +{ +private: + typedef re_search_state_types::value> base_type; + +#else + +template +class re_search_state : public re_search_state_types +{ +private: + typedef re_search_state_types base_type; + +#endif + +public: + + typedef typename base_type::submatch_core submatchcore_type; + typedef typename base_type::submatch_type submatch_type; + typedef typename base_type::counter_type counter_type; + typedef typename base_type::position_type position_type; + + typedef typename base_type::submatch_array submatch_array; + + typedef typename base_type::search_state_core search_state_core; + + typedef typename base_type::backtracking_array backtracking_array; + typedef typename base_type::counter_array counter_array; + typedef typename base_type::repeat_array repeat_array; + + typedef typename backtracking_array::size_type btstack_size_type; + + typedef typename base_type::bottom_state bottom_state; + +public: + + btstack_size_type btstack_size; + + search_state_core ssc; + + submatch_array bracket; + counter_array counter; + repeat_array repeat; + +#if !defined(SRELL_NO_LIMIT_COUNTER) + std::size_t failure_counter; +#endif + + BidirectionalIterator reallblim; + BidirectionalIterator srchbegin; + BidirectionalIterator lblim; + BidirectionalIterator curbegin; + BidirectionalIterator nextpos; + BidirectionalIterator srchend; + + const re_state *entry_state; + regex_constants::match_flag_type flags; + +public: + + void init( + const BidirectionalIterator begin, + const BidirectionalIterator end, + const BidirectionalIterator lookbehindlimit, + const regex_constants::match_flag_type f) + { + reallblim = lblim = lookbehindlimit; + nextpos = srchbegin = begin; + srchend = end; + flags = f; + } + + void init2(const ui_l32 num_of_brackets, const ui_l32 num_of_counters, const ui_l32 num_of_repeats) + { + counter.resize(num_of_counters); + repeat.resize(num_of_repeats); + + if (num_of_brackets > 1) // [0] is no longer used. + { + bracket.resize(num_of_brackets); + + for (ui_l32 i = 1; i < num_of_brackets; ++i) + bracket[i].init(this->srchend); + } + + btstack_size = 0; + base_type::clear_stacks(); + } + +#if defined(SRELL_NO_LIMIT_COUNTER) + void reset() +#else + void reset(const std::size_t limit) +#endif + { + ssc.state = this->entry_state; + + curbegin = ssc.iter; + +#if !defined(SRELL_NO_LIMIT_COUNTER) + failure_counter = limit; +#endif + } + + bool set_bracket0(const BidirectionalIterator begin, const BidirectionalIterator end) + { + ssc.iter = begin; + nextpos = end; + return true; + } +}; +// re_search_state + + } // namespace re_detail + +// ... "rei_search_state.hpp"] +// ["rei_bmh.hpp" ... + + namespace re_detail + { + +#if !defined(SRELLDBG_NO_BMH) + +template +class re_bmh +{ +public: + + re_bmh() + { + } + + re_bmh(const re_bmh &right) + { + operator=(right); + } + +#if defined(__cpp_rvalue_references) + re_bmh(re_bmh &&right) SRELL_NOEXCEPT + { + operator=(std::move(right)); + } +#endif + + re_bmh &operator=(const re_bmh &that) + { + if (this != &that) + { + this->u32string_ = that.u32string_; + this->bmtable_ = that.bmtable_; + this->repseq_ = that.repseq_; + } + return *this; + } + +#if defined(__cpp_rvalue_references) + re_bmh &operator=(re_bmh &&that) SRELL_NOEXCEPT + { + if (this != &that) + { + this->u32string_ = std::move(that.u32string_); + this->bmtable_ = std::move(that.bmtable_); + this->repseq_ = std::move(that.repseq_); + } + return *this; + } +#endif + + void clear() + { + u32string_.clear(); + bmtable_.clear(); + repseq_.clear(); + } + + void setup(const u32array &u32s, const ui_l32 icase) + { + u32string_ = u32s; + bmtable_.resize(257); + repseq_.clear(); + + if (icase == 0) + setup_for_casesensitive_(); + else + setup_for_icase_(); + } + + template + bool do_casesensitivesearch(re_search_state &sstate, const std::random_access_iterator_tag) const + { + RandomAccessIterator begin = sstate.srchbegin; + const RandomAccessIterator end = sstate.srchend; + std::size_t offset = static_cast(repseq_.size() - 1); + const charT *const repseqend = repseq_.data() + repseq_.size(); + + for (; static_cast(end - begin) > offset;) + { + begin += offset; + + if (*begin == repseq_[0]) + { + const charT *re = &repseq_[1]; + RandomAccessIterator tail = begin; + + for (; *re == *--tail;) + { + if (++re == repseqend) + return sstate.set_bracket0(tail, ++begin); + } + } + offset = bmtable_[*begin & 0xff]; + } + return false; + } + + template + bool do_casesensitivesearch(re_search_state &sstate, const std::bidirectional_iterator_tag) const + { + BidirectionalIterator begin = sstate.srchbegin; + const BidirectionalIterator end = sstate.srchend; + std::size_t offset = static_cast(repseq_.size() - 1); + const charT *const repseqend = repseq_.data() + repseq_.size(); + + for (;;) + { + for (; offset; --offset, ++begin) + if (begin == end) + return false; + + if (*begin == repseq_[0]) + { + const charT *re = &repseq_[1]; + BidirectionalIterator tail = begin; + + for (; *re == *--tail;) + { + if (++re == repseqend) + return sstate.set_bracket0(tail, ++begin); + } + } + offset = bmtable_[*begin & 0xff]; + } + } + + template + bool do_icasesearch(re_search_state &sstate, const std::random_access_iterator_tag) const + { + const RandomAccessIterator begin = sstate.srchbegin; + const RandomAccessIterator end = sstate.srchend; + std::size_t offset = bmtable_[256]; + const ui_l32 entrychar = u32string_[0]; + const ui_l32 *const u32strend = u32string_.data() + u32string_.size(); + RandomAccessIterator curpos = begin; + + for (; static_cast(end - curpos) > offset;) + { + curpos += offset; + + for (; utf_traits::is_trailing(*curpos);) + if (++curpos == end) + return false; + + RandomAccessIterator la(curpos); + const ui_l32 txtlastchar = utf_traits::codepoint_inc(la, end); + + if (txtlastchar == entrychar || unicode_case_folding::do_casefolding(txtlastchar) == entrychar) + { + const ui_l32 *re = &u32string_[1]; + RandomAccessIterator tail = curpos; + + for (; *re == unicode_case_folding::do_casefolding(utf_traits::dec_codepoint(tail, begin));) + { + if (++re == u32strend) + return sstate.set_bracket0(tail, la); + + if (tail == begin) + break; + } + } + offset = bmtable_[txtlastchar & 0xff]; + } + return false; + } + + template + bool do_icasesearch(re_search_state &sstate, const std::bidirectional_iterator_tag) const + { + const BidirectionalIterator begin = sstate.srchbegin; + const BidirectionalIterator end = sstate.srchend; + + if (begin != end) + { + std::size_t offset = bmtable_[256]; + const ui_l32 entrychar = u32string_[0]; + const ui_l32 *const u32strend = u32string_.data() + u32string_.size(); + BidirectionalIterator curpos = begin; + + for (;;) + { + do + { + if (++curpos == end) + return false; + } + while (--offset); + + for (; utf_traits::is_trailing(*curpos);) + if (++curpos == end) + return false; + + BidirectionalIterator la(curpos); + const ui_l32 txtlastchar = utf_traits::codepoint_inc(la, end); + + if (txtlastchar == entrychar || unicode_case_folding::do_casefolding(txtlastchar) == entrychar) + { + const ui_l32 *re = &u32string_[1]; + BidirectionalIterator tail = curpos; + + for (; *re == unicode_case_folding::do_casefolding(utf_traits::dec_codepoint(tail, begin));) + { + if (++re == u32strend) + return sstate.set_bracket0(tail, la); + + if (tail == begin) + break; + } + } + offset = bmtable_[txtlastchar & 0xff]; + } + } + return false; + } + +private: + + void setup_for_casesensitive_() + { + charT mbstr[utf_traits::maxseqlen]; + + for (std::size_t i = 0; i < static_cast(u32string_.size()); ++i) + { + const ui_l32 seqlen = utf_traits::to_codeunits(mbstr, u32string_[i]); + + repseq_.append(mbstr, seqlen); + } + + repseq_.reverse(); + + for (ui_l32 i = 0; i < 256; ++i) + bmtable_[i] = static_cast(repseq_.size()); + + for (std::size_t i = static_cast(repseq_.size() - 1); i; --i) + bmtable_[repseq_[i] & 0xff] = i; + } + + void setup_for_icase_() + { + ui_l32 unfolded[ucf_constants::rev_maxset]; + std::size_t culensum = 0; + + std::memset(&bmtable_[0], 0, sizeof (std::size_t) * bmtable_.size()); + u32string_.reverse(); + + for (std::size_t i = 1; i < static_cast(u32string_.size()); ++i) + { + const ui_l32 setnum = unicode_case_folding::do_caseunfolding(unfolded, u32string_[i]); + ui_l32 u32c = unfolded[0]; + + for (ui_l32 j = 1; j < setnum; ++j) + if (u32c > unfolded[j]) + u32c = unfolded[j]; + + culensum += utf_traits::seqlen(u32c); + + for (ui_l32 j = 0; j < setnum; ++j) + { + std::size_t &val = bmtable_[unfolded[j] & 0xff]; + if (val == 0) + val = culensum; + } + } + + bmtable_[256] = culensum; + + ++culensum; + + for (ui_l32 i = 0; i < 256; ++i) + if (bmtable_[i] == 0) + bmtable_[i] = culensum; + } + +public: // For debug. + + void print_table() const; + void print_seq() const; + +private: + + u32array u32string_; + simple_array bmtable_; + simple_array repseq_; +}; +// re_bmh + +#endif // !defined(SRELLDBG_NO_BMH) + } // namespace re_detail + +// ... "rei_bmh.hpp"] +// ["rei_upos.hpp" ... + + namespace re_detail + { + +struct posdata_holder +{ + u32array indices; + u32array seqs; + range_pairs ranges; + range_pair length; + + void clear() + { + indices.clear(); + seqs.clear(); + ranges.clear(); + length.set(1); + } + + bool has_empty() const + { + return (indices.size() >= 2 && indices[0] != indices[1]) ? true : false; + } + + bool has_data() const + { + return ranges.size() > 0 || indices.size() > 0; + } + + bool may_contain_strings() const + { + return indices.size() > 0; // >= 2; + } + + void swap(posdata_holder &right) + { + indices.swap(right.indices); + seqs.swap(right.seqs); + ranges.swap(right.ranges); + length.swap(right.length); + } + + void do_union(const posdata_holder &right) + { + u32array curseq; + + ranges.merge(right.ranges); + + if (right.has_empty() && !has_empty()) + register_emptystring(); + + for (ui_l32 seqlen = 2; seqlen < static_cast(right.indices.size()); ++seqlen) + { + const ui_l32 end = right.indices[seqlen - 1]; + ui_l32 begin = right.indices[seqlen]; + + if (begin != end) + { + ensure_length(seqlen); + curseq.resize(seqlen); + + for (; begin < end;) + { + const ui_l32 inspos = find_seq(&right.seqs[begin], seqlen); + + if (inspos == indices[seqlen - 1]) + { + for (ui_l32 i = 0; i < seqlen; ++i, ++begin) + curseq[i] = right.seqs[begin]; + + seqs.insert(inspos, curseq); + for (ui_l32 i = 0; i < seqlen; ++i) + indices[i] += seqlen; + } + else + begin += seqlen; + } + } + } + check_lengths(); + } + + void do_subtract(const posdata_holder &right) + { + const ui_l32 maxlen = static_cast(indices.size() <= right.indices.size() ? indices.size() : right.indices.size()); + + { + range_pairs removed; + + ranges.split_ranges(removed, right.ranges); + } + + if (right.has_empty() && has_empty()) + unregister_emptystring(); + + for (ui_l32 seqlen = 2; seqlen < maxlen; ++seqlen) + { + const ui_l32 end = right.indices[seqlen - 1]; + ui_l32 begin = right.indices[seqlen]; + + if (begin != end) + { + for (; begin < end;) + { + const ui_l32 delpos = find_seq(&right.seqs[begin], seqlen); + + if (delpos < indices[seqlen - 1]) + { + seqs.erase(delpos, seqlen); + + for (ui_l32 i = 0; i < seqlen; ++i) + indices[i] -= seqlen; + } + else + begin += seqlen; + } + } + } + check_lengths(); + } + + void do_and(const posdata_holder &right) + { + const ui_l32 maxlen = static_cast(indices.size() <= right.indices.size() ? indices.size() : right.indices.size()); + posdata_holder newpos; + u32array curseq; + + ranges.split_ranges(newpos.ranges, right.ranges); + ranges.swap(newpos.ranges); + + if (has_empty() && right.has_empty()) + newpos.register_emptystring(); + else if (may_contain_strings() || right.may_contain_strings()) + ensure_length(1); + + for (ui_l32 seqlen = 2; seqlen < maxlen; ++seqlen) + { + const ui_l32 end = right.indices[seqlen - 1]; + ui_l32 begin = right.indices[seqlen]; + + if (begin != end) + { + const ui_l32 myend = indices[seqlen - 1]; + + curseq.resize(seqlen); + + for (; begin < end; begin += seqlen) + { + const ui_l32 srcpos = find_seq(&right.seqs[begin], seqlen); + + if (srcpos < myend) + { + newpos.ensure_length(seqlen); + + const ui_l32 inspos = newpos.find_seq(&right.seqs[begin], seqlen); + + if (inspos == newpos.indices[seqlen - 1]) + { + for (ui_l32 i = 0; i < seqlen; ++i) + curseq[i] = right.seqs[begin + i]; + + newpos.seqs.insert(inspos, curseq); + for (ui_l32 i = 0; i < seqlen; ++i) + newpos.indices[i] += seqlen; + } + } + } + } + } + this->indices.swap(newpos.indices); + this->seqs.swap(newpos.seqs); + check_lengths(); + } + + void split_seqs_and_ranges(const u32array &inseqs, const bool icase, const bool back) + { + const ui_l32 max = static_cast(inseqs.size()); + u32array curseq; + + clear(); + + for (ui_l32 indx = 0; indx < max;) + { + const ui_l32 elen = inseqs[indx++]; + + if (elen == 1) // Range. + { + ranges.join(range_pair_helper(inseqs[indx], inseqs[indx + 1])); + indx += 2; + } + else if (elen == 2) + { + const ui_l32 ucpval = inseqs[indx++]; + + if (ucpval != constants::ccstr_empty) + ranges.join(range_pair_helper(ucpval)); + else + register_emptystring(); + } + else if (elen >= 3) + { + const ui_l32 seqlen = elen - 1; + + ensure_length(seqlen); + + const ui_l32 inspos = indices[seqlen - 1]; + + curseq.resize(seqlen); + if (!back) + { + for (ui_l32 j = 0; j < seqlen; ++j, ++indx) + curseq[j] = inseqs[indx]; + } + else + { + for (ui_l32 j = seqlen; j; ++indx) + curseq[--j] = inseqs[indx]; + } + + if (icase) + { + for (u32size_type i = 0; i < curseq.size(); ++i) + { + const ui_l32 cf = unicode_case_folding::try_casefolding(curseq[i]); + + if (cf != constants::invalid_u32value) + curseq[i] = cf | masks::cfolded; + } + } + + for (ui_l32 i = indices[seqlen];; i += seqlen) + { + if (i == inspos) + { + seqs.insert(inspos, curseq); + for (ui_l32 j = 0; j < seqlen; ++j) + indices[j] += seqlen; + break; + } + + if (is_sameseq(&seqs[i], curseq.data(), seqlen)) + break; + } + + } + //elen == 0: Padding. + } + + if (icase) + ranges.make_caseunfoldedcharset(); + + check_lengths(); + } + +private: + + void register_emptystring() + { + if (indices.size() < 2) + { + indices.resize(2); + indices[1] = 0; + indices[0] = 1; + } + else if (indices[0] == indices[1]) + { + ++indices[0]; + } + length.first = 0; + } + + void unregister_emptystring() + { + if (indices.size() >= 2 && indices[0] != indices[1]) + indices[0] = indices[1]; + } + + void ensure_length(const ui_l32 seqlen) + { + ui_l32 curlen = static_cast(indices.size()); + + if (seqlen >= curlen) + { + indices.resize(seqlen + 1); + for (; curlen <= seqlen; ++curlen) + indices[curlen] = 0; + } + } + + ui_l32 find_seq(const ui_l32 *const seqbegin, const ui_l32 seqlen) const + { + const ui_l32 end = indices[seqlen - 1]; + + for (ui_l32 begin = indices[seqlen]; begin < end; begin += seqlen) + { + if (is_sameseq(seqbegin, &seqs[begin], seqlen)) + return begin; + } + return end; + } + + void check_lengths() + { + length.set(constants::max_u32value, 0); + + for (ui_l32 i = 2; i < static_cast(indices.size()); ++i) + { + if (indices[i] != indices[i - 1]) + { + if (length.first > i) + length.first = i; + if (length.second < i) + length.second = i; + } + } + + if (ranges.size()) + { + if (length.first > 1) + length.first = 1; + if (length.second < 1) + length.second = 1; + } + + if (has_empty()) + length.first = 0; + + if (length.second == 0) + length.first = 0; + } + + bool is_sameseq(const ui_l32 *const s1, const ui_l32 *const s2, const ui_l32 len) const + { + for (ui_l32 i = 0; i < len; ++i) + if (s1[i] != s2[i]) + return false; + return true; + } +}; +// posdata_holder + + } // namespace re_detail + +// ... "rei_upos.hpp"] +// ["rei_compiler.hpp" ... + + namespace re_detail + { + +#if defined(SRELLDBG_NO_1STCHRCLS) +#define SRELLDBG_NO_BITSET +#define SRELLDBG_NO_SCFINDER +#undef SRELL_HAS_SSE42 +#endif + +#if defined(SRELLDBG_NO_ASTERISK_OPT) +#define SRELLDBG_NO_BRANCH_OPT +#define SRELLDBG_NO_POS_OPT +#endif + +#if defined(SRELLDBG_NO_STATEHOOK) +#define SRELLDBG_NO_BRANCH_OPT2 +#define SRELLDBG_NO_POS_OPT +#endif + +#if defined(SRELL_FIXEDWIDTHLOOKBEHIND) +#define SRELLDBG_NO_MPREWINDER +#endif + +#if !defined(SRELL_MAX_DEPTH) || ((SRELL_MAX_DEPTH + 0) == 0) +#undef SRELL_MAX_DEPTH +#define SRELL_MAX_DEPTH 256 +#endif + +template +struct re_object_core +{ +protected: + + typedef re_state state_type; + typedef simple_array state_array; + + state_array NFA_states; + re_character_class character_class; + +#if !defined(SRELLDBG_NO_1STCHRCLS) + #if !defined(SRELLDBG_NO_BITSET) + bitset firstchar_class_bs; + #endif +#endif + +#if !defined(SRELL_NO_LIMIT_COUNTER) +public: + + std::size_t limit_counter; + +protected: +#endif + + typedef typename traits::utf_traits utf_traits; + + ui_l32 number_of_brackets; + ui_l32 number_of_counters; + ui_l32 number_of_repeats; + ui_l32 soflags; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + groupname_mapper namedcaptures; + typedef typename groupname_mapper::gname_string gname_string; +#endif + +#if !defined(SRELLDBG_NO_BMH) + typedef re_bmh bmh_type; + bmh_type *bmdata; +#endif + +#if defined(SRELL_HAS_SSE42) + __m128i simdranges; +#endif + +#if !defined(SRELL_NO_LIMIT_COUNTER) +private: + + SRELL_STACON std::size_t lcounter_defnum_ = 1 << 21; + +#endif + +protected: + + re_object_core() +#if !defined(SRELL_NO_LIMIT_COUNTER) + : limit_counter(lcounter_defnum_) +#if !defined(SRELLDBG_NO_BMH) + , bmdata(NULL) +#endif +#elif !defined(SRELLDBG_NO_BMH) + : bmdata(NULL) +#endif + { + } + + re_object_core(const re_object_core &right) +#if !defined(SRELLDBG_NO_BMH) + : bmdata(NULL) +#endif + { + operator=(right); + } + +#if defined(__cpp_rvalue_references) + re_object_core(re_object_core &&right) SRELL_NOEXCEPT +#if !defined(SRELLDBG_NO_BMH) + : bmdata(NULL) +#endif + { + operator=(std::move(right)); + } +#endif + +#if !defined(SRELLDBG_NO_BMH) + ~re_object_core() + { + if (bmdata) + delete bmdata; + } +#endif + + void reset(const regex_constants::syntax_option_type flags) + { + NFA_states.clear(); + character_class.reset(); + +#if !defined(SRELLDBG_NO_1STCHRCLS) + #if !defined(SRELLDBG_NO_BITSET) + firstchar_class_bs.reset(); + #endif +#endif + +#if !defined(SRELL_NO_LIMIT_COUNTER) + limit_counter = lcounter_defnum_; +#endif + + number_of_brackets = 1; + number_of_counters = 0; + number_of_repeats = 0; + soflags = static_cast(flags); // regex_constants::ECMAScript; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + namedcaptures.clear(); +#endif + +#if !defined(SRELLDBG_NO_BMH) + if (bmdata) + delete bmdata; + bmdata = NULL; +#endif + } + + re_object_core &operator=(const re_object_core &that) + { + if (this != &that) + { + this->NFA_states = that.NFA_states; + this->character_class = that.character_class; + +#if !defined(SRELLDBG_NO_1STCHRCLS) + #if !defined(SRELLDBG_NO_BITSET) + this->firstchar_class_bs = that.firstchar_class_bs; + #endif +#endif + +#if !defined(SRELL_NO_LIMIT_COUNTER) + this->limit_counter = that.limit_counter; +#endif + + this->number_of_brackets = that.number_of_brackets; + this->number_of_counters = that.number_of_counters; + this->number_of_repeats = that.number_of_repeats; + this->soflags = that.soflags; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + this->namedcaptures = that.namedcaptures; +#endif + +#if !defined(SRELLDBG_NO_BMH) + if (that.bmdata) + { + if (this->bmdata) + *this->bmdata = *that.bmdata; + else + this->bmdata = new bmh_type(*that.bmdata); + } + else if (this->bmdata) + { + delete this->bmdata; + this->bmdata = NULL; + } +#endif +#if defined(SRELL_HAS_SSE42) + simdranges = that.simdranges; +#endif + + if (that.NFA_states.size()) + repair_nextstates(&that.NFA_states[0]); + } + return *this; + } + +#if defined(__cpp_rvalue_references) + re_object_core &operator=(re_object_core &&that) SRELL_NOEXCEPT + { + if (this != &that) + { + this->NFA_states = std::move(that.NFA_states); + this->character_class = std::move(that.character_class); + +#if !defined(SRELLDBG_NO_1STCHRCLS) + #if !defined(SRELLDBG_NO_BITSET) + this->firstchar_class_bs = std::move(that.firstchar_class_bs); + #endif +#endif + +#if !defined(SRELL_NO_LIMIT_COUNTER) + this->limit_counter = that.limit_counter; +#endif + + this->number_of_brackets = that.number_of_brackets; + this->number_of_counters = that.number_of_counters; + this->number_of_repeats = that.number_of_repeats; + this->soflags = that.soflags; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + this->namedcaptures = std::move(that.namedcaptures); +#endif + +#if !defined(SRELLDBG_NO_BMH) + if (this->bmdata) + delete this->bmdata; + this->bmdata = that.bmdata; + that.bmdata = NULL; +#endif +#if defined(SRELL_HAS_SSE42) + simdranges = that.simdranges; +#endif + } + return *this; + } +#endif // defined(__cpp_rvalue_references) + + void swap(re_object_core &right) + { + if (this != &right) + { + this->NFA_states.swap(right.NFA_states); + this->character_class.swap(right.character_class); + +#if !defined(SRELLDBG_NO_1STCHRCLS) + #if !defined(SRELLDBG_NO_BITSET) + this->firstchar_class_bs.swap(right.firstchar_class_bs); + #endif +#endif + +#if !defined(SRELL_NO_LIMIT_COUNTER) + { + const std::size_t tmp_limit_counter = this->limit_counter; + this->limit_counter = right.limit_counter; + right.limit_counter = tmp_limit_counter; + } +#endif + + { + const ui_l32 tmp_numof_brackets = this->number_of_brackets; + this->number_of_brackets = right.number_of_brackets; + right.number_of_brackets = tmp_numof_brackets; + } + { + const ui_l32 tmp_numof_counters = this->number_of_counters; + this->number_of_counters = right.number_of_counters; + right.number_of_counters = tmp_numof_counters; + } + { + const ui_l32 tmp_numof_repeats = this->number_of_repeats; + this->number_of_repeats = right.number_of_repeats; + right.number_of_repeats = tmp_numof_repeats; + } + { + const ui_l32 tmp_soflags = this->soflags; + this->soflags = right.soflags; + right.soflags = tmp_soflags; + } + +#if !defined(SRELL_NO_NAMEDCAPTURE) + this->namedcaptures.swap(right.namedcaptures); +#endif + +#if !defined(SRELLDBG_NO_BMH) + { + bmh_type *const tmp_bmdata = this->bmdata; + this->bmdata = right.bmdata; + right.bmdata = tmp_bmdata; + } +#endif +#if defined(SRELL_HAS_SSE42) + { + const __m128i tmp = this->simdranges; + this->simdranges = right.simdranges; + right.simdranges = tmp; + } +#endif + } + } + + bool set_error(const regex_constants::error_type e) + { +// reset(); + NFA_states.clear(); + soflags |= static_cast(e) << constants::errshift; + return false; + } + + regex_constants::error_type ecode() const + { + return static_cast(soflags >> constants::errshift); + } + +private: + + void repair_nextstates(const state_type *const oldbase) + { + state_type *const newbase = &this->NFA_states[0]; + + for (typename state_array::size_type i = 0; i < this->NFA_states.size(); ++i) + { + state_type &state = this->NFA_states[i]; + + if (state.next_state1) + state.next_state1 = state.next_state1 - oldbase + newbase; + + if (state.next_state2) + state.next_state2 = state.next_state2 - oldbase + newbase; + } + } +}; +// re_object_core + +#if defined(SRELL_HAS_SSE42) + +template +struct cpu_checker +{ + static T x86simd() + { + static const T v = check_(); + + return v; + } + +private: + + static T check_() + { +#if defined(__GNUC__) + return (__builtin_cpu_supports("sse4.2") ? 1 : 0) + | (__builtin_cpu_supports("avx2") ? 2 : 0); // Only for VPCMPEQB. +#elif defined(_MSC_VER) + int cpuInfo[4]; + T v = 0; + + __cpuid(cpuInfo, 0); + const int max = cpuInfo[0]; + + if (max >= 1) + { + __cpuid(cpuInfo, 1); + v |= (cpuInfo[2] & (1 << 20)) ? 1 : 0; // ecx. SSE4.2. + if (max >= 7) + { + __cpuidex(cpuInfo, 7, 0); + v |= (cpuInfo[1] & (1 << 5)) ? 2 : 0; // ebx. AVX2. + } + } + return v; +#else + return 0; +#endif + } +}; +// cpu_checker + +#endif // defined(SRELL_HAS_SSE42) + +template +class re_compiler : public re_object_core +{ +protected: + + template + bool compile(InputIterator begin, const InputIterator end, const regex_constants::syntax_option_type flags) + { + u32array u32; + + this->reset(flags); + + if (!to_u32array(u32, begin, end) || !compile_core(u32.data(), u32.data() + u32.size(), flags & regex_constants::pflagsmask_)) + { +#if !defined(SRELLDBG_NO_BMH) + if (this->bmdata) + delete this->bmdata; + this->bmdata = NULL; +#endif +#if !defined(SRELL_NO_THROW) + if (!(this->soflags & regex_constants::quiet)) + throw regex_error(this->ecode()); +#else + return false; +#endif + } + return true; + } + +private: + + typedef re_object_core base_type; + typedef typename base_type::utf_traits utf_traits; + typedef typename base_type::state_type state_type; + typedef typename base_type::state_array state_array; +#if !defined(SRELL_NO_NAMEDCAPTURE) + typedef typename base_type::gname_string gname_string; +#endif +#if !defined(SRELL_NO_UNICODE_PROPERTY) + typedef typename re_character_class::pstring pstring; +#endif +#if !defined(SRELLDBG_NO_BMH) + typedef typename base_type::bmh_type bmh_type; +#endif + typedef typename state_array::size_type state_size_type; + + typedef re_compiler_state cvars_type; + + template + bool to_u32array(u32array &u32, InputIterator begin, const InputIterator end) + { + while (begin != end) + { + const ui_l32 u32c = utf_traits::codepoint_inc(begin, end); + + if (u32c > constants::unicode_max_codepoint) + return this->set_error(regex_constants::error_utf8); + + u32.push_back_c(u32c); + } + return true; + } + + bool compile_core(const ui_l32 *begin, const ui_l32 *const end, const regex_constants::syntax_option_type flags) + { + re_quantifier piecesize; + cvars_type cvars; + state_type flstate; + + cvars.reset(flags, begin); + + flstate.reset(st_epsilon); + flstate.next2 = 1; + this->NFA_states.push_back(flstate); + + if (!make_nfa_states(this->NFA_states, piecesize, begin, end, cvars)) + { + return false; + } + + if (begin != end) + return this->set_error(regex_constants::error_paren); // ')'s are too many. + +#if !defined(SRELLDBG_NO_BMH) + setup_bmhdata(); +#endif + + flstate.type = st_success; + flstate.next1 = 0; + flstate.next2 = 0; + flstate.quantifier = piecesize; + this->NFA_states.push_back(flstate); + + if (cvars.backref_used && !check_backreferences(cvars)) + return false; + + const bool has_fcc = optimise(cvars); + finalise(has_fcc); + + return true; + } + + bool make_nfa_states(state_array &piece, re_quantifier &piecesize, const ui_l32 *&curpos, const ui_l32 *const end, cvars_type &cvars) + { +#if !defined(SRELL_NO_NAMEDCAPTURE) + const ui_l32 gno_at_groupbegin = this->number_of_brackets; + bool already_pushed = false; +#endif + state_size_type prevbranch_end = 0; + state_type bstate; + state_array branch; + re_quantifier branchsize; + + piecesize.set(constants::infinity, 0, 0); + + bstate.reset(st_epsilon, epsilon_type::et_alt); + + for (;;) + { + branch.clear(); + + if (!make_branch(branch, branchsize, curpos, end, cvars)) + return false; + + if (!piecesize.is_valid() || piecesize.atleast > branchsize.atleast) + piecesize.atleast = branchsize.atleast; + + if (piecesize.atmost < branchsize.atmost) + piecesize.atmost = branchsize.atmost; + + if (curpos != end && *curpos == meta_char::mc_bar) + { + bstate.next2 = static_cast(branch.size()) + 2; + branch.insert(0, bstate); + +#if !defined(SRELL_NO_NAMEDCAPTURE) + if (gno_at_groupbegin != this->number_of_brackets) + { + if (!already_pushed) + { + cvars.dupranges.push_back(gno_at_groupbegin); + cvars.dupranges.push_back(this->number_of_brackets); + already_pushed = true; + } + else + cvars.dupranges.back() = this->number_of_brackets; + } +#endif + } + + if (prevbranch_end) + { + state_type &pbend = piece[prevbranch_end]; + + pbend.next1 = static_cast(branch.size()) + 1; + pbend.char_num = epsilon_type::et_brnchend; // '/' + } + + piece.append(branch); + + if (curpos == end || *curpos == meta_char::mc_rbracl) + break; + + // *curpos == '|' + + prevbranch_end = piece.size(); + bstate.next2 = 0; + piece.push_back(bstate); + + ++curpos; + } + return true; + } + + bool make_branch(state_array &branch, re_quantifier &branchsize, const ui_l32 *&curpos, const ui_l32 *const end, cvars_type &cvars) + { + state_array piece; + state_array piece_with_quantifier; + re_quantifier quantifier; + range_pairs tmpcc; + state_type astate; + posdata_holder pos; + + branchsize.reset(0); + + for (;;) + { + if (curpos == end || *curpos == meta_char::mc_bar || *curpos == meta_char::mc_rbracl) // '|', ')'. + return true; + + piece.clear(); + piece_with_quantifier.clear(); + + astate.reset(st_character, *curpos++); + + switch (astate.char_num) + { + case meta_char::mc_rbraop: // '(' + if (!parse_group(piece, astate.quantifier, curpos, end, cvars)) + return false; + goto AFTER_PIECE_SET; + + case meta_char::mc_sbraop: // '[' + pos.clear(); + + if (!parse_unicharset(pos, curpos, end, cvars)) + return false; + + if (pos.may_contain_strings()) + goto ADD_POS; + + tmpcc.swap(pos.ranges); + + astate.char_num = tmpcc.consists_of_one_character(); + + if (astate.char_num != constants::invalid_u32value) + { + if (astate.char_num & masks::cfolded) + { + astate.char_num ^= masks::cfolded; + this->NFA_states[0].flags |= astate.flags = sflags::icase; + } + astate.type = st_character; + } + else + { + astate.type = st_character_class; + astate.char_num = this->character_class.register_newclass(tmpcc); + } + + goto SKIP_ICASE_CHECK_FOR_CHAR; + + case meta_char::mc_escape: // '\\' + if (curpos == end) + return this->set_error(regex_constants::error_escape); + + astate.char_num = *curpos; + + if (astate.char_num >= char_alnum::ch_1 && astate.char_num <= char_alnum::ch_9) // \1, \9. + { + astate.char_num = translate_numbers(curpos, end, 10, 0, 0, 0xfffffffe); + // 22.2.1.1 Static Semantics: Early Errors: ... >= 2**32 - 1. + + if (astate.char_num == constants::invalid_u32value) + return this->set_error(regex_constants::error_escape); + + astate.flags = 0u; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + BACKREF_POSTPROCESS: +#endif + astate.next2 = 1; + astate.type = st_backreference; + astate.quantifier.atleast = 0; + + cvars.backref_used = true; + + if (cvars.is_icase()) + astate.flags |= sflags::icase; + + break; + } + + ++curpos; + + switch (astate.char_num) + { + case char_alnum::ch_B: // \B. + astate.flags = sflags::is_not; + //@fallthrough@ + + case char_alnum::ch_b: // \b. + astate.type = st_boundary; // \b, \B. + astate.quantifier.reset(0); + astate.char_num = static_cast(!cvars.is_icase() ? re_character_class::word : re_character_class::icase_word); // \w, \W. + break; + + case char_alnum::ch_A: // \A. + goto PUSH_CARET; + + case char_alnum::ch_z: // \z. + goto PUSH_DOLLAR; + + case char_alnum::ch_Z: // \Z. + { + // "(?=(?:\r\n?|[\n\u2028\u2029])?\z)" + static const ui_l32 escZ[] = { 0x28, 0x3f, 0x3d, 0x28, 0x3f, 0x3a, 0x0d, 0x0a, 0x3f, 0x7c, 0x5b, 0x0a, 0x2028, 0x2029, 0x5d, 0x29, 0x3f, 0x5c, 0x7a, 0x29 }; + const ui_l32 *begin = escZ; + if (!make_nfa_states(piece, astate.quantifier, begin, begin + 20, cvars)) + return false; + astate.quantifier.reset(0); + goto AFTER_PIECE_SET; + } + +#if !defined(SRELL_NO_NAMEDCAPTURE) + case char_alnum::ch_k: // \k. + if (curpos == end || *curpos != meta_char::mc_lt) + return this->set_error(regex_constants::error_escape); + else + { + const gname_string groupname = get_groupname(++curpos, end, cvars); + + if (groupname.size() == 0) + return false; + + astate.flags = sflags::backrefno_unresolved; + astate.char_num = static_cast(cvars.unresolved_gnames.size() + 1); + astate.char_num = cvars.unresolved_gnames.assign_number(groupname, astate.char_num); + goto BACKREF_POSTPROCESS; + } +#endif + default: + pos.clear(); + if (!translate_escape(pos, astate, curpos, end, false, cvars)) + return false; + + if (pos.may_contain_strings()) + { + ADD_POS: + transform_seqdata(piece, pos); + astate.quantifier.set(pos.length.first, pos.length.second); + goto AFTER_PIECE_SET; + } + + if (astate.type == st_character_class) + astate.char_num = this->character_class.register_newclass(pos.ranges); + } + + break; + + case meta_char::mc_period: // '.' + astate.type = st_character_class; +#if !defined(SRELL_NO_SINGLELINE) + if (cvars.is_dotall()) + { + astate.char_num = static_cast(re_character_class::dotall); + } + else +#endif + { + this->character_class.copy_to(tmpcc, static_cast(re_character_class::newline)); + + tmpcc.negation(); + astate.char_num = this->character_class.register_newclass(tmpcc); + } + break; + + case meta_char::mc_caret: // '^' + if (cvars.is_multiline()) + astate.flags = sflags::multiline; + PUSH_CARET: + astate.type = st_bol; + astate.char_num = static_cast(re_character_class::newline); + astate.quantifier.reset(0); + break; + + case meta_char::mc_dollar: // '$' + if (cvars.is_multiline()) + astate.flags = sflags::multiline; + PUSH_DOLLAR: + astate.type = st_eol; + astate.char_num = static_cast(re_character_class::newline); + astate.quantifier.reset(0); + break; + + case meta_char::mc_astrsk: // '*' + case meta_char::mc_plus: // '+' + case meta_char::mc_query: // '?' + case meta_char::mc_cbraop: // '{' + return this->set_error(regex_constants::error_badrepeat); + + case meta_char::mc_cbracl: // '}' + return this->set_error(regex_constants::error_brace); + + case meta_char::mc_sbracl: // ']' + return this->set_error(regex_constants::error_brack); + + default:; + } + + if (astate.type == st_character && (cvars.soflags & regex_constants::icase)) + { + const ui_l32 cf = unicode_case_folding::try_casefolding(astate.char_num); + + if (cf != constants::invalid_u32value) + { + astate.char_num = cf; + this->NFA_states[0].flags |= astate.flags = sflags::icase; + } + } + + SKIP_ICASE_CHECK_FOR_CHAR: + + piece.push_back(astate); + AFTER_PIECE_SET: + + if (piece.size()) + { + const state_type &firststate = piece[0]; + + quantifier.reset(); + + if (firststate.has_quantifier() && curpos != end) + { + switch (*curpos) + { + case meta_char::mc_astrsk: // '*' + --quantifier.atleast; + //@fallthrough@ + + case meta_char::mc_plus: // '+' + quantifier.set_infinity(); + break; + + case meta_char::mc_query: // '?' + --quantifier.atleast; + break; + + case meta_char::mc_cbraop: // '{' + ++curpos; + quantifier.atleast = translate_numbers(curpos, end, 10, 1, 0, constants::max_u32value); + + if (quantifier.atleast == constants::invalid_u32value) + return this->set_error(regex_constants::error_brace); + + if (curpos == end) + return this->set_error(regex_constants::error_brace); + + if (*curpos == meta_char::mc_comma) // ',' + { + ++curpos; + quantifier.atmost = translate_numbers(curpos, end, 10, 1, 0, constants::max_u32value); + + if (quantifier.atmost == constants::invalid_u32value) + quantifier.set_infinity(); + + if (!quantifier.is_valid()) + return this->set_error(regex_constants::error_badbrace); + } + else + quantifier.atmost = quantifier.atleast; + + if (curpos == end || *curpos != meta_char::mc_cbracl) // '}' + return this->set_error(regex_constants::error_brace); + + // *curpos == '}' + break; + + default: + goto AFTER_GREEDINESS_CHECK; + } + + if (++curpos != end && *curpos == meta_char::mc_query) // '?' + { + quantifier.is_greedy = 0u; + ++curpos; + } + AFTER_GREEDINESS_CHECK:; + } + + if (piece.size() == 2 && firststate.is_ncgroup_open()) + { + // (?:) alone or followed by a quantifier. +// piece_with_quantifier += piece; + ; // Does nothing. + } + else if (!combine_piece_with_quantifier(piece_with_quantifier, piece, quantifier, astate.quantifier)) + return false; + + astate.quantifier.multiply(quantifier); + branchsize.add(astate.quantifier); + +#if !defined(SRELL_FIXEDWIDTHLOOKBEHIND) + + if (!cvars.is_back()) + branch.append(piece_with_quantifier); + else + branch.insert(0, piece_with_quantifier); +#else + branch.append(piece_with_quantifier); +#endif + } + } + } + + // '('. + + bool parse_group(state_array &piece, re_quantifier &piecesize, const ui_l32 *&curpos, const ui_l32 *const end, cvars_type &cvars) + { + const ui_l32 originalflags(cvars.soflags); + state_type rbstate; + + if (curpos == end) + return this->set_error(regex_constants::error_paren); + + rbstate.reset(st_roundbracket_open); + + if (*curpos == meta_char::mc_query) // '?' + { + if (++curpos == end) + return this->set_error(regex_constants::error_paren); + + rbstate.char_num = *curpos; + + if (rbstate.char_num == meta_char::mc_lt) // '<' + { + if (++curpos == end) + return this->set_error(regex_constants::error_paren); + + rbstate.char_num = *curpos; + + if (rbstate.char_num != meta_char::mc_eq && rbstate.char_num != meta_char::mc_exclam) + { +#if !defined(SRELL_NO_NAMEDCAPTURE) + const gname_string groupname = get_groupname(curpos, end, cvars); + + if (groupname.size() == 0) + return false; + + if (!this->namedcaptures.push_back(groupname, this->number_of_brackets, cvars.dupranges)) + return this->set_error(regex_constants::error_backref); + + goto CGROUP; +#else + return this->set_error(regex_constants::error_paren); +#endif // !defined(SRELL_NO_NAMEDCAPTURE) + } + // "(?<=" or "(?number_of_brackets; + piece.push_back(rbstate); + rbstate.next1 = 1; + rbstate.next2 = 0; + rbstate.type = st_lookaround_pop; + break; + + default: + { + const u32size_type boffset = curpos - cvars.begin; + ui_l32 to_be_modified = 0; + ui_l32 modified = 0; + ui_l32 localflags = cvars.soflags; + bool negate = false; + + for (;;) + { + switch (rbstate.char_num) + { +#if !defined(SRELLDBG_NO_MODIFIERS) + case meta_char::mc_colon: // ':' + // (?ims-ims:...) + if (modified) + { + if (modified & (regex_constants::unicodesets | regex_constants::sticky | regex_constants::nosubs)) + goto ERROR_PAREN; + + goto COLON_FOUND; + } + // "(?-:" + goto ERROR_MODIFIER; +#endif +#if !defined(SRELL_NO_UBMOD) + case meta_char::mc_rbracl: // ')' + if (modified) + { + cvars.soflags = localflags; + if (boffset == 2) + { + this->soflags = localflags; + } + else if (modified & regex_constants::sticky) + goto ERROR_MODIFIER; + + if (boffset == 2) // Restricts so that unbounded forms (?ims-ims) can be used only at the beginning of an expression. + { + ++curpos; + return true; + } + } + // "(?)" or "(?-)" + goto ERROR_MODIFIER; +#endif + case meta_char::mc_minus: // '-' + if (negate) + goto ERROR_MODIFIER; + negate = true; + break; + + case char_alnum::ch_i: // 'i' + to_be_modified = regex_constants::icase; + goto TRY_MODIFICATION; + + case char_alnum::ch_m: // 'm' + to_be_modified = regex_constants::multiline; + goto TRY_MODIFICATION; + + case char_alnum::ch_s: // 's' + to_be_modified = regex_constants::dotall; + goto TRY_MODIFICATION; + + case char_alnum::ch_v: // 'v' + to_be_modified = regex_constants::unicodesets; + goto TRY_MODIFICATION; + + case char_alnum::ch_y: // 'y' + to_be_modified = regex_constants::sticky; + goto TRY_MODIFICATION; + + case char_alnum::ch_n: // 'n' + to_be_modified = regex_constants::nosubs; + goto TRY_MODIFICATION; + + default: + ERROR_PAREN: + return this->set_error(regex_constants::error_paren); + + TRY_MODIFICATION: + if (modified & to_be_modified) + { + ERROR_MODIFIER: + return this->set_error(regex_constants::error_modifier); + } + + modified |= to_be_modified; + if (!negate) + localflags |= to_be_modified; + else + localflags &= ~to_be_modified; + } + + if (++curpos == end) + goto ERROR_PAREN; + + rbstate.char_num = *curpos; + } +#if !defined(SRELLDBG_NO_MODIFIERS) + COLON_FOUND:; + cvars.soflags = localflags; +#endif + } + //@fallthrough@ + + case meta_char::mc_colon: + ++curpos; + goto NCGROUP; + } + + ++curpos; + piece.push_back(rbstate); + } + else + { + if (cvars.is_nosubs()) + { + NCGROUP: + rbstate.type = st_epsilon; + rbstate.char_num = epsilon_type::et_ncgopen; + rbstate.quantifier.atleast = this->number_of_brackets; + } + else + { +#if !defined(SRELL_NO_NAMEDCAPTURE) + CGROUP: +#endif + if (this->number_of_brackets > constants::max_u32value) + return this->set_error(regex_constants::error_complexity); + + rbstate.char_num = this->number_of_brackets++; + rbstate.next1 = 2; + rbstate.next2 = 1; + rbstate.quantifier.atleast = this->number_of_brackets; + piece.push_back(rbstate); + + rbstate.type = st_roundbracket_pop; + rbstate.next1 = 0; + rbstate.next2 = 0; + } + piece.push_back(rbstate); + } + +#if !defined(SRELL_NO_NAMEDCAPTURE) + const u32size_type dzsize = cvars.dupranges.size(); +#endif + + if (++cvars.depth > SRELL_MAX_DEPTH) + return this->set_error(regex_constants::error_complexity); + + if (!make_nfa_states(piece, piecesize, curpos, end, cvars)) + return false; + + // end or ')'? + if (curpos == end) + return this->set_error(regex_constants::error_paren); + + --cvars.depth; + ++curpos; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + cvars.dupranges.resize(dzsize); +#endif + cvars.soflags = originalflags; + + state_type &firststate = piece[0]; + + firststate.quantifier.atmost = this->number_of_brackets - 1; + + switch (rbstate.type) + { + case st_epsilon: + if (piece.size() == 2) // ':' + something. + { + piece.erase(0); + return true; + } + + firststate.quantifier.is_greedy = piecesize.atleast != 0u; + rbstate.char_num = epsilon_type::et_ncgclose; + break; + + case st_lookaround_pop: +#if defined(SRELL_FIXEDWIDTHLOOKBEHIND) + if (firststate.quantifier.is_greedy) // > 0 means lookbehind. + { + if (!piecesize.is_same() || piecesize.is_infinity()) + return this->set_error(regex_constants::error_lookbehind); + + firststate.quantifier.is_greedy = piecesize.atleast; + } +#endif + +#if defined(SRELL_ENABLE_GT) + if (firststate.char_num != meta_char::mc_gt) +#endif + piecesize.reset(0); + + firststate.next1 = static_cast(piece.size()) + 1; + piece[1].quantifier.atmost = firststate.quantifier.atmost; + + rbstate.type = st_lookaround_close; + rbstate.next1 = 0; + break; + + default: + rbstate.type = st_roundbracket_close; + rbstate.next1 = 1; +// rbstate.next2 = 0; + + piece[1].quantifier.atmost = firststate.quantifier.atmost; + firststate.quantifier.is_greedy = piecesize.atleast != 0u; + } + + piece.push_back(rbstate); + return true; + } + + bool combine_piece_with_quantifier(state_array &piece_with_quantifier, state_array &piece, const re_quantifier &quantifier, const re_quantifier &piecesize) + { + if (quantifier.atmost == 0) + return true; + + state_type &firststate = piece[0]; + state_type qstate; + + qstate.reset(st_epsilon, firststate.is_character_or_class() + ? epsilon_type::et_ccastrsk + : epsilon_type::et_dfastrsk); + qstate.quantifier = quantifier; + + if (quantifier.atmost == 1) + { + if (quantifier.atleast == 0) + { + qstate.next2 = static_cast(piece.size()) + 1; + if (!quantifier.is_greedy) + { + qstate.next1 = qstate.next2; + qstate.next2 = 1; + } + + piece[piece.size() - 1].quantifier = quantifier; + piece_with_quantifier.push_back(qstate); + } + + if (firststate.type == st_roundbracket_open) + firststate.quantifier.atmost = piece[1].quantifier.atmost = 0; + + piece_with_quantifier.append(piece); + return true; + } + + // atmost >= 2 + +#if !defined(SRELLDBG_NO_SIMPLEEQUIV) + + // A counter requires at least 6 states: save, restore, check, inc, dec, ATOM(s). + // A character or charclass quantified by one of these has a simple equivalent representation: + // a{0,2} 1.epsilon(2|5), 2.CHorCL(3), 3.epsilon(4|5), 4.CHorCL(5), [5]. + // a{0,3} 1.epsilon(2|7), 2.CHorCL(3), 3.epsilon(4|7), 4.CHorCL(5), 5.epsilon(6|7), 6.CHorCL(7), [7]. + // a{1,2} 1.CHorCL(2), 2.epsilon(3|4), 3.CHorCL(4), [4]. + // a{1,3} 1.CHorCL(2), 2.epsilon(3|6), 3.CHorCL(4), 4.epsilon(5|6), 5.CHorCL(6), [6]. + // a{2,3} 1.CHorCL(2), 2.CHorCL(3), 3.epsilon(4|5), 4.CHorCL(5), [5]. + // a{2,4} 1.CHorCL(2), 2.CHorCL(3), 3.epsilon(4|7), 4.CHorCL(5), 5.epsilon(6|7), 6.CHorCL(7), [7]. + if (qstate.char_num == epsilon_type::et_ccastrsk && quantifier.has_simple_equivalence()) + { + const state_size_type branchsize = piece.size() + 1; + + for (ui_l32 i = 0; i < quantifier.atleast; ++i) + piece_with_quantifier.append(piece); + + firststate.quantifier.set(0, 1, quantifier.is_greedy); + + qstate.next2 = (quantifier.atmost - quantifier.atleast) * branchsize; + if (!quantifier.is_greedy) + { + qstate.next1 = qstate.next2; + qstate.next2 = 1; + } + + for (ui_l32 i = quantifier.atleast; i < quantifier.atmost; ++i) + { + piece_with_quantifier.push_back(qstate); + piece_with_quantifier.append(piece); + quantifier.is_greedy ? (qstate.next2 -= branchsize) : (qstate.next1 -= branchsize); + } + return true; + } +#endif // !defined(SRELLDBG_NO_SIMPLEEQUIV) + + if (firststate.type == st_backreference && (firststate.flags & sflags::backrefno_unresolved)) + { + firststate.quantifier = quantifier; + qstate.quantifier.set(1, 0); + goto ADD_CHECKER; + } + else if (firststate.is_ncgroup_open() && (piecesize.atleast == 0 || firststate.quantifier.is_valid())) + { + qstate.quantifier = firststate.quantifier; + ADD_CHECKER: + + if (this->number_of_repeats > constants::max_u32value) + return this->set_error(regex_constants::error_complexity); + + qstate.char_num = this->number_of_repeats++; + + qstate.type = st_repeat_in_pop; + qstate.next1 = 0; + qstate.next2 = 0; + piece.insert(0, qstate); + + qstate.type = st_repeat_in_push; + qstate.next1 = 2; + qstate.next2 = 1; + piece.insert(0, qstate); + + qstate.quantifier = quantifier; + qstate.type = st_check_0_width_repeat; + qstate.next2 = 1; + piece.push_back(qstate); + + if (piecesize.atleast == 0 && piece[2].type != st_backreference) + goto USE_COUNTER; + + qstate.char_num = epsilon_type::et_dfastrsk; + } + + qstate.type = st_epsilon; + + if (quantifier.is_asterisk()) // {0,} + { + // greedy: 1.epsilon(2|4), 2.piece, 3.LAorC0WR(1|0), 4.OutOfLoop. + // !greedy: 1.epsilon(4|2), 2.piece, 3.LAorC0WR(1|0), 4.OutOfLoop. + // LAorC0WR: LastAtomOfPiece or Check0WidthRepeat. + } + else if (quantifier.is_plus()) // {1,} + { +#if !defined(SRELLDBG_NO_ASTERISK_OPT) + + if (qstate.char_num == epsilon_type::et_ccastrsk) + { + piece_with_quantifier.append(piece); + --qstate.quantifier.atleast; // /.+/ -> /..*/. + } + else +#endif + { + const ui_l32 backup = qstate.char_num; + + qstate.next1 = 2; + qstate.next2 = 0; + qstate.char_num = epsilon_type::et_jmpinlp; + piece_with_quantifier.push_back(qstate); + qstate.char_num = backup; + // greedy: 1.epsilon(3), 2.epsilon(3|5), 3.piece, 4.LAorC0WR(2|0), 5.OutOfLoop. + // !greedy: 1.epsilon(3), 2.epsilon(5|3), 3.piece, 4.LAorC0WR(2|0), 5.OutOfLoop. + } + } + else + { +#if !defined(SRELLDBG_NO_ASTERISK_OPT) + if (qstate.char_num == epsilon_type::et_ccastrsk && quantifier.is_infinity()) + { + if (quantifier.atleast <= 6) + { + for (ui_l32 i = 0; i < quantifier.atleast; ++i) + piece_with_quantifier.append(piece); + qstate.quantifier.atleast = 0; + goto APPEND_ATOM; + } + qstate.quantifier.atmost = qstate.quantifier.atleast; + } +#endif // !defined(SRELLDBG_NO_ASTERISK_OPT) + + USE_COUNTER: + + if (this->number_of_counters > constants::max_u32value) + return this->set_error(regex_constants::error_complexity); + + qstate.char_num = this->number_of_counters++; + + qstate.type = st_save_and_reset_counter; + qstate.next1 = 2; + qstate.next2 = 1; + piece_with_quantifier.push_back(qstate); + + qstate.type = st_restore_counter; + qstate.next1 = 0; + qstate.next2 = 0; + piece_with_quantifier.push_back(qstate); + // 1.save_and_reset_counter(3|2), 2.restore_counter(0|0), + + qstate.type = st_decrement_counter; + piece.insert(0, qstate); + + qstate.next1 = 2; + qstate.next2 = piece[1].is_character_or_class() ? 0 : 1; + qstate.type = st_increment_counter; + piece.insert(0, qstate); + + qstate.type = st_check_counter; + // greedy: 3.check_counter(4|6), 4.piece, 5.LAorC0WR(3|0), 6.OutOfLoop. + // !greedy: 3.check_counter(6|4), 4.piece, 5.LAorC0WR(3|0), 6.OutOfLoop. + // 4.piece = { 4a.increment_counter(4c|4b), 4b.decrement_counter(0|0), 4c.OriginalPiece }. + } + + APPEND_ATOM: + + const std::ptrdiff_t piece_size = static_cast(piece.size()); + state_type &laststate = piece[piece_size - 1]; + + laststate.quantifier = qstate.quantifier; + laststate.next1 = 0 - piece_size; + + qstate.next1 = 1; + qstate.next2 = piece_size + 1; + if (!quantifier.is_greedy) + { + qstate.next1 = qstate.next2; + qstate.next2 = 1; + } + piece_with_quantifier.push_back(qstate); + piece_with_quantifier.append(piece); + +#if !defined(SRELLDBG_NO_ASTERISK_OPT) + + if (qstate.quantifier.atmost != quantifier.atmost) + { + qstate.type = st_epsilon; + qstate.char_num = epsilon_type::et_ccastrsk; + qstate.quantifier.atleast = 0; + qstate.quantifier.atmost = quantifier.atmost; + piece.erase(0, piece_size - 1); + goto APPEND_ATOM; + } +#endif // !defined(SRELLDBG_NO_ASTERISK_OPT) + + return true; + } + + // '['. + + bool parse_unicharset(posdata_holder &basepos, const ui_l32 *&curpos, const ui_l32 *const end, cvars_type &cvars) + { + if (curpos == end) + return this->set_error(regex_constants::error_brack); + + const bool is_umode = !cvars.is_vmode(); + const bool invert = (*curpos == meta_char::mc_caret) ? (++curpos, true) : false; // '^' + enum operation_type + { + op_init, op_firstcc, op_union, op_intersection, op_subtraction + }; + operation_type otype = op_init; + posdata_holder newpos; + range_pair code_range; + state_type castate; + + // ClassSetCharacter :: + // \ CharacterEscape[+UnicodeMode] + // \ ClassSetReservedPunctuator + // \ b + + for (;;) + { + if (curpos == end) + goto ERROR_NOT_CLOSED; + + if (*curpos == meta_char::mc_sbracl) // ']' + break; + + if (!is_umode) + { + ui_l32 next2chars = constants::invalid_u32value; + + if (curpos + 1 != end && *curpos == curpos[1]) + { + switch (*curpos) + { + // ClassSetReservedDoublePunctuator :: one of + // && !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~ + case char_other::co_amp: // '&' + case meta_char::mc_exclam: // '!' + case meta_char::mc_sharp: // '#' + case meta_char::mc_dollar: // '$' + case char_other::co_perc: // '%' + case meta_char::mc_astrsk: // '*' + case meta_char::mc_plus: // '+' + case meta_char::mc_comma: // ',' + case meta_char::mc_period: // '.' + case meta_char::mc_colon: // ':' + case char_other::co_smcln: // ';' + case meta_char::mc_lt: // '<' + case meta_char::mc_eq: // '=' + case meta_char::mc_gt: // '>' + case meta_char::mc_query: // '?' + case char_other::co_atmrk: // '@' + case meta_char::mc_caret: // '^' + case char_other::co_grav: // '`' + case char_other::co_tilde: // '~' + case meta_char::mc_minus: // '-' + next2chars = *curpos; + //@fallthrough@ + default:; + } + } + + switch (otype) + { + case op_intersection: + if (next2chars != char_other::co_amp) + goto ERROR_DOUBLE_PUNCT; + curpos += 2; + break; + + case op_subtraction: + if (next2chars != meta_char::mc_minus) + goto ERROR_DOUBLE_PUNCT; + curpos += 2; + break; + + case op_firstcc: + if (next2chars == char_other::co_amp) + otype = op_intersection; + else if (next2chars == meta_char::mc_minus) + otype = op_subtraction; + else if (next2chars == constants::invalid_u32value) + break; + else + goto ERROR_DOUBLE_PUNCT; + + curpos += 2; + break; + +// case op_union: +// case op_init: + default: + if (next2chars != constants::invalid_u32value) + goto ERROR_DOUBLE_PUNCT; + } + } + + AFTER_OPERATOR: + + if (curpos == end) + goto ERROR_NOT_CLOSED; + + castate.reset(); + + if (!is_umode && *curpos == meta_char::mc_sbraop) // '[' + { + if (++cvars.depth > SRELL_MAX_DEPTH) + return this->set_error(regex_constants::error_complexity); + + ++curpos; + if (!parse_unicharset(newpos, curpos, end, cvars)) + return false; + --cvars.depth; + } + else if (!get_classatom(newpos, castate, curpos, end, cvars, false)) + return false; + + if (curpos == end) + goto ERROR_NOT_CLOSED; + + if (otype == op_init) + otype = op_firstcc; + else if (otype == op_firstcc) + otype = op_union; + + if (castate.type == st_character_class) + { + // In the u-mode, '-' following a character class is an error except "-]", immediately before ']'. + if (is_umode && curpos != end && *curpos == meta_char::mc_minus) // '-' + if ((curpos + 1) != end && curpos[1] != meta_char::mc_sbracl) + goto ERROR_BROKEN_RANGE; + } + else if (castate.type == st_character) + { + if (!newpos.has_data()) + { + code_range.set(castate.char_num); + + if (otype <= op_union) + { + if (*curpos == meta_char::mc_minus && (curpos + 1) != end && curpos[1] != meta_char::mc_sbracl) // '-' + { + ++curpos; + + if (!is_umode && otype < op_union && *curpos == meta_char::mc_minus) // '-' + { + otype = op_subtraction; + ++curpos; + basepos.ranges.join(code_range); + goto AFTER_OPERATOR; + } + + if (!get_classatom(newpos, castate, curpos, end, cvars, true)) + return false; + + otype = op_union; + code_range.second = castate.char_num; + if (!code_range.is_range_valid()) + goto ERROR_BROKEN_RANGE; + } + } + + newpos.ranges.join(code_range); + if (cvars.is_icase()) + newpos.ranges.make_caseunfoldedcharset(); + } + } + + if (is_umode) + basepos.ranges.merge(newpos.ranges); + else + { + switch (otype) + { + case op_union: + basepos.do_union(newpos); + break; + + case op_intersection: + basepos.do_and(newpos); + break; + + case op_subtraction: + basepos.do_subtract(newpos); + break; + +// case op_firstcc: + default: + basepos.swap(newpos); + } + } + } + + // *curpos == ']' + ++curpos; + + if (invert) + { + if (basepos.may_contain_strings()) + return this->set_error(regex_constants::error_complement); + + basepos.ranges.negation(); + } + + return true; + + ERROR_NOT_CLOSED: + return this->set_error(regex_constants::error_brack); + + ERROR_BROKEN_RANGE: + return this->set_error(regex_constants::error_range); + + ERROR_DOUBLE_PUNCT: + return this->set_error(regex_constants::error_operator); + } + + bool get_classatom( + posdata_holder &pos, + state_type &castate, + const ui_l32 *&curpos, + const ui_l32 *const end, + const cvars_type &cvars, + const bool no_ccesc + ) + { + pos.clear(); + + castate.char_num = *curpos++; + + switch (castate.char_num) + { + // ClassSetSyntaxCharacter :: one of + // ( ) [ ] { } / - \ | + case meta_char::mc_rbraop: // '(' + case meta_char::mc_rbracl: // ')' + case meta_char::mc_sbraop: // '[' + case meta_char::mc_sbracl: // ']' + case meta_char::mc_cbraop: // '{' + case meta_char::mc_cbracl: // '}' + case char_other::co_slash: // '/' + case meta_char::mc_minus: // '-' + case meta_char::mc_bar: // '|' + return !cvars.is_vmode() ? true : this->set_error(regex_constants::error_noescape); + + case meta_char::mc_escape: // '\\' + break; + + default: + return true; + } + + if (curpos == end) + return this->set_error(regex_constants::error_escape); + + castate.char_num = *curpos++; + + switch (castate.char_num) + { + case char_alnum::ch_b: + castate.char_num = char_ctrl::cc_bs; // '\b' 0x08:BS + //@fallthrough@ + + case meta_char::mc_minus: // '-' + return true; + + // ClassSetReservedPunctuator :: one of + // & - ! # % , : ; < = > @ ` ~ + case char_other::co_amp: // '&' + case meta_char::mc_exclam: // '!' + case meta_char::mc_sharp: // '#' + case char_other::co_perc: // '%' + case meta_char::mc_comma: // ',' + case meta_char::mc_colon: // ':' + case char_other::co_smcln: // ';' + case meta_char::mc_lt: // '<' + case meta_char::mc_eq: // '=' + case meta_char::mc_gt: // '>' + case char_other::co_atmrk: // '@' + case char_other::co_grav: // '`' + case char_other::co_tilde: // '~' + if (cvars.is_vmode()) + return true; + break; + +#if !defined(SRELL_NO_UNICODE_POS) + case char_alnum::ch_q: // '\\q' + if (cvars.is_vmode() && !no_ccesc) + { + if (curpos == end || *curpos != meta_char::mc_cbraop) // '{' + return this->set_error(regex_constants::error_escape); + + u32array seqs; + u32array curseq; + posdata_holder dummypos; + state_type castate2; + + ++curpos; + + for (;;) + { + if (curpos == end) + return this->set_error(regex_constants::error_escape); + + if (*curpos == meta_char::mc_bar || *curpos == meta_char::mc_cbracl) // '|' or '}'. + { + const ui_l32 seqlen = static_cast(curseq.size()); + + if (seqlen <= 1) + { + seqs.push_back_c(2); + seqs.push_back_c(seqlen != 0 ? curseq[0] : constants::ccstr_empty); + } + else // >= 2 + { + seqs.push_back_c(seqlen + 1); + seqs.append(curseq); + } + + if (*curpos == meta_char::mc_cbracl) // '}' + break; + + curseq.clear(); + ++curpos; + } + else + { + castate2.reset(); + if (!get_classatom(dummypos, castate2, curpos, end, cvars, true)) + return false; + + curseq.push_back_c(castate2.char_num); + } + } + + ++curpos; +#if !defined(SRELL_FIXEDWIDTHLOOKBEHIND) + pos.split_seqs_and_ranges(seqs, cvars.is_icase(), cvars.is_back()); +#else + pos.split_seqs_and_ranges(seqs, cvars.is_icase(), false); +#endif + + return true; + } + //@fallthrough@ +#endif // !defined(SRELL_NO_UNICODE_POS) + + default:; + } + + return translate_escape(pos, castate, curpos, end, no_ccesc, cvars); + } + + bool translate_escape(posdata_holder &pos, state_type &eastate, const ui_l32 *&curpos, const ui_l32 *const end, const bool no_ccesc, const cvars_type &cvars) + { + if (!no_ccesc) + { + // Predefined classes. + switch (eastate.char_num) + { + case char_alnum::ch_D: // \D. + eastate.flags = sflags::is_not; + //@fallthrough@ + + case char_alnum::ch_d: // \d. + eastate.char_num = static_cast(re_character_class::digit); // \d, \D. + break; + + case char_alnum::ch_S: // \S. + eastate.flags = sflags::is_not; + //@fallthrough@ + + case char_alnum::ch_s: // \s. + eastate.char_num = static_cast(re_character_class::space); // \s, \S. + break; + + case char_alnum::ch_W: // \W. + eastate.flags = sflags::is_not; + //@fallthrough@ + + case char_alnum::ch_w: // \w. + eastate.char_num = static_cast(!cvars.is_icase() ? re_character_class::word : re_character_class::icase_word); // \w, \W. + break; + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + case char_alnum::ch_P: // \P{...} + eastate.flags = sflags::is_not; + //@fallthrough@ + + case char_alnum::ch_p: // \p{...} + { + u32view pname; + u32view pvalue; + + if (curpos == end || *curpos != meta_char::mc_cbraop) // '{' + return this->set_error(regex_constants::error_property); + + const bool digit_found = get_property_name_or_value(pvalue, ++curpos, end); + + if (pvalue.size() == 0) + return this->set_error(regex_constants::error_property); + + if (!digit_found) + { + if (curpos == end) + return this->set_error(regex_constants::error_property); + + if (*curpos == meta_char::mc_eq) // '=' + { + pname = pvalue; + get_property_name_or_value(pvalue, ++curpos, end); + if (pvalue.size() == 0) + return this->set_error(regex_constants::error_property); + } + } + + if (curpos == end || *curpos != meta_char::mc_cbracl) // '}' + return this->set_error(regex_constants::error_property); + + ++curpos; + + eastate.char_num = this->character_class.get_propertynumber(pname, pvalue); + + if (eastate.char_num == up_constants::error_property) + return this->set_error(regex_constants::error_property); + + if (!this->character_class.is_pos(eastate.char_num)) + { + pos.clear(); + + this->character_class.load_upranges(pos.ranges, eastate.char_num); + + if (cvars.is_vmode() && cvars.is_icase() && eastate.char_num >= static_cast(re_character_class::number_of_predefcls)) + pos.ranges.make_caseunfoldedcharset(); + + if (eastate.flags) // is_not. + { + pos.ranges.negation(); + eastate.flags = 0u; + } + + if (!cvars.is_vmode() && cvars.is_icase()) + pos.ranges.make_caseunfoldedcharset(); + + eastate.type = st_character_class; + eastate.quantifier.reset(1); + } + else + { +#if !defined(SRELL_NO_UNICODE_POS) + if (!cvars.is_vmode()) +#endif + return this->set_error(regex_constants::error_property); + + u32array sequences; + + this->character_class.get_prawdata(sequences, eastate.char_num); +#if !defined(SRELL_FIXEDWIDTHLOOKBEHIND) + pos.split_seqs_and_ranges(sequences, cvars.is_icase(), cvars.is_back()); +#else + pos.split_seqs_and_ranges(sequences, cvars.is_icase(), false); +#endif + + eastate.quantifier.set(pos.length.first, pos.length.second); + + if (eastate.flags) // is_not. + return this->set_error(regex_constants::error_complement); + } + return true; + } +#endif // !defined(SRELL_NO_UNICODE_PROPERTY) + + default: + goto CHARACTER_ESCAPE; + } + + range_pairs predefclass(this->character_class.view(eastate.char_num)); + + if (eastate.flags) // is_not. + predefclass.negation(); + + pos.ranges.merge(predefclass); + + eastate.flags = 0u; + eastate.type = st_character_class; + return true; + } + + CHARACTER_ESCAPE: + + switch (eastate.char_num) + { + case char_alnum::ch_t: + eastate.char_num = char_ctrl::cc_htab; // '\t' 0x09:HT + break; + + case char_alnum::ch_n: + eastate.char_num = char_ctrl::cc_nl; // '\n' 0x0a:LF + break; + + case char_alnum::ch_v: + eastate.char_num = char_ctrl::cc_vtab; // '\v' 0x0b:VT + break; + + case char_alnum::ch_f: + eastate.char_num = char_ctrl::cc_ff; // '\f' 0x0c:FF + break; + + case char_alnum::ch_r: + eastate.char_num = char_ctrl::cc_cr; // '\r' 0x0d:CR + break; + + case char_alnum::ch_c: // \cX + if (curpos != end) + { + eastate.char_num = static_cast(*curpos | masks::asc_icase); + + if (eastate.char_num >= char_alnum::ch_a && eastate.char_num <= char_alnum::ch_z) + { + eastate.char_num = static_cast(*curpos++ & 0x1f); + break; + } + } + return this->set_error(regex_constants::error_escape); + + case char_alnum::ch_0: + eastate.char_num = char_ctrl::cc_nul; // '\0' 0x00:NUL + if (curpos != end && *curpos >= char_alnum::ch_0 && *curpos <= char_alnum::ch_9) + return this->set_error(regex_constants::error_escape); + break; + + case char_alnum::ch_x: // \xhh + eastate.char_num = translate_numbers(curpos, end, 16, 2, 2, 0xff); + break; + + case char_alnum::ch_u: // \uhhhh, \u{h~hhhhhh} + eastate.char_num = parse_escape_u(curpos, end); + break; + + // SyntaxCharacter, and '/'. + case meta_char::mc_caret: // '^' + case meta_char::mc_dollar: // '$' + case meta_char::mc_escape: // '\\' + case meta_char::mc_period: // '.' + case meta_char::mc_astrsk: // '*' + case meta_char::mc_plus: // '+' + case meta_char::mc_query: // '?' + case meta_char::mc_rbraop: // '(' + case meta_char::mc_rbracl: // ')' + case meta_char::mc_sbraop: // '[' + case meta_char::mc_sbracl: // ']' + case meta_char::mc_cbraop: // '{' + case meta_char::mc_cbracl: // '}' + case meta_char::mc_bar: // '|' + case char_other::co_slash: // '/' + break; + + default: + eastate.char_num = constants::invalid_u32value; + } + + if (eastate.char_num == constants::invalid_u32value) + return this->set_error(regex_constants::error_escape); + + return true; + } + + ui_l32 parse_escape_u(const ui_l32 *&curpos, const ui_l32 *const end) const + { + ui_l32 ucp; + + if (curpos == end) + return constants::invalid_u32value; + + if (*curpos == meta_char::mc_cbraop) + { + ucp = translate_numbers(++curpos, end, 16, 1, 0, constants::unicode_max_codepoint); + + if (curpos == end || *curpos != meta_char::mc_cbracl) + return constants::invalid_u32value; + + ++curpos; + } + else + { + ucp = translate_numbers(curpos, end, 16, 4, 4, 0xffff); + + if (ucp >= 0xd800 && ucp <= 0xdbff && (curpos + 6) <= end && *curpos == meta_char::mc_escape && curpos[1] == char_alnum::ch_u) + { + const ui_l32 *la = curpos + 2; + const ui_l32 nextucp = translate_numbers(la, end, 16, 4, 4, 0xffff); + + if (nextucp >= 0xdc00 && nextucp <= 0xdfff) + { + curpos = la; + ucp = ((ucp << 10) + nextucp) - 0x35fdc00; // - ((0xd800 << 10) + 0xdc00) + 0x10000. + } + } + } + return ucp; + } + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + + bool get_property_name_or_value(u32view &name_or_value, const ui_l32 *&curpos, const ui_l32 *const end) const + { + bool number_found = false; + + name_or_value.data_ = curpos; + + for (;; ++curpos) + { + if (curpos == end) + break; + + const ui_l32 curchar = *curpos; + + if (curchar >= char_alnum::ch_A && curchar <= char_alnum::ch_Z) + ; + else if (curchar >= char_alnum::ch_a && curchar <= char_alnum::ch_z) + ; + else if (curchar == char_other::co_ll) // '_' + ; + else if (curchar >= char_alnum::ch_0 && curchar <= char_alnum::ch_9) + number_found = true; + else + break; + } + + name_or_value.size_ = curpos - name_or_value.data_; + + // A string containing a digit cannot be a property name. + return number_found; + } + +#endif // !defined(SRELL_NO_UNICODE_PROPERTY) + +#if !defined(SRELL_NO_NAMEDCAPTURE) + + gname_string get_groupname(const ui_l32 *&curpos, const ui_l32 *const end, cvars_type &cvars) + { + charT mbstr[utf_traits::maxseqlen]; + gname_string groupname; + +#if !defined(SRELL_NO_UNICODE_PROPERTY) + cvars.idchecker.setup(); +#else + static_cast(cvars); +#endif + for (;;) + { + if (curpos == end) + { + groupname.clear(); + break; + } + + ui_l32 curchar = *curpos++; + + if (curchar == meta_char::mc_gt) // '>' + break; + + if (curchar == meta_char::mc_escape && curpos != end && *curpos == char_alnum::ch_u) // '\\', 'u'. + curchar = parse_escape_u(++curpos, end); + +#if defined(SRELL_NO_UNICODE_PROPERTY) + if (curchar != meta_char::mc_escape) +#else + if (cvars.idchecker.is_identifier(curchar, groupname.size() != 0)) +#endif + ; // OK. + else + curchar = constants::invalid_u32value; + + if (curchar == constants::invalid_u32value) + { + groupname.clear(); + break; + } + + const ui_l32 seqlen = utf_traits::to_codeunits(mbstr, curchar); + + groupname.append(mbstr, seqlen); + } + + if (groupname.size() == 0) + this->set_error(regex_constants::error_escape); + + return groupname; + } +#endif // !defined(SRELL_NO_NAMEDCAPTURE) + + void transform_seqdata(state_array &piece, const posdata_holder &pos) + { + ui_l32 seqlen = static_cast(pos.indices.size()); + state_type castate; + + castate.reset(st_character_class); + castate.char_num = this->character_class.register_newclass(pos.ranges); + + if (seqlen > 0) + { + const bool has_empty = pos.has_empty(); +#if !defined(SRELLDBG_NO_POS_OPT) + bool hooked = false; + state_size_type prevbranch_end = 0; +#else + state_size_type prevbranch_alt = 0; +#endif + state_type branchstate; + state_type jumpstate; + state_array branch; + + branch.resize(seqlen); + for (ui_l32 i = 0; i < seqlen; ++i) + branch[i].reset(); + + branchstate.reset(st_epsilon, epsilon_type::et_alt); + + jumpstate.reset(st_epsilon, epsilon_type::et_brnchend); // '/' + + for (--seqlen; seqlen >= 2; --seqlen) + { + ui_l32 offset = pos.indices[seqlen]; + const ui_l32 seqend = pos.indices[seqlen - 1]; + + if (offset != seqend) + { + branch.shrink(seqlen + 1); + branch[seqlen] = jumpstate; + + for (ui_l32 count = 0; offset < seqend; ++offset) + { + const ui_l32 seqch = pos.seqs[offset]; + state_type *const ost = &branch[count++]; + + ost->char_num = seqch & masks::cf_char; + this->NFA_states[0].flags |= ost->flags = (seqch & masks::cfolded) ? sflags::icase : 0; + + if (count == seqlen) + { +#if !defined(SRELLDBG_NO_POS_OPT) + state_size_type bpos = 0; + + for (state_size_type ppos = 0; ppos < piece.size();) + { + if (bpos + 1 == branch.size()) + { + piece.push_back_c(piece[ppos]); + + state_type &pst = piece[ppos]; + + pst.reset(st_epsilon, epsilon_type::et_alt); + pst.next1 = static_cast(piece.size()) - ppos - 1; + pst.next2 = static_cast(prevbranch_end) - ppos; + pst.flags |= sflags::hooking; + hooked = true; + + state_type &bst = piece[piece.size() - 1]; + + bst.next1 = bst.next1 - pst.next1; + bst.next2 = bst.next2 ? (bst.next2 - pst.next1) : 0; + bst.flags |= sflags::hookedlast; + goto SKIP_APPEND; + } + + state_type &pst = piece[ppos]; + +#if 0 + if (pst.type == st_epsilon) + ppos += pst.next1; + else +#endif + if (pst.char_num == branch[bpos].char_num) + { + ++bpos; + ppos += pst.next1; + } + else if (pst.next2) + ppos += pst.next2; + else + { + pst.next2 = static_cast(piece.size()) - ppos; + break; + } + } + + { + const state_size_type alen = branch.size() - bpos; + + if (piece.size()) + piece[prevbranch_end].next1 = piece.size() + alen - 1 - prevbranch_end; + + piece.append(branch, bpos, alen); + prevbranch_end = piece.size() - 1; + } + SKIP_APPEND: + count = 0; + +#else // defined(SRELLDBG_NO_POS_OPT) + + if (piece.size()) + { + state_type &laststate = piece[piece.size() - 1]; + + laststate.next1 = seqlen + 2; + piece[prevbranch_alt].next2 = static_cast(piece.size()) - prevbranch_alt; + } + prevbranch_alt = piece.size(); + piece.push_back(branchstate); + piece.append(branch); + count = 0; + +#endif // !defined(SRELLDBG_NO_POS_OPT) + } + } + } + } + + if (piece.size()) + { +#if !defined(SRELLDBG_NO_POS_OPT) + state_type &laststate = piece[prevbranch_end]; + + laststate.next1 = piece.size() + (has_empty ? 2 : 1) - prevbranch_end; + + branchstate.next2 = static_cast(piece.size()) + 1; + piece.insert(0, branchstate); +#else + state_type &laststate = piece[piece.size() - 1]; + + laststate.next1 = has_empty ? 3 : 2; + + piece[prevbranch_alt].next2 = static_cast(piece.size()) - prevbranch_alt; +#endif + } + + if (has_empty) + { + branchstate.next2 = 2; + piece.push_back(branchstate); + } + + piece.push_back(castate); + + branchstate.char_num = epsilon_type::et_ncgopen; + branchstate.next1 = 1; + branchstate.next2 = 0; + branchstate.quantifier.set(1, 0); + piece.insert(0, branchstate); + + branchstate.char_num = epsilon_type::et_ncgclose; + branchstate.quantifier.atmost = 1; + piece.push_back(branchstate); + +#if !defined(SRELLDBG_NO_POS_OPT) + if (hooked) + reorder_piece(piece); +#endif + } + } + + ui_l32 translate_numbers(const ui_l32 *&curpos, const ui_l32 *const end, const ui_l32 radix, const ui_l32 minsize, const ui_l32 maxsize, const ui_l32 maxvalue) const + { + ui_l32 count = 0; + ui_l32 u32value = 0; + ui_l32 num; + + for (; maxsize == 0 || count < maxsize; ++curpos, ++count) + { + if (curpos == end) + break; + + ui_l32 ch = *curpos; + + if (ch >= char_alnum::ch_0 && ch <= char_alnum::ch_9) + num = ch - char_alnum::ch_0; + else if (radix == 16) + { + ch |= masks::asc_icase; + + if (ch >= char_alnum::ch_a && ch <= char_alnum::ch_f) + num = ch - char_alnum::ch_a + 10; + else + break; + } + else + break; + + const ui_l32 nextvalue = u32value * radix + num; + + if (nextvalue > maxvalue || nextvalue < u32value) + break; + + u32value = nextvalue; + } + + if (count >= minsize) + return u32value; + + return constants::invalid_u32value; + } + + bool check_backreferences(cvars_type &cvars) + { + const state_size_type orgsize = this->NFA_states.size(); + simple_array gno_found(this->number_of_brackets); + state_array additions; + + gno_found.reset(); + + for (state_size_type backrefpos = 1; backrefpos < orgsize; ++backrefpos) + { + state_type &brs = this->NFA_states[backrefpos]; + + if (brs.type == st_roundbracket_close) + { + gno_found[brs.char_num] = 1; + } + else if (brs.type == st_backreference) + { + const ui_l32 &backrefno = brs.char_num; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + if (brs.flags & sflags::backrefno_unresolved) + { + if (backrefno > cvars.unresolved_gnames.size()) + return this->set_error(regex_constants::error_backref); // Internal error. + + brs.flags &= ~sflags::backrefno_unresolved; + + const ui_l32 *list = this->namedcaptures[cvars.unresolved_gnames[backrefno]]; + + if (list == NULL || *list < 1) + return this->set_error(regex_constants::error_backref); + + const ui_l32 num = list[0]; + state_type newbrs(brs); + + for (ui_l32 ino = 1; ino <= num; ++ino) + { + if (gno_found[list[ino]]) + { + newbrs.char_num = list[ino]; + additions.push_back(newbrs); + } + } + + if (additions.size() == 0) + goto REMOVE_BACKREF; + + brs.char_num = additions[0].char_num; + additions.erase(0); + + if (additions.size()) + { + const std::ptrdiff_t next1abs = static_cast(backrefpos + brs.next1); + const std::ptrdiff_t next2abs = static_cast(backrefpos + brs.next2); + + brs.next1 = static_cast(this->NFA_states.size() - backrefpos); + brs.next2 = static_cast(this->NFA_states.size() - backrefpos); + brs.flags |= sflags::hooking; + + const std::ptrdiff_t lastabs = static_cast(this->NFA_states.size() + additions.size() - 1); + state_type &laststate = additions.back(); + + laststate.flags |= sflags::hookedlast; + laststate.next1 = static_cast(next1abs - lastabs); + laststate.next2 = static_cast(next2abs - lastabs); + this->NFA_states.append(additions); + additions.clear(); + } + } + else +#endif + { + if (backrefno >= this->number_of_brackets) + return this->set_error(regex_constants::error_backref); + + if (!gno_found[backrefno]) + { + REMOVE_BACKREF: + if (brs.next1 == -1) + { + state_type &prevstate = this->NFA_states[backrefpos + brs.next1]; + + if (prevstate.is_asterisk_or_plus_for_onelen_atom()) + { + brs.next1 = prevstate.farnext() + brs.next1; + prevstate.next1 = 1; + prevstate.next2 = 0; + prevstate.char_num = epsilon_type::et_fmrbckrf; + } + } + + brs.type = st_epsilon; + brs.next2 = 0; + brs.char_num = epsilon_type::et_fmrbckrf; + } + } + } + } + if (orgsize != this->NFA_states.size()) + reorder_piece(this->NFA_states); + + return true; + } + +#if !defined(SRELLDBG_NO_1STCHRCLS) + + void create_firstchar_class() + { + range_pairs fcc; + + const bool canbe0length = gather_nextchars(fcc, static_cast(this->NFA_states[0].next1), 0u, false); + + if (canbe0length) + { + fcc.set_solerange(range_pair_helper(0, constants::unicode_max_codepoint)); + // Expressions would consist of assertions only, such as /^$/. + // We cannot but accept every codepoint. + } + + this->NFA_states[0].quantifier.is_greedy = this->character_class.register_newclass(fcc); + +#if !defined(SRELLDBG_NO_SCFINDER) || defined(SRELL_HAS_SSE42) + ui_l32 entrycu = constants::max_u32value; +#endif +#if defined(SRELL_HAS_SSE42) + charT sranges[16]; + const int maxnum = sizeof (charT) ? (16 / sizeof (charT)) : 0; + int curnum = 0; +#endif + ui_l32 cu2 = 0; + + for (typename range_pairs::size_type i = 0; i < fcc.size(); ++i) + { + const range_pair &range = fcc[i]; + + if (range.first > static_cast(utf_traits::maxcpvalue)) + break; + + const ui_l32 maxr2 = range.second <= static_cast(utf_traits::maxcpvalue) ? range.second : static_cast(utf_traits::maxcpvalue); + ui_l32 r1 = range.first; + + for (; r1 <= maxr2;) + { + const ui_l32 prev2 = cu2; + const ui_l32 cu1 = utf_traits::firstcodeunit(r1) & utf_traits::bitsetmask; + ui_l32 r2 = utf_traits::nextlengthchange(r1) - 1; + + if (r2 > maxr2) + r2 = maxr2; + + cu2 = utf_traits::firstcodeunit(r2) & utf_traits::bitsetmask; + +#if !defined(SRELLDBG_NO_BITSET) + for (ui_l32 cu = cu1; cu <= cu2; ++cu) + this->firstchar_class_bs.set(cu); +#endif +#if !defined(SRELLDBG_NO_SCFINDER) + if (entrycu != constants::invalid_u32value) + { + if (cu1 == cu2 && (entrycu == cu1 || entrycu == constants::max_u32value)) + entrycu = cu1; + else + entrycu = constants::invalid_u32value; + } +#endif +#if defined(SRELL_HAS_SSE42) + if (curnum >= 0) + { + if (curnum > 0 && (prev2 == cu1 || prev2 + 1 == cu1)) + { + sranges[curnum - 1] = static_cast(cu2); + } + else if (curnum < maxnum) + { + sranges[curnum++] = static_cast(cu1); + sranges[curnum++] = static_cast(cu2); + } + else + curnum = -1; + } +#endif + static_cast(prev2); + if (r2 == maxr2) + break; + + r1 = r2 + 1; + } + } + + if (entrycu < constants::max_u32value) + ++entrycu; + else + entrycu = 0; + +#if defined(SRELL_HAS_SSE42) +#if defined(SRELL_OMIT_CPUCHECK) + if (sizeof (charT) <= 2) +#else + if ((cpu_checker::x86simd() & 1) && sizeof (charT) <= 2) +#endif + { + if (curnum > 0) + { + entrycu |= curnum << 16; // Bit 16 may be overlapping but safe. curnum's bit 0 is always 0. + std::memcpy(&this->simdranges, sranges, 16); + } + } +#endif + +#if !defined(SRELLDBG_NO_SCFINDER) || defined(SRELL_HAS_SSE42) + this->NFA_states[0].char_num = entrycu; +#endif + } +#endif // !defined(SRELLDBG_NO_1STCHRCLS) + + bool gather_nextchars(range_pairs &nextcharclass, state_size_type pos, simple_array &checked, const ui_l32 bracket_number, const bool subsequent) const + { + bool canbe0length = false; + + for (;;) + { + const state_type &state = this->NFA_states[pos]; + + if (checked[pos]) + break; + + checked[pos] = 1; + + if (state.next2 + && (state.type != st_increment_counter) + && (state.type != st_save_and_reset_counter) + && (state.type != st_roundbracket_open) + && (state.type != st_repeat_in_push) + && (state.type != st_backreference || (state.next1 != state.next2)) + && (state.type != st_lookaround_open)) + if (gather_nextchars(nextcharclass, pos + state.next2, checked, bracket_number, subsequent)) + canbe0length = true; + + switch (state.type) + { + case st_character: + if (!(state.flags & sflags::icase)) + { + nextcharclass.join(range_pair_helper(state.char_num)); + } + else + { + ui_l32 table[ucf_constants::rev_maxset]; + const ui_l32 setnum = unicode_case_folding::do_caseunfolding(table, state.char_num); + + for (ui_l32 j = 0; j < setnum; ++j) + nextcharclass.join(range_pair_helper(table[j])); + } + return canbe0length; + + case st_character_class: + nextcharclass.merge(this->character_class.view(state.char_num)); + return canbe0length; + + case st_backreference: + { + const state_size_type nextpos = find_next1_of_bracketopen(state.char_num); + + gather_nextchars(nextcharclass, nextpos, state.char_num, subsequent); + } + break; + + case st_eol: + case st_bol: + case st_boundary: + if (subsequent) + nextcharclass.set_solerange(range_pair_helper(0, constants::unicode_max_codepoint)); + + break; + + case st_lookaround_open: + if (!state.flags && state.quantifier.is_greedy == 0) // !is_not. + { + gather_nextchars(nextcharclass, pos + 2, checked, 0u, subsequent); + } + else if (subsequent) + nextcharclass.set_solerange(range_pair_helper(0, constants::unicode_max_codepoint)); + + break; + + case st_roundbracket_close: + if (/* bracket_number == 0 || */ state.char_num != bracket_number) + break; + //@fallthrough@ + + case st_success: // == st_lookaround_close. + return true; + + default:; + } + + if (state.next1) + pos += state.next1; + else + break; + } + return canbe0length; + } + + bool gather_nextchars(range_pairs &nextcharclass, const state_size_type pos, const ui_l32 bracket_number, const bool subsequent) const + { + simple_array checked(this->NFA_states.size()); + + checked.reset(); + return gather_nextchars(nextcharclass, pos, checked, bracket_number, subsequent); + } + + state_size_type find_next1_of_bracketopen(const ui_l32 bracketno) const + { + for (state_size_type no = 0; no < this->NFA_states.size(); ++no) + { + const state_type &state = this->NFA_states[no]; + + if (state.type == st_roundbracket_open && state.char_num == bracketno) + return no + state.next1; + } + return 0; + } + + void finalise(const bool has_fcc) + { +#if !defined(SRELLDBG_NO_CCPOS) + this->character_class.finalise(); + + if (has_fcc) + { + const range_pair &posinfo = this->character_class.charclasspos(this->NFA_states[0].quantifier.is_greedy); + this->NFA_states[0].quantifier.set(posinfo.first, posinfo.second); + } +#endif + static_cast(has_fcc); + + for (state_size_type pos = 0; pos < this->NFA_states.size(); ++pos) + { + state_type &state = this->NFA_states[pos]; + +#if !defined(SRELLDBG_NO_ASTERISK_OPT) + if (state.next1 || state.type == st_character || state.type == st_character_class) +#else + if (state.next1) +#endif + state.next_state1 = &this->NFA_states[pos + state.next1]; + else + state.next_state1 = NULL; + + if (state.next2) + state.next_state2 = &this->NFA_states[pos + state.next2]; + else + state.next_state2 = NULL; + +#if !defined(SRELLDBG_NO_CCPOS) + if (state.type == st_character_class || state.type == st_bol || state.type == st_eol || state.type == st_boundary) + { + const range_pair &posinfo = this->character_class.charclasspos(state.char_num); + state.quantifier.set(posinfo.first, posinfo.second); + } +#endif // !defined(SRELLDBG_NO_CCPOS) + + if (state.type == st_character && state.flags & sflags::icase) + state.type = st_characteri; + } + } + + bool optimise(const cvars_type &cvars) + { + const bool needs_prefilter = +#if !defined(SRELLDBG_NO_BMH) + !this->bmdata && +#endif + !(this->soflags & regex_constants::sticky); + +#if !defined(SRELLDBG_NO_BRANCH_OPT2) + branch_optimisation2(); +#endif + +#if !defined(SRELLDBG_NO_MPREWINDER) + if (needs_prefilter) + find_better_es(1u, cvars); +#endif + +#if !defined(SRELLDBG_NO_ASTERISK_OPT) + asterisk_optimisation(); +#endif + +#if !defined(SRELLDBG_NO_BRANCH_OPT) + branch_optimisation(); +#endif + +#if !defined(SRELLDBG_NO_1STCHRCLS) + if (needs_prefilter) + create_firstchar_class(); +#endif + +#if !defined(SRELLDBG_NO_SKIP_EPSILON) + skip_epsilon(); +#endif + + static_cast(cvars); + return needs_prefilter; + } + +#if !defined(SRELLDBG_NO_SKIP_EPSILON) + + void skip_epsilon() + { + for (state_size_type pos = 0; pos < this->NFA_states.size(); ++pos) + { + state_type &state = this->NFA_states[pos]; + + if (state.next1) + state.next1 = static_cast(skip_nonbranch_epsilon(pos + state.next1)) - pos; + + if (state.next2) + state.next2 = static_cast(skip_nonbranch_epsilon(pos + state.next2)) - pos; + } + } + + state_size_type skip_nonbranch_epsilon(state_size_type pos) const + { + for (;;) + { + const state_type &state = this->NFA_states[pos]; + + if (state.type == st_epsilon && state.next2 == 0) + { + pos += state.next1; + continue; + } + break; + } + return pos; + } + +#endif + +#if !defined(SRELLDBG_NO_ASTERISK_OPT) + + void asterisk_optimisation() + { + const state_size_type orgsize = this->NFA_states.size(); +#if !defined(SRELLDBG_NO_SPLITCC) + range_pairs removed; +#endif + range_pairs curcc; + range_pairs nextcc; + state_array additions; + + for (state_size_type pos = 1u; pos < orgsize; ++pos) + { + state_type &curstate = this->NFA_states[pos]; + + if ((curstate.type == st_character || curstate.type == st_character_class) && !curstate.quantifier.is_same()) + { + const state_size_type bpos = pos + (curstate.next1 < 0 ? curstate.next1 : (curstate.quantifier.is_question() ? -1 : 0)); + + if (bpos == pos) + continue; + + state_type &bstate = this->NFA_states[bpos]; + const state_size_type nextno = bpos + bstate.farnext(); + const re_quantifier &bq = bstate.quantifier; + state_type orgcur(curstate); + + if (curstate.type == st_character) + { + curcc.set_solerange(range_pair_helper(curstate.char_num)); + if (curstate.flags & sflags::icase) + curcc.make_caseunfoldedcharset(); + } + else + { + this->character_class.copy_to(curcc, curstate.char_num); + if (curcc.size() == 0) // Means [], which always makes matching fail. + goto IS_EXCLUSIVE; + } + + additions.clear(); + + { + nextcc.clear(); + const bool canbe0length = gather_nextchars(nextcc, nextno, 0u, true); + + if (nextcc.size()) + { + if (!canbe0length || bq.is_greedy) + { +#if !defined(SRELLDBG_NO_SPLITCC) + curcc.split_ranges(removed, nextcc); + + range_pairs &kept = curcc; + + if (removed.size() == 0) // !curcc.is_overlap(nextcc) + goto IS_EXCLUSIVE; + + if (curstate.type == st_character_class && kept.size()) + { + curstate.char_num = kept.consists_of_one_character(); + + if (curstate.char_num != constants::invalid_u32value) + { + if (curstate.char_num & masks::cfolded) + { + curstate.char_num ^= masks::cfolded; + this->NFA_states[0].flags |= curstate.flags = sflags::icase; + } + curstate.type = st_character; + } + else + curstate.char_num = this->character_class.register_newclass(kept); + + curstate.flags |= (sflags::hooking | sflags::byn2); + curstate.next2 = static_cast(this->NFA_states.size()) - pos; + + additions.resize(2); + state_type &n0 = additions[0]; + state_type &n1 = additions[1]; + + n0.reset(st_epsilon, epsilon_type::et_ccastrsk); + n0.quantifier = bq; + n0.next2 = static_cast(nextno) - this->NFA_states.size(); + if (!n0.quantifier.is_greedy) + { + n0.next1 = n0.next2; + n0.next2 = 1; + } + + n1.reset(st_character_class, removed.consists_of_one_character()); + + if (n1.char_num != constants::invalid_u32value) + { + if (n1.char_num & masks::cfolded) + { + n1.char_num ^= masks::cfolded; + this->NFA_states[0].flags |= n1.flags = sflags::icase; + } + n1.type = st_character; + } + else + n1.char_num = this->character_class.register_newclass(removed); + + n1.next1 = static_cast(bq.is_infinity() ? pos : (pos + curstate.next1)) - this->NFA_states.size() - 1; +// n1.next2 = 0; + n1.flags |= sflags::hookedlast; + goto IS_EXCLUSIVE; + } + +#else // defined(SRELLDBG_NO_SPLITCC) + + if (!curcc.is_overlap(nextcc)) + goto IS_EXCLUSIVE; + +#endif // !defined(SRELLDBG_NO_SPLITCC) + } + } + else // nextcc.size() == 0 + if (!canbe0length || bq.is_greedy) + goto IS_EXCLUSIVE; + + continue; + } + IS_EXCLUSIVE: + + if (bstate.type != st_check_counter) + { + bstate.next1 = 1; + bstate.next2 = 0; + bstate.char_num = epsilon_type::et_aofmrast; + + if (curstate.next1 < 0) + curstate.next1 = 0; + } + else + { + if (bstate.quantifier.atleast != 0) + { + const std::ptrdiff_t addpos = static_cast(this->NFA_states.size()) + additions.size(); + const state_size_type srpos = bpos - 2; + const state_size_type rcpos = bpos - 1; + state_type &srstate = this->NFA_states[srpos]; + state_type &rcstate = this->NFA_states[rcpos]; + + if (bstate.quantifier.atleast <= 4) + { + orgcur.next1 = 1; + orgcur.next2 = 0; + orgcur.quantifier.reset(); + additions.append(bstate.quantifier.atleast, orgcur); + + orgcur.flags |= sflags::hooking; + orgcur.next1 = addpos - srpos; + + const std::ptrdiff_t movedsrpos = addpos + bstate.quantifier.atleast - 1; + + srstate.next1 = static_cast(bpos) - movedsrpos; + srstate.next2 = static_cast(rcpos) - movedsrpos; + srstate.flags |= sflags::hookedlast; + additions.back() = srstate; + + srstate = orgcur; + + bstate.quantifier.atmost -= bstate.quantifier.atleast; + } + else + { + additions.append(this->NFA_states, bpos, 4); + + srstate.next1 = addpos - srpos; + + rcstate.flags |= (sflags::hooking | sflags::byn2 | sflags::clrn2); + rcstate.next2 = addpos - rcpos; + + state_type &flcc = additions[additions.size() - 4]; + + (flcc.quantifier.is_greedy ? flcc.next2 : flcc.next1) = static_cast(bpos) - addpos; + flcc.quantifier.atmost = flcc.quantifier.atleast; + + orgcur.flags |= sflags::hookedlast; + orgcur.quantifier.atmost = orgcur.quantifier.atleast; + additions.back() = orgcur; + } + } + bstate.quantifier.atleast = bstate.quantifier.atmost; + + curstate.quantifier.atmost -= curstate.quantifier.atleast; + curstate.quantifier.atleast = 0; + } + + if (curstate.next2 == 0) + curstate.next2 = static_cast(nextno) - pos; + + this->NFA_states.append(additions); + } + } + if (orgsize != this->NFA_states.size()) + reorder_piece(this->NFA_states); + } + +#endif // !defined(SRELLDBG_NO_ASTERISK_OPT) + + void reorder_piece(state_array &piece) const + { + u32array newpos; + ui_l32 offset = 0; + + newpos.resize(piece.size() + 1, 0); + newpos[piece.size()] = static_cast(piece.size()); + + for (ui_l32 indx = 0; indx < piece.size(); ++indx) + { + if (newpos[indx] == 0) + { + newpos[indx] = indx + offset; + + state_type &st = piece[indx]; + + if (st.flags & sflags::hooking) + { + const std::ptrdiff_t next1or2 = (st.flags & sflags::byn2) ? (st.flags ^= sflags::byn2, st.next2) : st.next1; + st.flags ^= sflags::hooking; + + if (st.flags & sflags::clrn2) + st.flags ^= sflags::clrn2, st.next2 = 0; + + for (ui_l32 i = static_cast(indx + next1or2); i < piece.size(); ++i) + { + ++offset; + newpos[i] = indx + offset; + if (piece[i].flags & sflags::hookedlast) + { + piece[i].flags ^= sflags::hookedlast; + break; + } + } + } + } + else + --offset; + } + + state_array newpiece(piece.size()); + + for (state_size_type indx = 0; indx < piece.size(); ++indx) + { + state_type &st = piece[indx]; + + if (st.next1 != 0) + st.next1 = static_cast(newpos[indx + st.next1]) - newpos[indx]; + + if (st.next2 != 0) + st.next2 = static_cast(newpos[indx + st.next2]) - newpos[indx]; + + newpiece[newpos[indx]] = piece[indx]; + } + newpiece.swap(piece); + } + + bool check_if_backref_used(state_size_type pos, const ui_l32 number) const + { + for (; pos < this->NFA_states.size(); ++pos) + { + const state_type &state = this->NFA_states[pos]; + + if (state.type == st_backreference && state.char_num == number) + return true; + } + return false; + } + +#if !defined(SRELLDBG_NO_BRANCH_OPT) || !defined(SRELLDBG_NO_BRANCH_OPT2) + + state_size_type gather_if_char_or_charclass(range_pairs &charclass, state_size_type pos) const + { + for (;;) + { + const state_type &cst = this->NFA_states[pos]; + + if (cst.next2 != 0) + break; + + if (cst.type == st_character) + { + charclass.set_solerange(range_pair_helper(cst.char_num)); + if (cst.flags & sflags::icase) + charclass.make_caseunfoldedcharset(); + return pos; + } + else if (cst.type == st_character_class) + { + this->character_class.copy_to(charclass, cst.char_num); + return pos; + } + else if (cst.type == st_epsilon && cst.char_num != epsilon_type::et_jmpinlp && cst.char_num != epsilon_type::et_ncgclose && cst.char_num != epsilon_type::et_rvfmrcg && cst.char_num != epsilon_type::et_mfrfmrcg) + { + pos += cst.next1; + } + else + break; + } + return 0; + } +#endif // !defined(SRELLDBG_NO_BRANCH_OPT) || !defined(SRELLDBG_NO_BRANCH_OPT2) + +#if !defined(SRELLDBG_NO_BRANCH_OPT) + void branch_optimisation() + { + range_pairs nextcharclass1; + + for (state_size_type pos = 1; pos < this->NFA_states.size(); ++pos) + { + const state_type &state = this->NFA_states[pos]; + + if (state.is_alt()) + { + const state_size_type nextcharpos = gather_if_char_or_charclass(nextcharclass1, pos + state.next1); + + if (nextcharpos) + { + range_pairs nextcharclass2; + const bool canbe0length = gather_nextchars(nextcharclass2, pos + state.next2, 0u /* bracket_number */, true); + + if (!canbe0length && !nextcharclass1.is_overlap(nextcharclass2)) + { + state_type &branch = this->NFA_states[pos]; + state_type &next1 = this->NFA_states[nextcharpos]; + + next1.next2 = pos + branch.next2 - nextcharpos; + branch.next2 = 0; + branch.char_num = epsilon_type::et_bo1fmrbr; + } + } + } + } + } +#endif // !defined(SRELLDBG_NO_BRANCH_OPT) + +#if !defined(SRELLDBG_NO_BMH) + void setup_bmhdata() + { + const ui_l32 folded = this->NFA_states[0].flags & sflags::icase; + u32array u32s; + + for (state_size_type i = 1; i < this->NFA_states.size(); ++i) + { + const state_type &state = this->NFA_states[i]; + + if (state.is_ncgroup_open_or_close()) + continue; + + if (state.type != st_character) + return; + + if (folded && !(state.flags & sflags::icase) && unicode_case_folding::try_casefolding(state.char_num) != constants::invalid_u32value) + return; + + u32s.push_back_c(state.char_num); + } + + if (u32s.size() > 1) + { + this->bmdata = new bmh_type; + this->bmdata->setup(u32s, folded); + } + } +#endif // !defined(SRELLDBG_NO_BMH) + +#if !defined(SRELLDBG_NO_BRANCH_OPT2) + + void branch_optimisation2() + { + bool hooked = false; + range_pairs basealt1stch; + range_pairs nextalt1stch; + + for (state_size_type pos = 1; pos < this->NFA_states.size(); ++pos) + { + const state_type &curstate = this->NFA_states[pos]; + + if (curstate.is_alt()) + { + state_size_type precharchainpos = pos; + const state_size_type n1pos = gather_if_char_or_charclass(basealt1stch, pos + curstate.next1); + + if (n1pos != 0) + { + state_type &n1ref = this->NFA_states[n1pos]; + state_size_type n2pos = precharchainpos + curstate.next2; + state_size_type postcharchainpos = 0; + + for (;;) + { + state_type &n2ref = this->NFA_states[n2pos]; + const bool n2isalt = n2ref.is_alt(); + const state_size_type next2next1poso = n2pos + (n2isalt ? n2ref.next1 : 0); + const state_size_type next2next2pos = n2isalt ? n2pos + n2ref.next2 : 0; + const state_size_type next2next1pos = gather_if_char_or_charclass(nextalt1stch, next2next1poso); + + if (next2next1pos != 0) + { + const int relation = basealt1stch.relationship(nextalt1stch); + + if (relation == 0) + { + state_type &prechainalt = this->NFA_states[precharchainpos]; + state_type &becomes_unused = this->NFA_states[next2next1pos]; + const state_size_type next1next1pos = n1pos + n1ref.next1; + + becomes_unused.type = st_epsilon; + + if (next2next2pos) + { + becomes_unused.char_num = epsilon_type::et_bo2fmrbr; // '2' + + if (postcharchainpos == 0) + { + n2ref.next1 = next1next1pos - n2pos; + n2ref.next2 = next2next1pos - n2pos; + + n1ref.next1 = n2pos - n1pos; + n1ref.flags |= sflags::hooking; + n2ref.flags |= sflags::hookedlast; + hooked = true; + } + else + { + state_type &becomes_alt = this->NFA_states[postcharchainpos]; + + becomes_alt.char_num = epsilon_type::et_alt; // '|' <- '2' + becomes_alt.next2 = next2next1pos - postcharchainpos; + + n2ref.next2 = 0; + n2ref.char_num = epsilon_type::et_bo2skpd; // '!' + } + postcharchainpos = next2next1pos; + prechainalt.next2 = next2next2pos - precharchainpos; + } + else + { + if (postcharchainpos == 0) + { + becomes_unused.char_num = epsilon_type::et_alt; // '|' + becomes_unused.next2 = becomes_unused.next1; + becomes_unused.next1 = next1next1pos - next2next1pos; + + n1ref.next1 = next2next1pos - n1pos; + n1ref.flags |= sflags::hooking; + becomes_unused.flags |= sflags::hookedlast; + hooked = true; + } + else + { + state_type &becomes_alt = this->NFA_states[postcharchainpos]; + + becomes_alt.char_num = epsilon_type::et_alt; // '|' <- '2' + becomes_alt.next2 = next2next1pos + becomes_unused.next1 - postcharchainpos; + + becomes_unused.char_num = epsilon_type::et_bo2skpd; // '!' + } + prechainalt.next2 = 0; + prechainalt.char_num = epsilon_type::et_bo2fmrbr; // '2' + } + } + else if (relation == 1) + { + break; + } + else + precharchainpos = n2pos; + } + else + { + // Fix for bug210428. + // Original: /mm2|m|mm/ + // 1st step: /m(?:m2||m)/ <- No more optimisation can be performed. Must quit. + // 2nd step: /mm(?:2||)/ <- BUG. + break; + } + + if (next2next2pos == 0) + break; + + n2pos = next2next2pos; + } + } + } + } + + if (hooked) + reorder_piece(this->NFA_states); + } +#endif // !defined(SRELLDBG_NO_BRANCH_OPT2) + +#if !defined(SRELLDBG_NO_MPREWINDER) + + bool create_rewinder(const state_size_type end, const int needs_rerun, const cvars_type &cvars) + { + state_array newNFAs; + state_type rwstate; + + if (!reverse_atoms(newNFAs, this->NFA_states, 1u, end, cvars) || newNFAs.size() == 0u) + return false; + + for (state_size_type i = 0;; ++i) + { + if (i == newNFAs.size()) + return false; + if (newNFAs[i].is_character_or_class()) + break; + } + + rwstate.reset(st_lookaround_pop, meta_char::mc_eq); + rwstate.quantifier.atmost = 0; + newNFAs.insert(0, rwstate); + + rwstate.type = st_lookaround_open; + rwstate.next1 = static_cast(end + newNFAs.size() + 2) - 1; + rwstate.next2 = 1; + rwstate.quantifier.is_greedy = needs_rerun ? 3 : 2; // Match point rewinder. + // "singing" problem: /\w+ing/ against "singing" matches + // the entire "singing". However, if altered into + // /(?<=\K\w+)ing/ it matches "sing" only. To avoid this, + // after rewinding is finished rerunning is needed if the + // reversed states contain a variable length atom. + // TODO: This rerunning can be avoided if the reversed atoms + // are an exclusive sequence, like /\d+[:,]+\d+abcd/. + newNFAs.insert(0, rwstate); + + rwstate.type = st_lookaround_close; + rwstate.next1 = 0; + rwstate.next2 = 0; + newNFAs.append(1, rwstate); + + this->NFA_states.insert(1, newNFAs); + this->NFA_states[0].next2 = static_cast(newNFAs.size()) + 1; + + return true; + } + + bool reverse_atoms(state_array &revNFAs, const state_array &NFAs, state_size_type cur, const state_size_type send, const cvars_type &cvars) + { + const state_size_type orglen = send - cur; + state_array atomseq; + state_array revgrp; + state_type epsilon; + + epsilon.reset(st_epsilon, epsilon_type::et_rvfmrcg); + + revNFAs.clear(); + + for (; cur < send;) + { + const state_type &state = NFAs[cur]; + + switch (state.type) + { + case st_epsilon: + if (state.next2 != 0) + { + if (state.char_num != epsilon_type::et_alt) + { + const state_size_type repbgn = cur + state.nearnext(); + state_size_type repend = cur + state.farnext(); + + if (repend > send) + repend = send; + + if (repbgn >= repend) + return false; + + atomseq.clear(); + + if (NFAs[repbgn].is_character_or_class()) + { + atomseq.append(NFAs, cur, repend - cur); + + for (state_size_type i = 0; i < atomseq.size(); ++i) + { + state_type &s = atomseq[i]; + if (s.type == st_epsilon && !s.quantifier.is_greedy) + { + s.next2 = s.next1; + s.next1 = 1; + s.quantifier.is_greedy = 1; + } + } + + state_size_type inspos = 0; + + if (revNFAs.size()) + { + const state_type &r0 = revNFAs[0]; + const state_type &corcc = NFAs[repbgn]; + + if (r0.type == corcc.type && r0.char_num == corcc.char_num) + ++inspos; + else if (cur) + { + const state_type &ps = NFAs[cur - 1]; + + if (ps.type == st_epsilon && ps.char_num == epsilon_type::et_jmpinlp) + { + revNFAs[0] = ps; + ++inspos; + } + } + } + revNFAs.insert(inspos, atomseq); + cur = repend; + continue; + } + + ++cur; + + if (reverse_atoms(revgrp, NFAs, cur, repend, cvars)) + { + cur = repend; + revNFAs.insert(0, revgrp); + revNFAs.insert(0, epsilon); + continue; + } + } + return false; + } + revNFAs.insert(0, epsilon); + ++cur; + continue; + + case st_save_and_reset_counter: + { + if (cur + 5 >= send) + return false; + + const state_size_type ccpos = cur + 2; // state.next1; + const state_type &cc = NFAs[ccpos]; + const state_size_type icpos = ccpos + 1; // cc.nearnext(); +// const state_type &ic = NFAs[icpos]; + const state_size_type repbgn = icpos + 2; // ic.next1; + state_size_type repend = ccpos + cc.farnext(); + + if (repend > send) + repend = send; + + if (repbgn >= repend) + return false; + + atomseq.clear(); + + if (NFAs[repbgn].is_character_or_class()) + { + atomseq.append(NFAs, cur, repend - cur); + + state_type &s = atomseq[2]; + if (!s.quantifier.is_greedy) + { + s.next2 = s.next1; + s.next1 = 1; + s.quantifier.is_greedy = 1; + } + + revNFAs.insert(0, atomseq); + cur = repend; + continue; + } + + if (reverse_atoms(revgrp, NFAs, repbgn, repend, cvars)) + { + revNFAs.insert(0, revgrp); + + for (; cur < repbgn; ++cur) + atomseq.push_back(epsilon); + + revNFAs.insert(0, atomseq); + cur = repend; + continue; + } + return false; + } + + case st_roundbracket_open: + atomseq.clear(); + atomseq.push_back(epsilon); + atomseq.push_back(epsilon); + revNFAs.insert(0, atomseq); + cur += 2; + continue; + + case st_roundbracket_close: + case st_repeat_in_push: + case st_repeat_in_pop: + case st_check_0_width_repeat: + revNFAs.insert(0, epsilon); + ++cur; + continue; + + default:; + revNFAs.insert(0, state); + ++cur; + } + } + return revNFAs.size() == orglen; + } + + bool find_better_es(state_size_type cur, const cvars_type &cvars) + { + const state_array &NFAs = this->NFA_states; + state_size_type betterpos = 0u; + ui_l32 bp_cunum = constants::invalid_u32value; + int needs_rerun = 0; + int next_nr = 0; + state_size_type end = NFAs.size(); + range_pairs nextcc; + + for (; cur < end;) + { + const state_type &state = NFAs[cur]; + int final = 0; + + if (state.type == st_epsilon) + { + if (state.next2 != 0) + { + if ((next_nr & 2) == 0 && state.char_num != epsilon_type::et_alt) + { + const state_size_type repbgn = cur + state.nearnext(); + + if (NFAs[repbgn].is_character_or_class()) + { + next_nr |= 1; + cur += state.farnext(); + if (state.quantifier.is_greedy == 0) + next_nr |= 2; + continue; + } + } + final = 1; + } + else + { + if (state.char_num == epsilon_type::et_jmpinlp) + { + const state_size_type repbgn = cur + state.next1; + + if (NFAs[repbgn].is_character_or_class()) + next_nr |= 1; + else + end = cur + 1 + NFAs[cur + 1].farnext(); + + cur = repbgn; + } + else + ++cur; + + continue; + } + } + else if (state.type == st_save_and_reset_counter) + { + const state_size_type ccpos = cur + 2; // state.next1; + const state_type &cc = NFAs[ccpos]; + const state_size_type repend = ccpos + cc.farnext(); + const state_size_type icpos = ccpos + 1; // cc.nearnext(); +// const state_type &ic = NFAs[icpos]; + const state_size_type repbgn = icpos + 2; // ic.next1; + + if (NFAs[repbgn].is_character_or_class()) + { + if (!cc.quantifier.is_same()) + { + next_nr |= 1; + if (next_nr & 2) + { + final = 1; + goto SKIP_CCHECK; + } + } + + if (cc.quantifier.is_greedy == 0) + next_nr |= 2; + + if (cc.quantifier.atleast == 0) + { + cur = repend; + continue; + } + cur = repbgn; + SKIP_CCHECK:; + } + else + { + if (cc.quantifier.atleast == 0) + final = 1; + else + { + end = repend; + cur = repbgn; + continue; + } + } + } + else if (state.type == st_roundbracket_open) + { + cur += state.next1; + next_nr |= 1; + continue; + } + else if (state.type == st_repeat_in_push || state.type == st_bol || state.type == st_eol || state.type == st_boundary) + { + cur += state.next1; + continue; + } + else if (state.type == st_roundbracket_close || state.type == st_check_0_width_repeat) + { + ++cur; + continue; + } + else if (state.type == st_backreference || state.type == st_lookaround_open) + break; + + nextcc.clear(); + const bool canbe0length = gather_nextchars(nextcc, cur, 0u, false); + + if (canbe0length) + break; + + const ui_l32 cunum = nextcc.num_codeunits(); + + if (bp_cunum >= cunum) + { + betterpos = cur; + bp_cunum = cunum; + needs_rerun |= next_nr; + } + + if (final) + break; + + ++cur; + } + + return create_rewinder(betterpos, needs_rerun, cvars); + } + +#endif // !defined(SRELLDBG_NO_MPREWINDER) + +public: // For debug. + + void print_NFA_states(const int) const; +}; +// re_compiler + + } // namespace re_detail + +// ... "rei_compiler.hpp"] +// ["regex_sub_match.hpp" ... + +template +class sub_match : public std::pair +{ +public: + + typedef typename std::iterator_traits::value_type value_type; + typedef typename std::iterator_traits::difference_type difference_type; + typedef BidirectionalIterator iterator; + typedef std::basic_string string_type; + + bool matched; + +// constexpr sub_match(); // C++11. + + sub_match() : matched(false) + { + } + + difference_type length() const + { + return matched ? std::distance(this->first, this->second) : 0; + } + + operator string_type() const + { + return matched ? string_type(this->first, this->second) : string_type(); + } + + string_type str() const + { + return matched ? string_type(this->first, this->second) : string_type(); + } + + int compare(const sub_match &s) const + { + return str().compare(s.str()); + } + + int compare(const string_type &s) const + { + return str().compare(s); + } + + int compare(const value_type *const s) const + { + return str().compare(s); + } + + void swap(sub_match &s) + { + if (this != &s) + { + this->std::pair::swap(s); + std::swap(matched, s.matched); + } + } + + void set_(const typename re_detail::re_submatch_type &br, const BidirectionalIterator srchend) + { + this->matched = br.counter != 0; + + if (this->matched) + { + this->first = br.core.open_at; + this->second = br.core.close_at; + } + else + this->first = this->second = srchend; + } +}; + +// const reference, const reference. +template +bool operator==(const sub_match &lhs, const sub_match &rhs) +{ + return lhs.compare(rhs) == 0; // 1 +} + +template +bool operator!=(const sub_match &lhs, const sub_match &rhs) +{ + return lhs.compare(rhs) != 0; // 2 +} + +template +bool operator<(const sub_match &lhs, const sub_match &rhs) +{ + return lhs.compare(rhs) < 0; // 3 +} + +template +bool operator<=(const sub_match &lhs, const sub_match &rhs) +{ + return lhs.compare(rhs) <= 0; // 4 +} + +template +bool operator>=(const sub_match &lhs, const sub_match &rhs) +{ + return lhs.compare(rhs) >= 0; // 5 +} + +template +bool operator>(const sub_match &lhs, const sub_match &rhs) +{ + return lhs.compare(rhs) > 0; // 6 +} + +// basic_string, const reference. +template +bool operator==( + const std::basic_string::value_type, ST, SA> &lhs, + const sub_match &rhs +) +{ + return rhs.compare(lhs.c_str()) == 0; // 7 +} + +template +bool operator!=( + const std::basic_string::value_type, ST, SA> &lhs, + const sub_match &rhs +) +{ + return !(lhs == rhs); // 8 +} + +template +bool operator<( + const std::basic_string::value_type, ST, SA> &lhs, + const sub_match &rhs +) +{ + return rhs.compare(lhs.c_str()) > 0; // 9 +} + +template +bool operator>( + const std::basic_string::value_type, ST, SA> &lhs, + const sub_match &rhs +) +{ + return rhs < lhs; // 10 +} + +template +bool operator>=( + const std::basic_string::value_type, ST, SA> &lhs, + const sub_match &rhs +) +{ + return !(lhs < rhs); // 11 +} + +template +bool operator<=( + const std::basic_string::value_type, ST, SA> &lhs, + const sub_match &rhs +) +{ + return !(rhs < lhs); // 12 +} + +// const reference, basic_string. +template +bool operator==( + const sub_match &lhs, + const std::basic_string::value_type, ST, SA> &rhs +) +{ + return lhs.compare(rhs.c_str()) == 0; // 13 +} + +template +bool operator!=( + const sub_match &lhs, + const std::basic_string::value_type, ST, SA> &rhs +) +{ + return !(lhs == rhs); // 14 +} + +template +bool operator<( + const sub_match &lhs, + const std::basic_string::value_type, ST, SA> &rhs +) +{ + return lhs.compare(rhs.c_str()) < 0; // 15 +} + +template +bool operator>( + const sub_match &lhs, + const std::basic_string::value_type, ST, SA> &rhs +) +{ + return rhs < lhs; // 16 +} + +template +bool operator>=( + const sub_match &lhs, + const std::basic_string::value_type, ST, SA> &rhs +) +{ + return !(lhs < rhs); // 17 +} + +template +bool operator<=( + const sub_match &lhs, + const std::basic_string::value_type, ST, SA> &rhs +) +{ + return !(rhs < lhs); // 18 +} + +// pointer, const reference. +template +bool operator==( + typename std::iterator_traits::value_type const *lhs, + const sub_match &rhs +) +{ + return rhs.compare(lhs) == 0; // 19 +} + +template +bool operator!=( + typename std::iterator_traits::value_type const *lhs, + const sub_match &rhs +) +{ + return !(lhs == rhs); // 20 +} + +template +bool operator<( + typename std::iterator_traits::value_type const *lhs, + const sub_match &rhs +) +{ + return rhs.compare(lhs) > 0; // 21 +} + +template +bool operator>( + typename std::iterator_traits::value_type const *lhs, + const sub_match &rhs +) +{ + return rhs < lhs; // 22 +} + +template +bool operator>=( + typename std::iterator_traits::value_type const *lhs, + const sub_match &rhs +) +{ + return !(lhs < rhs); // 23 +} + +template +bool operator<=( + typename std::iterator_traits::value_type const *lhs, + const sub_match &rhs +) +{ + return !(rhs < lhs); // 24 +} + +// const reference, pointer. +template +bool operator==( + const sub_match &lhs, + typename std::iterator_traits::value_type const *rhs +) +{ + return lhs.compare(rhs) == 0; // 25 +} + +template +bool operator!=( + const sub_match &lhs, + typename std::iterator_traits::value_type const *rhs +) +{ + return !(lhs == rhs); // 26 +} + +template +bool operator<( + const sub_match &lhs, + typename std::iterator_traits::value_type const *rhs +) +{ + return lhs.compare(rhs) < 0; // 27 +} + +template +bool operator>( + const sub_match &lhs, + typename std::iterator_traits::value_type const *rhs +) +{ + return rhs < lhs; // 28 +} + +template +bool operator>=( + const sub_match &lhs, + typename std::iterator_traits::value_type const *rhs +) +{ + return !(lhs < rhs); // 29 +} + +template +bool operator<=( + const sub_match &lhs, + typename std::iterator_traits::value_type const *rhs +) +{ + return !(rhs < lhs); // 30 +} + +// charT, const reference. +template +bool operator==( + typename std::iterator_traits::value_type const &lhs, + const sub_match &rhs +) +{ + return rhs.compare(typename sub_match::string_type(1, lhs)) == 0; // 31 +} + +template +bool operator!=( + typename std::iterator_traits::value_type const &lhs, + const sub_match &rhs +) +{ + return !(lhs == rhs); // 32 +} + +template +bool operator<( + typename std::iterator_traits::value_type const &lhs, + const sub_match &rhs +) +{ + return rhs.compare(typename sub_match::string_type(1, lhs)) > 0; // 33 +} + +template +bool operator>( + typename std::iterator_traits::value_type const &lhs, + const sub_match &rhs +) +{ + return rhs < lhs; // 34 +} + +template +bool operator>=( + typename std::iterator_traits::value_type const &lhs, + const sub_match &rhs +) +{ + return !(lhs < rhs); // 35 +} + +template +bool operator<=( + typename std::iterator_traits::value_type const &lhs, + const sub_match &rhs +) +{ + return !(rhs < lhs); // 36 +} + +// const reference, charT. +template +bool operator==( + const sub_match &lhs, + typename std::iterator_traits::value_type const &rhs +) +{ + return lhs.compare(typename sub_match::string_type(1, rhs)) == 0; // 37 +} + +template +bool operator!=( + const sub_match &lhs, + typename std::iterator_traits::value_type const &rhs +) +{ + return !(lhs == rhs); // 38 +} + +template +bool operator<( + const sub_match &lhs, + typename std::iterator_traits::value_type const &rhs +) +{ + return lhs.compare(typename sub_match::string_type(1, rhs)) < 0; // 39 +} + +template +bool operator>( + const sub_match &lhs, + typename std::iterator_traits::value_type const &rhs +) +{ + return rhs < lhs; // 40 +} + +template +bool operator>=( + const sub_match &lhs, + typename std::iterator_traits::value_type const &rhs +) +{ + return !(lhs < rhs); // 41 +} + +template +bool operator<=( + const sub_match &lhs, + typename std::iterator_traits::value_type const &rhs +) +{ + return !(rhs < lhs); // 42 +} + +template +std::basic_ostream &operator<<(std::basic_ostream &os, const sub_match &m) +{ + return (os << m.str()); +} + +// ... "regex_sub_match.hpp"] +// ["regex_match_results.hpp" ... + +template > > +class match_results +{ +private: + + typedef std::vector, Allocator> sub_match_array_; + +public: + + typedef sub_match value_type; + typedef const value_type & const_reference; + typedef const_reference reference; + typedef typename sub_match_array_::const_iterator const_iterator; + typedef const_iterator iterator; + typedef typename std::iterator_traits::difference_type difference_type; + typedef typename sub_match_array_::size_type size_type; + typedef Allocator allocator_type; + typedef typename std::iterator_traits::value_type char_type; + typedef std::basic_string string_type; + typedef typename re_detail::concon_view contiguous_container_view; + +public: + + explicit match_results(const Allocator &a = Allocator()) : ready_(0u), sub_matches_(a) + { + } + + match_results(const match_results &m) + { + operator=(m); + } + +#if defined(__cpp_rvalue_references) + match_results(match_results &&m) SRELL_NOEXCEPT + { + operator=(std::move(m)); + } +#endif + + match_results &operator=(const match_results &m) + { + if (this != &m) + { +// this->sstate_ = m.sstate_; + this->ready_ = m.ready_; + this->sub_matches_ = m.sub_matches_; + this->prefix_ = m.prefix_; + this->suffix_ = m.suffix_; + this->base_ = m.base_; +#if !defined(SRELL_NO_NAMEDCAPTURE) + this->gnames_ = m.gnames_; +#endif + } + return *this; + } + +#if defined(__cpp_rvalue_references) + match_results &operator=(match_results &&m) SRELL_NOEXCEPT + { + if (this != &m) + { +// this->sstate_ = std::move(m.sstate_); + this->ready_ = m.ready_; + this->sub_matches_ = std::move(m.sub_matches_); + this->prefix_ = std::move(m.prefix_); + this->suffix_ = std::move(m.suffix_); + this->base_ = m.base_; +#if !defined(SRELL_NO_NAMEDCAPTURE) + this->gnames_ = std::move(m.gnames_); +#endif + } + return *this; + } +#endif + +// ~match_results(); + + bool ready() const + { + return (ready_ & 1u) ? true : false; + } + + size_type size() const + { + return sub_matches_.size(); + } + + size_type max_size() const + { + return sub_matches_.max_size(); + } + + bool empty() const + { + return size() == 0; + } + + difference_type length(const size_type sub = 0) const + { + return (*this)[sub].length(); + } + + difference_type position(const size_type sub = 0) const + { + return std::distance(base_, (*this)[sub].first); + } + + string_type str(const size_type sub = 0) const + { + return (*this)[sub].str(); + } + + const_reference operator[](const size_type n) const + { + return n < sub_matches_.size() ? sub_matches_[n] : unmatched_; + } + +#if !defined(SRELL_NO_NAMEDCAPTURE) + + difference_type length(const string_type &sub) const + { + return (*this)[sub].length(); + } + + difference_type position(const string_type &sub) const + { + return std::distance(base_, (*this)[sub].first); + } + + string_type str(const string_type &sub) const + { + return (*this)[sub].str(); + } + + const_reference operator[](const string_type &sub) const + { + const re_detail::ui_l32 backrefno = lookup_backref_number(sub.data(), sub.data() + sub.size()); + + return backrefno != gnamemap_type::notfound ? sub_matches_[backrefno] : unmatched_; + } + + // In the following 4 functions, CharType is substituted for char_type. + // If there are overloads whose parameter is const char_type *, when + // the argument is the literal 0, overload resolution fails between + // const char_type * and size_type. + + template + difference_type length(const CharType *sub) const + { + return (*this)[sub].length(); + } + + template + difference_type position(const CharType *sub) const + { + return std::distance(base_, (*this)[sub].first); + } + + template + string_type str(const CharType *sub) const + { + return (*this)[sub].str(); + } + + template + const_reference operator[](const CharType *sub) const +// requires std::is_same_v + { + const re_detail::ui_l32 backrefno = lookup_backref_number(sub, sub + std::char_traits::length(sub)); + + return backrefno != gnamemap_type::notfound ? sub_matches_[backrefno] : unmatched_; + } + +#endif // !defined(SRELL_NO_NAMEDCAPTURE) + + const_reference prefix() const + { + return prefix_; + } + + const_reference suffix() const + { + return suffix_; + } + + const_iterator begin() const + { + return sub_matches_.begin(); + } + + const_iterator end() const + { + return sub_matches_.end(); + } + + const_iterator cbegin() const + { + return sub_matches_.begin(); + } + + const_iterator cend() const + { + return sub_matches_.end(); + } + + template + OutputIter format( + OutputIter out, + const char_type *fmt_first, + const char_type *const fmt_last, + regex_constants::match_flag_type /* flags */ = regex_constants::format_default + ) const + { + if (this->ready() && !this->empty()) + { +#if !defined(SRELL_NO_NAMEDCAPTURE) + const bool no_groupnames = gnames_.size() == 0; +#endif + const value_type &m0 = (*this)[0]; + + while (fmt_first != fmt_last) + { + if (*fmt_first != static_cast(re_detail::meta_char::mc_dollar)) // '$' + { + *out++ = *fmt_first++; + continue; + } + + ++fmt_first; + if (fmt_first == fmt_last) + { + *out++ = re_detail::meta_char::mc_dollar; // '$'; + } + else if (*fmt_first == static_cast(re_detail::char_other::co_amp)) // '&', $& + { + out = std::copy(m0.first, m0.second, out); + ++fmt_first; + } + else if (*fmt_first == static_cast(re_detail::char_other::co_grav)) // '`', $`, prefix. + { + out = std::copy(this->prefix().first, this->prefix().second, out); + ++fmt_first; + } + else if (*fmt_first == static_cast(re_detail::char_other::co_apos)) // '\'', $', suffix. + { + out = std::copy(this->suffix().first, this->suffix().second, out); + ++fmt_first; + } +#if !defined(SRELL_NO_NAMEDCAPTURE) + else if (*fmt_first == static_cast(re_detail::meta_char::mc_lt) && !no_groupnames) // '<', $< + { + const char_type *const lt_pos = fmt_first; + + for (++fmt_first;; ++fmt_first) + { + if (fmt_first == fmt_last) + { + fmt_first = lt_pos; + *out++ = re_detail::meta_char::mc_dollar; // '$'; + break; + } + + if (*fmt_first == static_cast(re_detail::meta_char::mc_gt)) + { + const re_detail::ui_l32 backref_number = lookup_backref_number(lt_pos + 1, fmt_first); + + if (backref_number != gnamemap_type::notfound) + { + const value_type &mn = (*this)[backref_number]; + + if (mn.matched) + out = std::copy(mn.first, mn.second, out); + } + ++fmt_first; + break; + } + } + } +#endif // !defined(SRELL_NO_NAMEDCAPTURE) + else + { + const char_type *const afterdollar_pos = fmt_first; + size_type backref_number = 0; + + if (fmt_first != fmt_last && *fmt_first >= static_cast(re_detail::char_alnum::ch_0) && *fmt_first <= static_cast(re_detail::char_alnum::ch_9)) // '0'-'9' + { + backref_number += *fmt_first - re_detail::char_alnum::ch_0; // '0'; + + if (++fmt_first != fmt_last && *fmt_first >= static_cast(re_detail::char_alnum::ch_0) && *fmt_first <= static_cast(re_detail::char_alnum::ch_9)) // '0'-'9' + { + backref_number *= 10; + backref_number += *fmt_first - re_detail::char_alnum::ch_0; // '0'; + ++fmt_first; + } + } + + if (backref_number && backref_number < this->size()) + { + const value_type &mn = (*this)[backref_number]; + + if (mn.matched) + out = std::copy(mn.first, mn.second, out); + } + else + { + *out++ = re_detail::meta_char::mc_dollar; // '$'; + + fmt_first = afterdollar_pos; + if (*fmt_first == static_cast(re_detail::meta_char::mc_dollar)) + ++fmt_first; + } + } + } + } + return out; + } + + template + OutputIter format( + OutputIter out, + const std::basic_string &fmt, + regex_constants::match_flag_type flags = regex_constants::format_default + ) const + { + return format(out, fmt.data(), fmt.data() + fmt.size(), flags); + } + + template + std::basic_string format( + const string_type &fmt, + regex_constants::match_flag_type flags = regex_constants::format_default + ) const + { + std::basic_string result; + + format(std::back_inserter(result), fmt, flags); + return result; + } + + string_type format(const char_type *fmt, regex_constants::match_flag_type flags = regex_constants::format_default) const + { + string_type result; + + format(std::back_inserter(result), fmt, fmt + std::char_traits::length(fmt), flags); + return result; + } + + allocator_type get_allocator() const + { + return allocator_type(); + } + + void swap(match_results &that) + { + { + const re_detail::ui_l32 tmp(ready_); + ready_ = that.ready_; + that.ready_ = tmp; + } + sub_matches_.swap(that.sub_matches_); + prefix_.swap(that.prefix_); + suffix_.swap(that.suffix_); + std::swap(base_, that.base_); +#if !defined(SRELL_NO_NAMEDCAPTURE) + gnames_.swap(that.gnames_); +#endif + } + + regex_constants::error_type ecode() const + { + return static_cast(ready_ >> 1); + } + +public: // For internal. + + typedef match_results match_results_type; + typedef typename match_results_type::size_type match_results_size_type; + typedef typename re_detail::re_search_state search_state_type; +#if !defined(SRELL_NO_NAMEDCAPTURE) + typedef typename re_detail::groupname_mapper gnamemap_type; +#endif + + search_state_type sstate_; + + void clear_() + { + ready_ = 0u; + sub_matches_.clear(); +// prefix_.matched = false; +// suffix_.matched = false; +#if !defined(SRELL_NO_NAMEDCAPTURE) + gnames_.clear(); +#endif + } + +#if !defined(SRELL_NO_NAMEDCAPTURE) + bool set_match_results_(const re_detail::ui_l32 num_of_brackets, const gnamemap_type &gnames) +#else + bool set_match_results_(const re_detail::ui_l32 num_of_brackets) +#endif + { + sub_matches_.resize(num_of_brackets); + + sub_matches_[0].matched = true; + + for (re_detail::ui_l32 i = 1; i < num_of_brackets; ++i) + sub_matches_[i].set_(sstate_.bracket[i], sstate_.srchend); + + base_ = sstate_.lblim; + prefix_.first = sstate_.srchbegin; + prefix_.second = sub_matches_[0].first = sstate_.curbegin; + suffix_.first = sub_matches_[0].second = sstate_.ssc.iter; + suffix_.second = sstate_.srchend; + + prefix_.matched = prefix_.first != prefix_.second; + suffix_.matched = suffix_.first != suffix_.second; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + gnames_ = gnames; +#endif + ready_ = 1u; + return true; + } + + bool set_match_results_bmh_() + { + sub_matches_.resize(1); +// value_type &m0 = sub_matches_[0]; + + sub_matches_[0].matched = true; + + base_ = sstate_.lblim; + prefix_.first = sstate_.srchbegin; + prefix_.second = sub_matches_[0].first = sstate_.ssc.iter; + suffix_.first = sub_matches_[0].second = sstate_.nextpos; + suffix_.second = sstate_.srchend; + + prefix_.matched = prefix_.first != prefix_.second; + suffix_.matched = suffix_.first != suffix_.second; + + ready_ = 1u; + return true; + } + + void set_prefix1_(const BidirectionalIterator pf) + { + prefix_.first = pf; + } + + void update_prefix1_(const BidirectionalIterator pf) + { + prefix_.first = pf; + prefix_.matched = prefix_.first != prefix_.second; + } + + void update_prefix2_(const BidirectionalIterator ps) + { + prefix_.second = ps; + prefix_.matched = prefix_.first != prefix_.second; + } + + void update_m0_(const BidirectionalIterator mf, const BidirectionalIterator ms) + { + sub_matches_.resize(1); + + sub_matches_[0].first = mf; + sub_matches_[0].second = ms; + sub_matches_[0].matched = true; + + prefix_.first = prefix_.second = mf; + } + + bool set_as_failed_(const re_detail::ui_l32 reason) + { + ready_ = reason ? (reason << 1) : 1u; + return false; + } + +#if !defined(SRELL_NO_NAMEDCAPTURE) + + typename gnamemap_type::gname_string lookup_gname_(const re_detail::ui_l32 gno) const + { + return gnames_[gno]; + } + +#endif + +private: + +#if !defined(SRELL_NO_NAMEDCAPTURE) + + re_detail::ui_l32 lookup_backref_number(const char_type *begin, const char_type *const end) const + { + const re_detail::ui_l32 *list = gnames_[typename gnamemap_type::view_type(begin, end - begin)]; + re_detail::ui_l32 gno = gnamemap_type::notfound; + + if (list) + { + const re_detail::ui_l32 num = list[0]; + + for (re_detail::ui_l32 i = 1; i <= num; ++i) + { + gno = list[i]; + if (gno < static_cast(sub_matches_.size()) && sub_matches_[gno].matched) + break; + } + } + return gno; + } + +#endif // !defined(SRELL_NO_NAMEDCAPTURE) + +public: // For debug. + + template + void print_sub_matches(const BasicRegexT &, const int) const; + void print_addresses(const value_type &, const char *const) const; + +private: + + re_detail::ui_l32 ready_; + sub_match_array_ sub_matches_; + value_type prefix_; + value_type suffix_; + value_type unmatched_; + BidirectionalIterator base_; + +#if !defined(SRELL_NO_NAMEDCAPTURE) + gnamemap_type gnames_; +#endif +}; + +template +void swap( + match_results &m1, + match_results &m2 +) +{ + m1.swap(m2); +} + +template +bool operator==( + const match_results &m1, + const match_results &m2 +) +{ + if (!m1.ready() && !m2.ready()) + return true; + + if (m1.ready() && m2.ready()) + { + if (m1.empty() && m2.empty()) + return true; + + if (!m1.empty() && !m2.empty()) + { + return m1.prefix() == m2.prefix() && m1.size() == m2.size() && std::equal(m1.begin(), m1.end(), m2.begin()) && m1.suffix() == m2.suffix(); + } + } + return false; +} + +template +bool operator!=( + const match_results &m1, + const match_results &m2 +) +{ + return !(m1 == m2); +} + +// ... "regex_match_results.hpp"] +// ["rei_algorithm.hpp" ... + + namespace re_detail + { + +struct is_cont_iter { enum { is_ci = 1 }; }; +struct non_cont_iter { enum { is_ci = 0 }; }; + +template +class re_object : public re_compiler +{ +public: + + template + bool search + ( + const BidirectionalIterator begin, + const BidirectionalIterator end, + const BidirectionalIterator lookbehind_limit, + match_results &results, + const regex_constants::match_flag_type flags + ) const + { + ui_l32 reason = 0; + + results.clear_(); + + if (this->NFA_states.size()) + { + typedef typename std::iterator_traits bi_traits; + typedef typename contiguous_checker::itype ci_checker; +#if defined(SRELL_HAS_SSE42) + typedef ci_checker simd_ac; +#else + typedef non_cont_iter simd_ac; +#endif + re_search_state &sstate = results.sstate_; + + sstate.init(begin, end, lookbehind_limit, flags | static_cast(this->soflags & regex_constants::sticky)); + +#if !defined(SRELLDBG_NO_BMH) + if (this->bmdata && !(sstate.flags & regex_constants::match_continuous)) + { + typedef typename bi_traits::iterator_category ic; + + if (this->NFA_states[0].flags == 0 ? this->bmdata->do_casesensitivesearch(sstate, ic()) : this->bmdata->do_icasesearch(sstate, ic())) + return results.set_match_results_bmh_(); + } + else +#endif // !defined(SRELLDBG_NO_BMH) + { + sstate.init2(this->number_of_brackets, this->number_of_counters, this->number_of_repeats); + + if (sstate.flags & regex_constants::match_continuous) + { + sstate.entry_state = this->NFA_states[0].next_state2; + + sstate.ssc.iter = sstate.nextpos; + +#if defined(SRELL_NO_LIMIT_COUNTER) + sstate.reset(); +#else + sstate.reset(this->limit_counter); +#endif + reason = do_match(sstate); + } + else + { + sstate.entry_state = this->NFA_states[0].next_state1; + +#if !defined(SRELLDBG_NO_SCFINDER) +SRELL_NO_VCWARNING(4127) + if ((this->NFA_states[0].char_num & static_cast(utf_traits::ecmask)) +#if defined(SRELL_HAS_SSE42) + && ((simd_ac::is_ci == 0) || ((cpu_checker::x86simd() & 2) +#if !defined(_MSC_VER) || (((_HAS_CXX17 + 0) > 0) && (!defined(_MSVC_STL_UPDATE) || (_MSVC_STL_UPDATE < 202408L))) + && (sizeof (typename bi_traits::value_type) != 2) +#endif + )) +#endif + ) +SRELL_NO_VCWARNING_END + { + reason = do_search_sc(sstate, ci_checker()); + } + else +#endif // !defined(SRELLDBG_NO_SCFINDER) + { + reason = do_search(sstate, simd_ac()); + } + } + + if (reason == 1) + { +#if !defined(SRELL_NO_NAMEDCAPTURE) + return results.set_match_results_(this->number_of_brackets, this->namedcaptures); +#else + return results.set_match_results_(this->number_of_brackets); +#endif + } + +#if !defined(SRELL_NO_THROW) + if (reason && !(this->soflags & regex_constants::quiet)) + throw regex_error(static_cast(reason)); +#endif + } + } + return results.set_as_failed_(reason); + } + +private: + + typedef typename traits::utf_traits utf_traits; + +#if defined(SRELL_HAS_SSE42) + + template + SRELL_AT_SSE42 ui_l32 do_search(re_search_state &sstate, const is_cont_iter) const + { + typedef typename std::iterator_traits::value_type char_type2; + +SRELL_NO_VCWARNING(4127) + if SRELL_IFCE (sizeof (charT) == sizeof (char_type2)) +SRELL_NO_VCWARNING_END + { + const int numofranges = static_cast(this->NFA_states[0].char_num >> 16) & 0x1e; + + if (numofranges) + { + const int borw = sizeof (char_type2) == 1 ? 4 : 5; + const int maxsize = sizeof (char_type2) == 1 ? 16 : 8; + const __m128i sranges = this->simdranges; + + for (; (sstate.srchend - sstate.nextpos) >= maxsize;) + { + __m128i data; + std::memcpy(&data, &*sstate.nextpos, 16); + const int pos = _mm_cmpestri(sranges, numofranges, data, maxsize, borw); + + if (pos == maxsize) + { + sstate.nextpos += maxsize; + continue; + } + sstate.nextpos += pos; + sstate.ssc.iter = sstate.nextpos; + +SRELL_NO_VCWARNING(4127) + if (utf_traits::maxseqlen > 1 && utf_traits::is_mculeading(*sstate.nextpos & utf_traits::bitsetmask)) +SRELL_NO_VCWARNING_END + { + const ui_l32 cp = utf_traits::codepoint_inc(sstate.nextpos, sstate.srchend); + const re_quantifier &r0q = this->NFA_states[0].quantifier; + +#if !defined(SRELLDBG_NO_CCPOS) + if (!this->character_class.is_included(r0q.atleast, r0q.atmost, cp)) +#else + if (!this->character_class.is_included(r0q.is_greedy, cp)) +#endif + continue; + } + else + ++sstate.nextpos; + +#if defined(SRELL_NO_LIMIT_COUNTER) + sstate.reset(); +#else + sstate.reset(this->limit_counter); +#endif + const ui_l32 reason = do_match(sstate); + if (reason) + return reason; + } + } + } + return do_search(sstate, non_cont_iter()); + } + +#endif // defined(SRELL_HAS_SSE42) + + template + ui_l32 do_search(re_search_state &sstate, const non_cont_iter) const + { + for (;;) + { + const bool final = sstate.nextpos == sstate.srchend; + + sstate.ssc.iter = sstate.nextpos; + + if (!final) + { +#if defined(SRELLDBG_NO_1STCHRCLS) + utf_traits::codepoint_inc(sstate.nextpos, sstate.srchend); +#else + #if !defined(SRELLDBG_NO_BITSET) + const ui_l32 cu = *sstate.nextpos & utf_traits::bitsetmask; + + if (!this->firstchar_class_bs.test(cu)) + { + ++sstate.nextpos; + continue; + } + +SRELL_NO_VCWARNING(4127) + if (utf_traits::maxseqlen > 1 && utf_traits::is_mculeading(cu)) +SRELL_NO_VCWARNING_END + { + const ui_l32 cp = utf_traits::codepoint_inc(sstate.nextpos, sstate.srchend); + const re_quantifier &r0q = this->NFA_states[0].quantifier; + +#if !defined(SRELLDBG_NO_CCPOS) + if (!this->character_class.is_included(r0q.atleast, r0q.atmost, cp)) +#else + if (!this->character_class.is_included(r0q.is_greedy, cp)) +#endif + continue; + } + else + ++sstate.nextpos; + #else + const ui_l32 firstchar = utf_traits::codepoint_inc(sstate.nextpos, sstate.srchend); + + const re_quantifier &r0q = this->NFA_states[0].quantifier; + +#if !defined(SRELLDBG_NO_CCPOS) + if (!this->character_class.is_included(r0q.atleast, r0q.atmost, firstchar)) +#else + if (!this->character_class.is_included(r0q.is_greedy, firstchar)) +#endif + continue; + #endif +#endif // defined(SRELLDBG_NO_1STCHRCLS) + } + // Even when final == true, we have to try for such expressions + // as "" =~ /^$/ or "..." =~ /$/. + +#if defined(SRELL_NO_LIMIT_COUNTER) + sstate.reset(/* first */); +#else + sstate.reset(/* first, */ this->limit_counter); +#endif + const ui_l32 reason = do_match(sstate); + if (reason) + return reason; + + if (final) + break; + } + return 0; + } + +#if !defined(SRELLDBG_NO_SCFINDER) + + template + ui_l32 do_search_sc(re_search_state &sstate, const is_cont_iter) const + { + typedef typename std::iterator_traits::value_type char_type2; + const char_type2 ec = static_cast(this->NFA_states[0].char_num & static_cast(utf_traits::ecmask)) - 1; + const bool ismcul = utf_traits::is_mculeading(ec); + + static_cast(ismcul); + + for (; sstate.nextpos < sstate.srchend;) + { + const char_type2 *const bgnpos = find_(&*sstate.nextpos, sstate.srchend - sstate.nextpos, ec); + + if (bgnpos) + { + sstate.nextpos += bgnpos - &*sstate.nextpos; + sstate.ssc.iter = sstate.nextpos; + +SRELL_NO_VCWARNING(4127) + if (utf_traits::maxseqlen > 1 && ismcul) +SRELL_NO_VCWARNING_END + { + const ui_l32 cp = utf_traits::codepoint_inc(sstate.nextpos, sstate.srchend); + const re_quantifier &r0q = this->NFA_states[0].quantifier; + +#if !defined(SRELLDBG_NO_CCPOS) + if (!this->character_class.is_included(r0q.atleast, r0q.atmost, cp)) +#else + if (!this->character_class.is_included(r0q.is_greedy, cp)) +#endif + continue; + } + else + ++sstate.nextpos; + +#if defined(SRELL_NO_LIMIT_COUNTER) + sstate.reset(); +#else + sstate.reset(this->limit_counter); +#endif + const ui_l32 reason = do_match(sstate); + if (reason) + return reason; + } + else + break; + } + return 0; + } + + template + ui_l32 do_search_sc(re_search_state &sstate, const non_cont_iter) const + { + typedef typename std::iterator_traits::value_type char_type2; + const char_type2 ec = static_cast(this->NFA_states[0].char_num & static_cast(utf_traits::ecmask)) - 1; + const bool ismcul = utf_traits::is_mculeading(ec); + + static_cast(ismcul); + + for (; sstate.nextpos != sstate.srchend;) + { + if ((*sstate.nextpos ^ ec) & utf_traits::bitsetmask) + { + ++sstate.nextpos; + continue; + } + + sstate.ssc.iter = sstate.nextpos; + +SRELL_NO_VCWARNING(4127) + if (utf_traits::maxseqlen > 1 && ismcul) +SRELL_NO_VCWARNING_END + { + const ui_l32 cp = utf_traits::codepoint_inc(sstate.nextpos, sstate.srchend); + const re_quantifier &r0q = this->NFA_states[0].quantifier; + +#if !defined(SRELLDBG_NO_CCPOS) + if (!this->character_class.is_included(r0q.atleast, r0q.atmost, cp)) +#else + if (!this->character_class.is_included(r0q.is_greedy, cp)) +#endif + continue; + } + else + ++sstate.nextpos; + +#if defined(SRELL_NO_LIMIT_COUNTER) + sstate.reset(); +#else + sstate.reset(this->limit_counter); +#endif + const ui_l32 reason = do_match(sstate); + if (reason) + return reason; + } + return 0; + } + + template + const charT2 *find_(const charT2 *const ptr, const std::size_t count, const charT2 &ch) const + { + return std::char_traits::find(ptr, count, ch); + } + +#if defined(__cpp_char8_t) + const char8_t *find_(const char8_t *const ptr, const std::size_t count, const char8_t ch) const + { + return static_cast(std::memchr(ptr, static_cast(ch), count)); + } +#endif + +#endif // !defined(SRELLDBG_NO_SCFINDER) + + template + struct contiguous_checker + { + typedef non_cont_iter itype; + }; + +#if defined(__cpp_concepts) + + template + struct contiguous_checker + { + typedef is_cont_iter itype; + }; + +#else + + template + struct contiguous_checker + { + typedef is_cont_iter itype; + }; + template + struct contiguous_checker::const_iterator, N> + { + typedef is_cont_iter itype; + }; + +#endif + + template + ui_l32 do_match(re_search_state &sstate) const + { + typedef typename re_object_core::state_type state_type; + typedef re_search_state ss_type; +// typedef typename ss_type::search_state_core ssc_type; + typedef typename ss_type::submatchcore_type submatchcore_type; + typedef typename ss_type::submatch_type submatch_type; + typedef typename ss_type::counter_type counter_type; + typedef typename ss_type::position_type position_type; + + goto START; + + NOT_MATCHED: + +#if !defined(SRELL_NO_LIMIT_COUNTER) + if (--sstate.failure_counter) + { +#endif + NOT_MATCHED0: + if (sstate.bt_size() > sstate.btstack_size) + { + sstate.pop_bt(sstate.ssc); + + sstate.ssc.state = sstate.ssc.state->next_state2; + } + else + return 0; + +#if !defined(SRELL_NO_LIMIT_COUNTER) + } + else + return static_cast(regex_constants::error_complexity); +#endif + + for (;;) + { + START: + + if (sstate.ssc.state->type <= st_characteri) + { + // 1 cmp 2 jmps. + if (sstate.ssc.state->type != st_characteri) // st_character. + { +SRELL_NO_VCWARNING(4127) + if SRELL_IFCE (!reverse) +SRELL_NO_VCWARNING_END + { + if (!(sstate.ssc.iter == sstate.srchend)) + { + const BidirectionalIterator prevpos = sstate.ssc.iter; + const ui_l32 uchar = utf_traits::codepoint_inc(sstate.ssc.iter, sstate.srchend); + + for (;;) + { + if (sstate.ssc.state->char_num == uchar) + { + sstate.ssc.state = sstate.ssc.state->next_state1; + goto START; + } + + if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + + if (sstate.ssc.state->type == st_character) + continue; + + sstate.ssc.iter = prevpos; + goto START; + } + break; + } + } + else if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + continue; + } + } + else // reverse == true. + { + if (!(sstate.ssc.iter == sstate.lblim)) + { + const BidirectionalIterator prevpos = sstate.ssc.iter; + const ui_l32 uchar = utf_traits::dec_codepoint(sstate.ssc.iter, sstate.lblim); + + for (;;) + { + if (sstate.ssc.state->char_num == uchar) + { + sstate.ssc.state = sstate.ssc.state->next_state1; + goto START; + } + + if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + + if (sstate.ssc.state->type == st_character) + continue; + + sstate.ssc.iter = prevpos; + goto START; + } + break; + } + } + else if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + continue; + } + } + goto NOT_MATCHED; + } + + // st_characteri. +SRELL_NO_VCWARNING(4127) + if SRELL_IFCE (!reverse) +SRELL_NO_VCWARNING_END + { + if (!(sstate.ssc.iter == sstate.srchend)) + { + const BidirectionalIterator prevpos = sstate.ssc.iter; + const ui_l32 uchar = unicode_case_folding::do_casefolding(utf_traits::codepoint_inc(sstate.ssc.iter, sstate.srchend)); + + for (;;) + { + if (sstate.ssc.state->char_num == uchar) + { + sstate.ssc.state = sstate.ssc.state->next_state1; + goto START; + } + + if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + + if (sstate.ssc.state->type == st_characteri) + continue; + + sstate.ssc.iter = prevpos; + goto START; + } + break; + } + } + else if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + continue; + } + } + else // reverse == true. + { + if (!(sstate.ssc.iter == sstate.lblim)) + { + const BidirectionalIterator prevpos = sstate.ssc.iter; + const ui_l32 uchar = unicode_case_folding::do_casefolding(utf_traits::dec_codepoint(sstate.ssc.iter, sstate.lblim)); + + for (;;) + { + if (sstate.ssc.state->char_num == uchar) + { + sstate.ssc.state = sstate.ssc.state->next_state1; + goto START; + } + + if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + + if (sstate.ssc.state->type == st_characteri) + continue; + + sstate.ssc.iter = prevpos; + goto START; + } + break; + } + } + else if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + continue; + } + } + goto NOT_MATCHED; + } + + if (sstate.ssc.state->type <= st_epsilon) + { + // 1 cmp 2 jmps. + if (sstate.ssc.state->type != st_epsilon) // st_character_class. + { +SRELL_NO_VCWARNING(4127) + if SRELL_IFCE (!reverse) +SRELL_NO_VCWARNING_END + { + if (!(sstate.ssc.iter == sstate.srchend)) + { + const BidirectionalIterator prevpos = sstate.ssc.iter; + const ui_l32 uchar = utf_traits::codepoint_inc(sstate.ssc.iter, sstate.srchend); + +#if !defined(SRELLDBG_NO_CCPOS) + if (this->character_class.is_included(sstate.ssc.state->quantifier.atleast, sstate.ssc.state->quantifier.atmost, uchar)) +#else + if (this->character_class.is_included(sstate.ssc.state->char_num, uchar)) +#endif + { + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + } + + if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + + sstate.ssc.iter = prevpos; + continue; + } + } + else if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + continue; + } + } + else // reverse == true. + { + if (!(sstate.ssc.iter == sstate.lblim)) + { + const BidirectionalIterator prevpos = sstate.ssc.iter; + const ui_l32 uchar = utf_traits::dec_codepoint(sstate.ssc.iter, sstate.lblim); + +#if !defined(SRELLDBG_NO_CCPOS) + if (this->character_class.is_included(sstate.ssc.state->quantifier.atleast, sstate.ssc.state->quantifier.atmost, uchar)) +#else + if (this->character_class.is_included(sstate.ssc.state->char_num, uchar)) +#endif + { + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + } + + if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + + sstate.ssc.iter = prevpos; + continue; + } + } + else if (sstate.ssc.state->next_state2) + { + sstate.ssc.state = sstate.ssc.state->next_state2; + continue; + } + } + goto NOT_MATCHED; + } + + // st_epsilon. +#if defined(SRELLDBG_NO_SKIP_EPSILON) + if (sstate.ssc.state->next_state2) +#endif + { + sstate.push_bt_wc(sstate.ssc); + } + + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + } + + switch (sstate.ssc.state->type) + { + case st_check_counter: + { + ST_CHECK_COUNTER: + const counter_type counter = sstate.counter[sstate.ssc.state->char_num]; + + if (counter < sstate.ssc.state->quantifier.atleast) + { + ++sstate.ssc.state; + } + else + { + if (counter < sstate.ssc.state->quantifier.atmost || sstate.ssc.state->quantifier.is_infinity()) + { + sstate.push_bt_wc(sstate.ssc); + sstate.ssc.state = sstate.ssc.state->next_state1; + } + else + { + sstate.ssc.state = sstate.ssc.state->quantifier.is_greedy + ? sstate.ssc.state->next_state2 + : sstate.ssc.state->next_state1; + } + continue; + } + } + //@fallthrough@ + + case st_increment_counter: + { + counter_type &counter = sstate.counter[sstate.ssc.state->char_num]; + + if (counter != constants::infinity) + { + ++counter; + if (sstate.ssc.state->next_state2) + sstate.push_bt_wc(sstate.ssc); + } + } + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + + case st_decrement_counter: + --sstate.counter[sstate.ssc.state->char_num]; + goto NOT_MATCHED0; + + case st_save_and_reset_counter: + { + counter_type &counter = sstate.counter[sstate.ssc.state->char_num]; + + sstate.expand(sizeof counter + sizeof sstate.ssc); + + sstate.push_c(counter); + sstate.push_bt(sstate.ssc); + counter = 0; + } + sstate.ssc.state = sstate.ssc.state->next_state1; + goto ST_CHECK_COUNTER; + + case st_restore_counter: + sstate.pop_c(sstate.counter[sstate.ssc.state->char_num]); + goto NOT_MATCHED0; + + case st_roundbracket_open: + { + submatch_type &bracket = sstate.bracket[sstate.ssc.state->char_num]; + const re_quantifier &sq = sstate.ssc.state->quantifier; + + sstate.expand((sq.atleast <= sq.atmost ? ((sizeof (submatchcore_type) + sizeof (counter_type)) * (sq.atmost - sq.atleast + 1)) : 0) + sizeof (submatchcore_type) + sizeof sstate.ssc); + + sstate.push_sm(bracket.core); + ++bracket.counter; + // Now .counter is of size_t. Out-of-memory will precede its overflow. + + for (ui_l32 brno = sstate.ssc.state->quantifier.atleast; brno <= sstate.ssc.state->quantifier.atmost; ++brno) + { + submatch_type &inner_bracket = sstate.bracket[brno]; + + sstate.push_sm(inner_bracket.core); + sstate.push_c(inner_bracket.counter); + inner_bracket.counter = 0; + // ECMAScript 2025 22.2.2.3.1, NOTE 3. + } + sstate.push_bt(sstate.ssc); + + (!reverse ? bracket.core.open_at : bracket.core.close_at) = sstate.ssc.iter; + } + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + + case st_roundbracket_pop: + { + for (ui_l32 brno = sstate.ssc.state->quantifier.atmost; brno >= sstate.ssc.state->quantifier.atleast; --brno) + { + submatch_type &inner_bracket = sstate.bracket[brno]; + + sstate.pop_c(inner_bracket.counter); + sstate.pop_sm(inner_bracket.core); + } + + submatch_type &bracket = sstate.bracket[sstate.ssc.state->char_num]; + + --bracket.counter; + sstate.pop_sm(bracket.core); + } + goto NOT_MATCHED0; + + case st_roundbracket_close: + { + submatch_type &bracket = sstate.bracket[sstate.ssc.state->char_num]; + submatchcore_type &brc = bracket.core; + + if ((!reverse ? brc.open_at : brc.close_at) == sstate.ssc.iter + && bracket.counter > sstate.ssc.state->quantifier.atleast) + goto NOT_MATCHED0; + + sstate.ssc.state = sstate.ssc.state->next_state1; + (!reverse ? brc.close_at : brc.open_at) = sstate.ssc.iter; + } + continue; + + case st_repeat_in_push: + { + position_type &r = sstate.repeat[sstate.ssc.state->char_num]; + const re_quantifier &sq = sstate.ssc.state->quantifier; + + sstate.expand(sizeof r + (sq.atleast <= sq.atmost ? ((sizeof (submatchcore_type) + sizeof (counter_type)) * (sq.atmost - sq.atleast + 1)) : 0) + sizeof sstate.ssc); + + sstate.push_rp(r); + r = sstate.ssc.iter; + + for (ui_l32 brno = sstate.ssc.state->quantifier.atleast; brno <= sstate.ssc.state->quantifier.atmost; ++brno) + { + submatch_type &inner_bracket = sstate.bracket[brno]; + + sstate.push_sm(inner_bracket.core); + sstate.push_c(inner_bracket.counter); + inner_bracket.counter = 0; + } + sstate.push_bt(sstate.ssc); + } + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + + case st_repeat_in_pop: + for (ui_l32 brno = sstate.ssc.state->quantifier.atmost; brno >= sstate.ssc.state->quantifier.atleast; --brno) + { + submatch_type &inner_bracket = sstate.bracket[brno]; + + sstate.pop_c(inner_bracket.counter); + sstate.pop_sm(inner_bracket.core); + } + + sstate.pop_rp(sstate.repeat[sstate.ssc.state->char_num]); + goto NOT_MATCHED0; + + case st_check_0_width_repeat: + if (sstate.ssc.iter != sstate.repeat[sstate.ssc.state->char_num]) + { + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + } + + if (sstate.ssc.state->next_state1->type == st_check_counter) + { + const counter_type counter = sstate.counter[sstate.ssc.state->next_state1->char_num]; + + if (counter > sstate.ssc.state->next_state1->quantifier.atleast) + goto NOT_MATCHED0; + + sstate.ssc.state = sstate.ssc.state->next_state1; + } + else + sstate.ssc.state = sstate.ssc.state->next_state2; + + continue; + + case st_backreference: + { + const submatch_type &bracket = sstate.bracket[sstate.ssc.state->char_num]; + const submatchcore_type &brc = bracket.core; + + if (bracket.counter == 0 || brc.open_at == brc.close_at) // Undefined or "". + { + sstate.ssc.state = sstate.ssc.state->next_state2; + continue; + } + +SRELL_NO_VCWARNING(4127) + if SRELL_IFCE (!reverse) +SRELL_NO_VCWARNING_END + { + BidirectionalIterator backrefpos = brc.open_at; + + if (!sstate.ssc.state->flags) // !icase. + { + for (; backrefpos != brc.close_at;) + { + if (sstate.ssc.iter == sstate.srchend || *sstate.ssc.iter++ != *backrefpos++) + goto NOT_MATCHED; + } + } + else // icase. + { + for (; backrefpos != brc.close_at;) + { + if (!(sstate.ssc.iter == sstate.srchend)) + { + const ui_l32 uchartxt = utf_traits::codepoint_inc(sstate.ssc.iter, sstate.srchend); + const ui_l32 ucharref = utf_traits::codepoint_inc(backrefpos, brc.close_at); + + if (unicode_case_folding::do_casefolding(uchartxt) == unicode_case_folding::do_casefolding(ucharref)) + continue; + } + goto NOT_MATCHED; + } + } + } + else // reverse == true. + { + BidirectionalIterator backrefpos = brc.close_at; + + if (!sstate.ssc.state->flags) // !icase. + { + for (; backrefpos != brc.open_at;) + { + if (sstate.ssc.iter == sstate.lblim || *--sstate.ssc.iter != *--backrefpos) + goto NOT_MATCHED; + } + } + else // icase. + { + for (; backrefpos != brc.open_at;) + { + if (!(sstate.ssc.iter == sstate.lblim)) + { + const ui_l32 uchartxt = utf_traits::dec_codepoint(sstate.ssc.iter, sstate.lblim); + const ui_l32 ucharref = utf_traits::dec_codepoint(backrefpos, brc.open_at); + + if (unicode_case_folding::do_casefolding(uchartxt) == unicode_case_folding::do_casefolding(ucharref)) + continue; + } + goto NOT_MATCHED; + } + } + } + } + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + + case st_lookaround_open: + { + const state_type *const lostate = sstate.ssc.state; + const re_quantifier *const losq = &lostate->quantifier; + + sstate.expand((losq->atleast <= losq->atmost ? ((sizeof (submatchcore_type) + sizeof (counter_type)) * (losq->atmost - losq->atleast + 1)) : 0) + sizeof sstate.ssc); + + for (ui_l32 brno = losq->atleast; brno <= losq->atmost; ++brno) + { + const submatch_type &sm = sstate.bracket[brno]; + sstate.push_sm(sm.core); + sstate.push_c(sm.counter); + } + + const typename ss_type::bottom_state backup_bottom(sstate.btstack_size, sstate); + const BidirectionalIterator orgpos = sstate.ssc.iter; + + if (losq->atleast <= losq->atmost) + sstate.push_bt(sstate.ssc); + +#if !defined(SRELLDBG_NO_MPREWINDER) + if (losq->is_greedy >= 2) + sstate.lblim = sstate.srchbegin; +#endif + + sstate.btstack_size = sstate.bt_size(); + +#if defined(SRELL_FIXEDWIDTHLOOKBEHIND) + ui_l32 is_matched; + +// if (lostate->reverse) + { + for (ui_l32 i = 0; i < losq->is_greedy; ++i) + { + if (sstate.ssc.iter == sstate.lblim) + { + is_matched = 0; + goto AFTER_LOOKAROUND; + } + utf_traits::dec_codepoint(sstate.ssc.iter, sstate.lblim); + } + } +#endif + sstate.ssc.state = lostate->next_state2->next_state1; + + // sstate.ssc.state is no longer pointing to lookaround_open! + +#if !defined(SRELL_FIXEDWIDTHLOOKBEHIND) + const ui_l32 is_matched = (losq->is_greedy == 0 ? do_match(sstate) : do_match(sstate)); +#else + is_matched = do_match(sstate); +#endif + + if (is_matched >> 1) + return is_matched; + +#if defined(SRELL_FIXEDWIDTHLOOKBEHIND) + AFTER_LOOKAROUND: +#endif + sstate.bt_resize(sstate.btstack_size); + +#if !defined(SRELLDBG_NO_MPREWINDER) + if (losq->is_greedy >= 2) + { + sstate.lblim = sstate.reallblim; + if (is_matched) + sstate.curbegin = sstate.ssc.iter; + } +#endif + +#if defined(SRELL_ENABLE_GT) + if (lostate->char_num != meta_char::mc_gt) // '>' +#endif + { +#if !defined(SRELLDBG_NO_MPREWINDER) + if (losq->is_greedy < 3) +#endif + sstate.ssc.iter = orgpos; + } + + backup_bottom.restore(sstate.btstack_size, sstate); + + if (is_matched ^ lostate->flags) + { +#if !defined(SRELLDBG_NO_MPREWINDER) + if (losq->is_greedy == 3) + sstate.ssc.state = this->NFA_states[0].next_state2; + else +#endif + sstate.ssc.state = lostate->next_state1; + continue; + } + + if (losq->atleast <= losq->atmost) + sstate.pop_bt(sstate.ssc); + sstate.ssc.state = lostate->next_state2; + } + //@fallthrough@ + + case st_lookaround_pop: + for (ui_l32 brno = sstate.ssc.state->quantifier.atmost; brno >= sstate.ssc.state->quantifier.atleast; --brno) + { + submatch_type &sm = sstate.bracket[brno]; + + sstate.pop_c(sm.counter); + sstate.pop_sm(sm.core); + } + goto NOT_MATCHED0; + + case st_bol: + if (sstate.ssc.iter == sstate.lblim && !(sstate.reallblim != sstate.lblim || (sstate.flags & regex_constants::match_prev_avail) != 0)) + { + if (!(sstate.flags & regex_constants::match_not_bol)) + { + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + } + } + // !sstate.is_at_lookbehindlimit() || sstate.match_prev_avail_flag() + else if (sstate.ssc.state->flags) // multiline. + { + BidirectionalIterator lb(sstate.ssc.iter); + const ui_l32 prevchar = utf_traits::dec_codepoint(lb, sstate.reallblim); + +#if !defined(SRELLDBG_NO_CCPOS) + if (this->character_class.is_included(sstate.ssc.state->quantifier.atleast, sstate.ssc.state->quantifier.atmost, prevchar)) +#else + if (this->character_class.is_included(re_character_class::newline, prevchar)) +#endif + { + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + } + } + goto NOT_MATCHED; + + case st_eol: + if (sstate.ssc.iter == sstate.srchend) + { + if (!(sstate.flags & regex_constants::match_not_eol)) + { + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + } + } + else if (sstate.ssc.state->flags) // multiline. + { + BidirectionalIterator la(sstate.ssc.iter); + const ui_l32 nextchar = utf_traits::codepoint_inc(la, sstate.srchend); + +#if !defined(SRELLDBG_NO_CCPOS) + if (this->character_class.is_included(sstate.ssc.state->quantifier.atleast, sstate.ssc.state->quantifier.atmost, nextchar)) +#else + if (this->character_class.is_included(re_character_class::newline, nextchar)) +#endif + { + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + } + } + goto NOT_MATCHED; + + case st_boundary: // '\b' '\B' + { + ui_l32 is_matched = sstate.ssc.state->flags; // is_not. + + // First, suppose the previous character is not \w but \W. + + if (sstate.ssc.iter == sstate.srchend) + { + if (sstate.flags & regex_constants::match_not_eow) + is_matched ^= 1u; + } + else + { + BidirectionalIterator la(sstate.ssc.iter); +#if !defined(SRELLDBG_NO_CCPOS) + if (this->character_class.is_included(sstate.ssc.state->quantifier.atleast, sstate.ssc.state->quantifier.atmost, utf_traits::codepoint_inc(la, sstate.srchend))) +#else + if (this->character_class.is_included(sstate.ssc.state->char_num, utf_traits::codepoint_inc(la, sstate.srchend))) +#endif + { + is_matched ^= 1u; + } + } + // \W/last \w + // \b false true + // \B true false + + // Second, if the actual previous character is \w, flip is_matched. + + if (sstate.ssc.iter == sstate.lblim && !(sstate.reallblim != sstate.lblim || (sstate.flags & regex_constants::match_prev_avail) != 0)) + { + if (sstate.flags & regex_constants::match_not_bow) + is_matched ^= 1u; + } + else + { + BidirectionalIterator lb(sstate.ssc.iter); + // !sstate.is_at_lookbehindlimit() || sstate.match_prev_avail_flag() +#if !defined(SRELLDBG_NO_CCPOS) + if (this->character_class.is_included(sstate.ssc.state->quantifier.atleast, sstate.ssc.state->quantifier.atmost, utf_traits::dec_codepoint(lb, sstate.reallblim))) +#else + if (this->character_class.is_included(sstate.ssc.state->char_num, utf_traits::dec_codepoint(lb, sstate.reallblim))) +#endif + { + is_matched ^= 1u; + } + } + // \b \B + // pre cur \W/last \w pre cur \W/last \w + // \W/base false true \W/base true false + // \w true false \w false true + + if (is_matched) + { + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; + } + + goto NOT_MATCHED; + } + + case st_success: // == lookaround_close. + if (sstate.btstack_size) + return 1; + + if + ( + (!(sstate.flags & regex_constants::match_not_null) || !(sstate.ssc.iter == sstate.curbegin)) + && + (!(sstate.flags & regex_constants::match_match_) || sstate.ssc.iter == sstate.srchend) + ) + return 1; + + goto NOT_MATCHED0; + +#if defined(SRELLTEST_NEXTPOS_OPT) + case st_move_nextpos: +#if !defined(SRELLDBG_NO_1STCHRCLS) && !defined(SRELLDBG_NO_BITSET) + sstate.nextpos = sstate.ssc.iter; + if (!(sstate.ssc.iter == sstate.srchend)) + ++sstate.nextpos; +#else // defined(SRELLDBG_NO_1STCHRCLS) || defined(SRELLDBG_NO_BITSET) + if (sstate.ssc.iter != sstate.curbegin) + { + sstate.nextpos = sstate.ssc.iter; + if (!(sstate.ssc.iter == sstate.srchend)) + utf_traits::codepoint_inc(sstate.nextpos, sstate.srchend); + } +#endif + sstate.ssc.state = sstate.ssc.state->next_state1; + continue; +#endif + + default: + // Reaching here means that this->NFA_states is corrupted. + return static_cast(regex_constants::error_internal); + } + } + } +}; +// re_object + + } // namespace re_detail + +// ... "rei_algorithm.hpp"] +// ["basic_regex.hpp" ... + +template > +class basic_regex : public re_detail::re_object +{ +public: + + // Types: + typedef charT value_type; + typedef traits traits_type; + typedef typename traits::string_type string_type; + typedef regex_constants::syntax_option_type flag_type; + typedef typename traits::locale_type locale_type; + typedef typename re_detail::concon_view contiguous_container_view; + + static const regex_constants::syntax_option_type icase = regex_constants::icase; + static const regex_constants::syntax_option_type nosubs = regex_constants::nosubs; + static const regex_constants::syntax_option_type optimize = regex_constants::optimize; + static const regex_constants::syntax_option_type collate = regex_constants::collate; + static const regex_constants::syntax_option_type ECMAScript = regex_constants::ECMAScript; + static const regex_constants::syntax_option_type basic = regex_constants::basic; + static const regex_constants::syntax_option_type extended = regex_constants::extended; + static const regex_constants::syntax_option_type awk = regex_constants::awk; + static const regex_constants::syntax_option_type grep = regex_constants::grep; + static const regex_constants::syntax_option_type egrep = regex_constants::egrep; + static const regex_constants::syntax_option_type multiline = regex_constants::multiline; + + static const regex_constants::syntax_option_type sticky = regex_constants::sticky; + static const regex_constants::syntax_option_type dotall = regex_constants::dotall; + static const regex_constants::syntax_option_type unicodesets = regex_constants::unicodesets; + static const regex_constants::syntax_option_type vmode = regex_constants::vmode; + static const regex_constants::syntax_option_type quiet = regex_constants::quiet; + + basic_regex() + { + } + + explicit basic_regex(const charT *const p, const flag_type f = regex_constants::ECMAScript) + { + assign(p, p + std::char_traits::length(p), f); + } + + basic_regex(const charT *const p, const std::size_t len, const flag_type f = regex_constants::ECMAScript) + { + assign(p, p + len, f); + } + + basic_regex(const basic_regex &e) + { + assign(e); + } + +#if defined(__cpp_rvalue_references) + basic_regex(basic_regex &&e) SRELL_NOEXCEPT + { + assign(std::move(e)); + } +#endif + + explicit basic_regex(const contiguous_container_view c, const flag_type f = regex_constants::ECMAScript) + { + assign(c, f); + } + + template + basic_regex(ForwardIterator first, ForwardIterator last, const flag_type f = regex_constants::ECMAScript) + { + assign(first, last, f); + } + +#if defined(__cpp_initializer_lists) + basic_regex(std::initializer_list il, const flag_type f = regex_constants::ECMAScript) + { + assign(il, f); + } +#endif + +// ~basic_regex(); + + basic_regex &operator=(const basic_regex &right) + { + return assign(right); + } + +#if defined(__cpp_rvalue_references) + basic_regex &operator=(basic_regex &&e) SRELL_NOEXCEPT + { + return assign(std::move(e)); + } +#endif + + basic_regex &operator=(const charT *const ptr) + { + return assign(ptr); + } + +#if defined(__cpp_initializer_lists) + basic_regex &operator=(std::initializer_list il) + { + return assign(il.begin(), il.end()); + } +#endif + + basic_regex &operator=(const contiguous_container_view c) + { + return assign(c); + } + + basic_regex &assign(const basic_regex &right) + { + re_detail::re_object_core::operator=(right); + return *this; + } + +#if defined(__cpp_rvalue_references) + basic_regex &assign(basic_regex &&right) SRELL_NOEXCEPT + { + re_detail::re_object_core::operator=(std::move(right)); + return *this; + } +#endif + + basic_regex &assign(const charT *const ptr, const flag_type f = regex_constants::ECMAScript) + { + return assign(ptr, ptr + std::char_traits::length(ptr), f); + } + + basic_regex &assign(const charT *const p, std::size_t len, const flag_type f = regex_constants::ECMAScript) + { + return assign(p, p + len, f); + } + + basic_regex &assign(const contiguous_container_view c, const flag_type f = regex_constants::ECMAScript) + { + return assign(c.data_, c.data_ + c.size_, f); + } + + template + basic_regex &assign(InputIterator first, InputIterator last, const flag_type f = regex_constants::ECMAScript) + { +#if defined(SRELL_STRICT_IMPL) + basic_regex tmp; + tmp.compile(first, last, f); +#if !defined(SRELL_NO_THROW) + tmp.swap(*this); +#else + if (tmp.ecode() == 0) + tmp.swap(*this); + else + { + this->soflags &= re_detail::masks::somask; + this->soflags |= tmp.soflags & re_detail::masks::errmask; + } +#endif +#else + this->compile(first, last, f); +#endif + return *this; + } + +#if defined(__cpp_initializer_lists) + basic_regex &assign(std::initializer_list il, const flag_type f = regex_constants::ECMAScript) + { + return assign(il.begin(), il.end(), f); + } +#endif + + unsigned mark_count() const + { + return this->number_of_brackets - 1; + } + + flag_type flags() const + { + return static_cast(this->soflags & re_detail::masks::somask); + } + + locale_type imbue(locale_type /* loc */) + { + return locale_type(); + } + + locale_type getloc() const + { + return locale_type(); + } + + void swap(basic_regex &e) + { + re_detail::re_object_core::swap(e); + } + + regex_constants::error_type ecode() const + { + return re_detail::re_object_core::ecode(); + } + +#if !defined(SRELL_NO_APIEXT) + + template + bool match( + const BidirectionalIterator begin, + const BidirectionalIterator end, + match_results &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return base_type::search(begin, end, begin, m, flags | regex_constants::match_continuous | regex_constants::match_match_); + } + + template + bool match( + const charT *const str, + match_results &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return this->match(str, str + std::char_traits::length(str), m, flags); + } + + template + bool match( + const std::basic_string &s, + match_results::const_iterator, MA> &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return this->match(s.begin(), s.end(), m, flags); + } + template + bool match( + const contiguous_container_view c, + match_results &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return this->match(c.data_, c.data_ + c.size_, m, flags); + } + + template + bool search( + const BidirectionalIterator begin, + const BidirectionalIterator end, + const BidirectionalIterator lookbehind_limit, + match_results &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return base_type::search(begin, end, lookbehind_limit, m, flags); + } + + template + bool search( + const std::basic_string &s, + const std::size_t start, + match_results::const_iterator, MA> &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return base_type::search(s.begin() + start, s.end(), s.begin(), m, flags); + } + template + bool search( + const contiguous_container_view c, + const std::size_t start, + match_results &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return base_type::search(c.data_ + start, c.data_ + c.size_, c.data_, m, flags); + } + + template + bool search( + const BidirectionalIterator begin, + const BidirectionalIterator end, + match_results &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return base_type::search(begin, end, begin, m, flags); + } + + template + bool search( + const charT *const str, + match_results &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return this->search(str, str + std::char_traits::length(str), m, flags); + } + + template + bool search( + const std::basic_string &s, + match_results::const_iterator, MA> &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return this->search(s.begin(), s.end(), m, flags); + } + template + bool search( + const contiguous_container_view c, + match_results &m, + const regex_constants::match_flag_type flags = regex_constants::match_default) const + { + return this->search(c.data_, c.data_ + c.size_, m, flags); + } + +private: + + typedef re_detail::re_object base_type; + +#endif // !defined(SRELL_NO_APIEXT) +}; +#define SRELLTMP_BRFLG(fn) template const regex_constants::syntax_option_type basic_regex::fn; +SRELLTMP_BRFLG(icase) +SRELLTMP_BRFLG(nosubs) +SRELLTMP_BRFLG(optimize) +SRELLTMP_BRFLG(collate) +SRELLTMP_BRFLG(ECMAScript) +SRELLTMP_BRFLG(basic) +SRELLTMP_BRFLG(extended) +SRELLTMP_BRFLG(awk) +SRELLTMP_BRFLG(grep) +SRELLTMP_BRFLG(egrep) +SRELLTMP_BRFLG(multiline) + +SRELLTMP_BRFLG(sticky) +SRELLTMP_BRFLG(dotall) +SRELLTMP_BRFLG(unicodesets) +SRELLTMP_BRFLG(vmode) +SRELLTMP_BRFLG(quiet) +#undef SRELLTMP_BRFLG + +template +void swap(basic_regex &lhs, basic_regex &rhs) +{ + lhs.swap(rhs); +} + +// ... "basic_regex.hpp"] +// ["regex_iterator.hpp" ... + +template ::value_type, class traits = regex_traits > +class regex_iterator +{ +public: + + typedef basic_regex regex_type; + typedef match_results value_type; + typedef std::ptrdiff_t difference_type; + typedef const value_type *pointer; + typedef const value_type &reference; + typedef std::forward_iterator_tag iterator_category; + + regex_iterator() + { + // 28.12.1.1: Constructs an end-of-sequence iterator. + } + + regex_iterator( + const BidirectionalIterator a, + const BidirectionalIterator b, + const regex_type &re, + const regex_constants::match_flag_type m = regex_constants::match_default) + : begin(a), end(b), pregex(&re), flags(m) + { + regex_search(begin, end, begin, match, *pregex, flags); + } + + regex_iterator(const regex_iterator &that) + { + operator=(that); + } + + regex_iterator &operator=(const regex_iterator &that) + { + if (this != &that) + { + this->match = that.match; + if (this->match.size() > 0) + { + this->begin = that.begin; + this->end = that.end; + this->pregex = that.pregex; + this->flags = that.flags; + } + } + return *this; + } + + bool operator==(const regex_iterator &right) const + { + if (right.match.size() == 0 || this->match.size() == 0) + return this->match.size() == right.match.size(); + + return this->begin == right.begin + && this->end == right.end + && this->pregex == right.pregex + && this->flags == right.flags + && this->match[0] == right.match[0]; + } + + bool operator!=(const regex_iterator &right) const + { + return !(*this == right); + } + + const value_type &operator*() const + { + return match; + } + + const value_type *operator->() const + { + return &match; + } + + regex_iterator &operator++() + { + if (this->match.size()) + { + BidirectionalIterator start = match[0].second; + + if (match[0].first == start) // The iterator holds a 0-length match. + { + if (start == end) + { + match.clear_(); + } + else + { + if (!regex_search(start, end, begin, match, *pregex, flags | regex_constants::match_not_null | regex_constants::match_continuous)) + { + const BidirectionalIterator prevend = start; + +// ++start; + utf_traits::codepoint_inc(start, end); + + flags |= regex_constants::match_prev_avail; + + if (regex_search(start, end, begin, match, *pregex, flags)) + match.update_prefix1_(prevend); + } + } + } + else + { + flags |= regex_constants::match_prev_avail; + + regex_search(start, end, begin, match, *pregex, flags); + } + } + return *this; + } + + regex_iterator operator++(int) + { + const regex_iterator tmp = *this; + ++(*this); + return tmp; + } + +private: + + BidirectionalIterator begin; + BidirectionalIterator end; + const regex_type *pregex; + regex_constants::match_flag_type flags; + match_results match; + + typedef typename traits::utf_traits utf_traits; +}; + +#if !defined(SRELL_NO_APIEXT) + +template ::value_type, regex_traits::value_type> >, typename MatchResults = match_results > +class regex_iterator2 +{ +public: + + typedef typename std::iterator_traits::value_type char_type; + typedef BasicRegex regex_type; + typedef MatchResults value_type; + typedef std::ptrdiff_t difference_type; + typedef const value_type *pointer; + typedef const value_type &reference; + typedef std::input_iterator_tag iterator_category; + typedef typename regex_type::contiguous_container_view contiguous_container_view; + + regex_iterator2() {} + + regex_iterator2( + const BidirectionalIterator b, + const BidirectionalIterator e, + const regex_type &re, + const regex_constants::match_flag_type m = regex_constants::match_default) + { + assign(b, e, b, re, m); + } + regex_iterator2( + const BidirectionalIterator begin, + const BidirectionalIterator end, + const BidirectionalIterator lookbehind_limit, + const regex_type &re, + const regex_constants::match_flag_type m = regex_constants::match_default) + { + assign(begin, end, lookbehind_limit, re, m); + } + + regex_iterator2( + const contiguous_container_view c, + const regex_type &re, + const regex_constants::match_flag_type m = regex_constants::match_default) + { + assign(c, 0, re, m); + } + regex_iterator2( + const contiguous_container_view c, + const std::size_t start, + const regex_type &re, + const regex_constants::match_flag_type m = regex_constants::match_default) + { + assign(c, start, re, m); + } + + regex_iterator2(const regex_iterator2 &right) + { + operator=(right); + } + + regex_iterator2 &operator=(const regex_iterator2 &right) + { + if (this != &right) + { + this->match_ = right.match_; + if (this->match_.size() > 0) + { + this->begin_ = right.begin_; + this->end_ = right.end_; + this->pregex_ = right.pregex_; + this->flags_ = right.flags_; + this->submatch_ = right.submatch_; + this->prevmatch_empty_ = right.prevmatch_empty_; + } + } + return *this; + } + + bool operator==(const regex_iterator2 &right) const + { + if (right.match_.size() == 0 || this->match_.size() == 0) + return this->match_.size() == right.match_.size(); + + return this->begin_ == right.begin_ + && this->end_ == right.end_ + && this->pregex_ == right.pregex_ + && this->flags_ == right.flags_ + && this->match_[0] == right.match_[0] + && this->submatch_ == right.submatch_ + && this->prevmatch_empty_ == right.prevmatch_empty_; + } + + bool operator!=(const regex_iterator2 &right) const + { + return !operator==(right); + } + + const value_type &operator*() const + { + return match_; + } + + const value_type *operator->() const + { + return &match_; + } + + bool done() const + { + return match_.size() == 0; + } + + void assign( + const BidirectionalIterator b, + const BidirectionalIterator e, + const regex_type &re, + const regex_constants::match_flag_type m = regex_constants::match_default) + { + assign(b, e, b, re, m); + } + void assign( + const BidirectionalIterator begin, + const BidirectionalIterator end, + const BidirectionalIterator lookbehind_limit, + const regex_type &re, + const regex_constants::match_flag_type m = regex_constants::match_default) + { + begin_ = lookbehind_limit; + end_ = end; + pregex_ = &re; + flags_ = m; + submatch_ = 0u; + + if (re.search(begin, end_, begin_, match_, flags_)) + { + prevmatch_empty_ = match_[0].first == match_[0].second; + } + else + match_.set_prefix1_(begin_); + } + + void assign( + const contiguous_container_view c, + const regex_type &re, + const regex_constants::match_flag_type m = regex_constants::match_default) + { + assign(c, 0, re, m); + } + void assign( + const contiguous_container_view c, + const std::size_t start, + const regex_type &re, + const regex_constants::match_flag_type m = regex_constants::match_default) + { + assign(pos0_(c, BidirectionalIterator()) + start, pos1_(c, BidirectionalIterator()), pos0_(c, BidirectionalIterator()), re, m); + } + void assign(const regex_iterator2 &right) + { + operator=(right); + } + + regex_iterator2 &operator++() + { + if (match_.size()) + { + const BidirectionalIterator prevend = match_[0].second; + BidirectionalIterator start = prevend; + + if (prevmatch_empty_) + { + if (start == end_) + { + match_.clear_(); + return *this; + } + utf_traits::codepoint_inc(start, end_); + } + + if (pregex_->search(start, end_, begin_, match_, flags_ | regex_constants::match_prev_avail)) + prevmatch_empty_ = match_[0].first == match_[0].second; + + match_.update_prefix1_(prevend); + } + return *this; + } + + regex_iterator2 operator++(int) + { + const regex_iterator2 tmp = *this; + ++(*this); + return tmp; + } + + // For replace. + + // Replaces [match_[0].first, match_[0].second) in + // [entire_string.begin(), entire_string.end()) with replacement, + // and adjusts all the internal iterators accordingly. + template + void replace(std::basic_string &entire_string, const contiguous_container_view replacement) + { + replace(entire_string, replacement.data_, replacement.size_); + } + + template + void replace(std::basic_string &entire_string, const char_type *const replacement, const std::size_t replen) + { + typedef std::basic_string string_type; + typedef typename string_type::size_type size_type; + + if (match_.size()) + { + const BidirectionalIterator oldbegin = pos0_(entire_string, BidirectionalIterator()); + const size_type oldbeginoffset = begin_ - oldbegin; + const size_type oldendoffset = end_ - oldbegin; + const size_type pos = match_[0].first - oldbegin; + const size_type count = match_[0].second - match_[0].first; + const typename match_type::difference_type addition = replen - match_.length(0); + + entire_string.replace(pos, count, replacement, replen); + + const BidirectionalIterator newbegin = pos0_(entire_string, BidirectionalIterator()); + + begin_ = newbegin + oldbeginoffset; + end_ = newbegin + (oldendoffset + addition); // VC checks if an iterator exceeds end(). + + match_.update_m0_(newbegin + pos, newbegin + (pos + count + addition)); + + prevmatch_empty_ = count == 0; + } + } + + template + void replace(std::basic_string &entire_string, const BidirectionalIterator b, const BidirectionalIterator e) + { + typedef std::basic_string string_type; + + replace(entire_string, string_type(b, e)); + } + + template + void replace(std::basic_string &entire_string, const char_type *const replacement) + { + replace(entire_string, replacement, std::char_traits::length(replacement)); + } + + // For split. + + // 1. Until done() returns true, gather this->prefix() and + // increment while split_ready() returns true, + // 2. Once done() becomes true, get remainder(). + + // Returns if this->prefix() holds a range that is worthy of being + // treated as a split substring. + bool split_ready() //const + { + if (match_.size()) + { + if (match_[0].first != end_) + return match_.prefix().first != match_[0].second; + + // [end_, end_) is not appropriate as a split range. Invalidates the current match. + match_.clear_(); + } + return false; // Iterating complete. + } + + // If only_after_match is false, returns [prefix().first, end); + // otherwise (if true) returns [match_[0].second, end). + // This function is intended to be called after iterating is + // finished, to receive the range of suffix() of the last match. + // If iterating is broken off during processing (e.g. pushing to a + // list container) captured subsequences (match_[n] where n >= 1), + // then should be called with only_after_match being true, + // otherwise [prefix().first, prefix().second) would be duplicated. + const typename value_type::value_type &remainder(const bool only_after_match = false) + { + if (only_after_match && match_.size()) + match_.set_prefix1_(match_[0].second); + + match_.update_prefix2_(end_); + return match_.prefix(); + } + + // The following 4 split_* functions are intended to be used + // together, as follows: + // + // for (it.split_begin(); !it.done(); it.split_next()) { + // if (++count == LIMIT) + // break; + // list.push_back(it.split_range()); + // } + // list.push_back(it.split_remainder()); + + // Moves to a first subsequence for which split_ready() returns + // true. This should be called only once before beginning iterating. + bool split_begin() + { + if (split_ready()) + return true; + + operator++(); + return split_ready(); + } + + // Moves to a next subsequence for which split_ready() returns + // true. + // This function is intended to be used instead of the ordinary + // increment operator++(). + bool split_next() + { + if (++submatch_ >= match_.size()) + { + submatch_ = 0u; + operator++(); + return split_begin(); + } + return !done(); + } + + // Returns the current subsequence to which the iterator points. + const typename value_type::value_type &split_range() const + { + return submatch_ == 0u ? match_.prefix() : match_[submatch_]; + } + + // Returns the final subsequence immediately following the last + // match range. This should be called after iterating is complete + // or broken off. + // Unlike remainder() above, a boolean value corresponding to + // only_after_match is automatically calculated. + const typename value_type::value_type &split_remainder() + { + if (submatch_ > 0u) + match_.set_prefix1_(match_[0].second); + + match_.update_prefix2_(end_); + return match_.prefix(); + } + + // Returns an appropriate range depending on done(). + const typename value_type::value_type &split_aptrange() + { + return !done() ? split_range() : split_remainder(); + } + +private: + + typedef match_results match_type; + typedef typename regex_type::traits_type::utf_traits utf_traits; + + template + iteratorTag pos0_(const StringLike &s, iteratorTag) + { + return s.begin(); + } + template + const char_type *pos0_(const StringLike &s, const char_type *) + { + return s.data(); + } + + template + iteratorTag pos1_(const StringLike &s, iteratorTag) + { + return s.end(); + } + template + const char_type *pos1_(const StringLike &s, const char_type *) + { + return s.data() + s.size(); + } + + BidirectionalIterator begin_; + BidirectionalIterator end_; + const regex_type *pregex_; + regex_constants::match_flag_type flags_; + match_type match_; + typename match_type::size_type submatch_; + bool prevmatch_empty_; +}; + +#endif // !defined(SRELL_NO_APIEXT) + +// ... "regex_iterator.hpp"] +// ["regex_algorithm.hpp" ... + +template +bool regex_match( + const BidirectionalIterator first, + const BidirectionalIterator last, + match_results &m, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return e.search(first, last, first, m, flags | regex_constants::match_continuous | regex_constants::match_match_); +} + +template +bool regex_match( + const BidirectionalIterator first, + const BidirectionalIterator last, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + match_results what; + + return regex_match(first, last, what, e, flags); +} + +template +bool regex_match( + const charT *const str, + match_results &m, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return regex_match(str, str + std::char_traits::length(str), m, e, flags); +} + +template +bool regex_match( + const charT *const str, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return regex_match(str, str + std::char_traits::length(str), e, flags); +} + +template +bool regex_match( + const std::basic_string &s, + match_results::const_iterator, Allocator> &m, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return regex_match(s.begin(), s.end(), m, e, flags); +} + +template +bool regex_match( + const std::basic_string &s, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return regex_match(s.begin(), s.end(), e, flags); +} + +template +bool regex_search( + const BidirectionalIterator first, + const BidirectionalIterator last, + const BidirectionalIterator lookbehind_limit, + match_results &m, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return e.search(first, last, lookbehind_limit, m, flags); +} + +template +bool regex_search( + const BidirectionalIterator first, + const BidirectionalIterator last, + const BidirectionalIterator lookbehind_limit, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + match_results what; + return regex_search(first, last, lookbehind_limit, what, e, flags); +} + +template +bool regex_search( + const std::basic_string &s, + const std::size_t start, + match_results::const_iterator, Allocator> &m, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return e.search(s.begin() + start, s.end(), s.begin(), m, flags); +} + +template +bool regex_search( + const BidirectionalIterator first, + const BidirectionalIterator last, + match_results &m, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return e.search(first, last, first, m, flags); +} + +template +bool regex_search( + const BidirectionalIterator first, + const BidirectionalIterator last, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + match_results what; + return regex_search(first, last, what, e, flags); +} + +template +bool regex_search( + const charT *const str, + match_results &m, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return regex_search(str, str + std::char_traits::length(str), m, e, flags); +} + +template +bool regex_search( + const charT *const str, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return regex_search(str, str + std::char_traits::length(str), e, flags); +} + +template +bool regex_search( + const std::basic_string &s, + match_results::const_iterator, Allocator> &m, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return regex_search(s.begin(), s.end(), m, e, flags); +} + +template +bool regex_search( + const std::basic_string &s, + const basic_regex &e, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + return regex_search(s.begin(), s.end(), e, flags); +} + +template +OutputIterator regex_replace( + OutputIterator out, + const BidirectionalIterator first, + const BidirectionalIterator last, + const basic_regex &e, + const std::basic_string &fmt, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + typedef regex_iterator iterator_type; + + const bool do_copy = !(flags & regex_constants::format_no_copy); + const iterator_type eos; + iterator_type i(first, last, e, flags); + typename iterator_type::value_type::value_type last_m_suffix; + + last_m_suffix.first = first; + last_m_suffix.second = last; + + for (; i != eos; ++i) + { + if (do_copy) + out = std::copy(i->prefix().first, i->prefix().second, out); + + out = i->format(out, fmt, flags); + last_m_suffix = i->suffix(); + + if (flags & regex_constants::format_first_only) + break; + } + + if (do_copy) + out = std::copy(last_m_suffix.first, last_m_suffix.second, out); + + return out; +} + +template +OutputIterator regex_replace( + OutputIterator out, + const BidirectionalIterator first, + const BidirectionalIterator last, + const basic_regex &e, + const charT *const fmt, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + const std::basic_string fs(fmt, fmt + std::char_traits::length(fmt)); + + return regex_replace(out, first, last, e, fs, flags); +} + +template +std::basic_string regex_replace( + const std::basic_string &s, + const basic_regex &e, + const std::basic_string &fmt, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + std::basic_string result; + + regex_replace(std::back_inserter(result), s.begin(), s.end(), e, fmt, flags); + return result; +} + +template +std::basic_string regex_replace( + const std::basic_string &s, + const basic_regex &e, + const charT *const fmt, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + std::basic_string result; + + regex_replace(std::back_inserter(result), s.begin(), s.end(), e, fmt, flags); + return result; +} + +template +std::basic_string regex_replace( + const charT *const s, + const basic_regex &e, + const std::basic_string &fmt, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + std::basic_string result; + + regex_replace(std::back_inserter(result), s, s + std::char_traits::length(s), e, fmt, flags); + return result; +} + +template +std::basic_string regex_replace( + const charT *const s, + const basic_regex &e, + const charT *const fmt, + const regex_constants::match_flag_type flags = regex_constants::match_default) +{ + std::basic_string result; + + regex_replace(std::back_inserter(result), s, s + std::char_traits::length(s), e, fmt, flags); + return result; +} + +// ... "regex_algorithm.hpp"] +// ["regex_token_iterator.hpp" ... + +template ::value_type, class traits = regex_traits > +class regex_token_iterator +{ +public: + + typedef basic_regex regex_type; + typedef sub_match value_type; + typedef std::ptrdiff_t difference_type; + typedef const value_type *pointer; + typedef const value_type &reference; + typedef std::forward_iterator_tag iterator_category; + + regex_token_iterator() : result_(NULL) + { + // Constructs the end-of-sequence iterator. + } + + regex_token_iterator( + const BidirectionalIterator a, + const BidirectionalIterator b, + const regex_type &re, + const int submatch = 0, + regex_constants::match_flag_type m = regex_constants::match_default + ) : position_(a, b, re, m), result_(NULL), subs_(2) + { + post_constructor_(a, b, &submatch, 1); + } + + regex_token_iterator( + const BidirectionalIterator a, + const BidirectionalIterator b, + const regex_type &re, + const std::vector &submatches, + regex_constants::match_flag_type m = regex_constants::match_default + ) : position_(a, b, re, m), result_(NULL), subs_(submatches.size() + 1) + { + post_constructor_(a, b, submatches.begin(), submatches.size()); + } + +#if defined(__cpp_initializer_lists) + regex_token_iterator( + const BidirectionalIterator a, + const BidirectionalIterator b, + const regex_type &re, + const std::initializer_list submatches, + regex_constants::match_flag_type m = regex_constants::match_default + ) : position_(a, b, re, m), result_(NULL), subs_(submatches.size() + 1) + { + post_constructor_(a, b, submatches.begin(), submatches.size()); + } +#endif + + template // Was R in TR1. + regex_token_iterator( + const BidirectionalIterator a, + const BidirectionalIterator b, + const regex_type &re, + const int (&submatches)[N], + regex_constants::match_flag_type m = regex_constants::match_default + ) : position_(a, b, re, m), result_(NULL), subs_(N + 1) + { + post_constructor_(a, b, submatches, N); + } + + regex_token_iterator(const regex_token_iterator &that) + { + operator=(that); + } + + regex_token_iterator &operator=(const regex_token_iterator &that) + { + if (this != &that) + { + this->result_ = that.result_; + if (this->result_) + { + this->position_ = that.position_; + this->suffix_ = that.suffix_; + this->N_ = that.N_; + this->subs_ = that.subs_; + + if (that.result_ == &that.suffix_) + result_ = &suffix_; + else + result_ = subs_[this->N_] != -1 ? &((*position_)[subs_[this->N_]]) : &((*position_).prefix()); + } + } + return *this; + } + + bool operator==(const regex_token_iterator &right) const + { + if (right.result_ == NULL || this->result_ == NULL) + return this->result_ == right.result_; + + if (this->result_ == &this->suffix_ || right.result_ == &right.suffix_) + return this->suffix_ == right.suffix_; + + return this->position_ == right.position_ + && this->N_ == right.N_ + && this->subs_ == right.subs_; + } + + bool operator!=(const regex_token_iterator &right) const + { + return !(*this == right); + } + + const value_type &operator*() const + { + return *result_; + } + + const value_type *operator->() const + { + return result_; + } + + regex_token_iterator &operator++() + { + if (result_ == &suffix_) + result_ = NULL; + else if (result_ != NULL) + { + if (++this->N_ >= subs_.size()) + { + this->N_ = 1; + suffix_ = position_->suffix(); + if ((++position_)->size() == 0) + { + result_ = (suffix_.matched && subs_[0] == -1) ? &suffix_ : NULL; + return *this; + } + } + result_ = subs_[this->N_] != -1 ? &((*position_)[subs_[this->N_]]) : &((*position_).prefix()); + } + return *this; + } + + regex_token_iterator operator++(int) + { + const regex_token_iterator tmp(*this); + ++(*this); + return tmp; + } + + regex_constants::error_type ecode() const + { + return position_->ecode(); + } + +private: + + template + void post_constructor_(const BidirectionalIterator a, const BidirectionalIterator b, Iterator it, const std::size_t num) + { + this->N_ = 1; + + subs_[0] = 0; + for (std::size_t i = 0; i < num; ++i, ++it) + { + this->subs_[i + 1] = *it; + if (*it == -1) + subs_[0] = -1; + } + + if (position_->size() && this->N_ < subs_.size()) + { + result_ = subs_[this->N_] != -1 ? &((*position_)[subs_[this->N_]]) : &((*position_).prefix()); + return; + } + + if (subs_[0] == -1) + { + suffix_.matched = a != b; + + if (suffix_.matched) + { + suffix_.first = a; + suffix_.second = b; + result_ = &suffix_; + return; + } + } + result_ = NULL; + } + +private: + + typedef regex_iterator position_iterator; + position_iterator position_; + const value_type *result_; + value_type suffix_; + std::size_t N_; + re_detail::simple_array subs_; +}; + +// ... "regex_token_iterator.hpp"] + +typedef sub_match csub_match; +typedef sub_match wcsub_match; +typedef sub_match ssub_match; +typedef sub_match wssub_match; +typedef csub_match u8ccsub_match; +typedef ssub_match u8cssub_match; + +typedef match_results cmatch; +typedef match_results wcmatch; +typedef match_results smatch; +typedef match_results wsmatch; +typedef cmatch u8ccmatch; +typedef smatch u8csmatch; + +typedef basic_regex regex; +typedef basic_regex wregex; +typedef basic_regex > u8cregex; + +typedef regex_iterator cregex_iterator; +typedef regex_iterator wcregex_iterator; +typedef regex_iterator sregex_iterator; +typedef regex_iterator wsregex_iterator; + +typedef regex_iterator::value_type, u8regex_traits::value_type> > u8ccregex_iterator; +typedef regex_iterator::value_type, u8regex_traits::value_type> > u8csregex_iterator; + +typedef regex_iterator2 cregex_iterator2; +typedef regex_iterator2 wcregex_iterator2; +typedef regex_iterator2 sregex_iterator2; +typedef regex_iterator2 wsregex_iterator2; + +typedef regex_iterator2 u8ccregex_iterator2; +typedef regex_iterator2 u8csregex_iterator2; + +typedef regex_token_iterator cregex_token_iterator; +typedef regex_token_iterator wcregex_token_iterator; +typedef regex_token_iterator sregex_token_iterator; +typedef regex_token_iterator wsregex_token_iterator; + +typedef regex_token_iterator::value_type, u8regex_traits::value_type> > u8ccregex_token_iterator; +typedef regex_token_iterator::value_type, u8regex_traits::value_type> > u8csregex_token_iterator; + +#if defined(WCHAR_MAX) + #if (WCHAR_MAX >= 0x10ffff) + typedef wcsub_match u32wcsub_match; + typedef wssub_match u32wssub_match; + typedef u32wcsub_match u1632wcsub_match; + typedef u32wssub_match u1632wssub_match; + + typedef wcmatch u32wcmatch; + typedef wsmatch u32wsmatch; + typedef u32wcmatch u1632wcmatch; + typedef u32wsmatch u1632wsmatch; + + typedef wregex u32wregex; + typedef u32wregex u1632wregex; + + typedef wcregex_iterator u32wcregex_iterator; + typedef wsregex_iterator u32wsregex_iterator; + typedef u32wcregex_iterator u1632wcregex_iterator; + typedef u32wsregex_iterator u1632wsregex_iterator; + + typedef wcregex_iterator2 u32wcregex_iterator2; + typedef wsregex_iterator2 u32wsregex_iterator2; + typedef u32wcregex_iterator2 u1632wcregex_iterator2; + typedef u32wsregex_iterator2 u1632wsregex_iterator2; + + typedef wcregex_token_iterator u32wcregex_token_iterator; + typedef wsregex_token_iterator u32wsregex_token_iterator; + typedef u32wcregex_token_iterator u1632wcregex_token_iterator; + typedef u32wsregex_token_iterator u1632wsregex_token_iterator; + #elif (WCHAR_MAX >= 0xffff) + typedef wcsub_match u16wcsub_match; + typedef wssub_match u16wssub_match; + typedef u16wcsub_match u1632wcsub_match; + typedef u16wssub_match u1632wssub_match; + + typedef wcmatch u16wcmatch; + typedef wsmatch u16wsmatch; + typedef u16wcmatch u1632wcmatch; + typedef u16wsmatch u1632wsmatch; + + typedef basic_regex > u16wregex; + typedef u16wregex u1632wregex; + + typedef regex_iterator::value_type, u16regex_traits::value_type> > u16wcregex_iterator; + typedef regex_iterator::value_type, u16regex_traits::value_type> > u16wsregex_iterator; + typedef u16wcregex_iterator u1632wcregex_iterator; + typedef u16wsregex_iterator u1632wsregex_iterator; + + typedef regex_iterator2 u16wcregex_iterator2; + typedef regex_iterator2 u16wsregex_iterator2; + typedef u16wcregex_iterator2 u1632wcregex_iterator2; + typedef u16wsregex_iterator2 u1632wsregex_iterator2; + + typedef regex_token_iterator::value_type, u16regex_traits::value_type> > u16wcregex_token_iterator; + typedef regex_token_iterator::value_type, u16regex_traits::value_type> > u16wsregex_token_iterator; + typedef u16wcregex_token_iterator u1632wcregex_token_iterator; + typedef u16wsregex_token_iterator u1632wsregex_token_iterator; + #endif +#endif + +#if defined(__cpp_unicode_characters) + typedef sub_match u16csub_match; + typedef sub_match u32csub_match; + typedef sub_match u16ssub_match; + typedef sub_match u32ssub_match; + + typedef match_results u16cmatch; + typedef match_results u32cmatch; + typedef match_results u16smatch; + typedef match_results u32smatch; + + typedef basic_regex u16regex; + typedef basic_regex u32regex; + + typedef regex_iterator u16cregex_iterator; + typedef regex_iterator u32cregex_iterator; + typedef regex_iterator u16sregex_iterator; + typedef regex_iterator u32sregex_iterator; + + typedef regex_iterator2 u16cregex_iterator2; + typedef regex_iterator2 u32cregex_iterator2; + typedef regex_iterator2 u16sregex_iterator2; + typedef regex_iterator2 u32sregex_iterator2; + + typedef regex_token_iterator u16cregex_token_iterator; + typedef regex_token_iterator u32cregex_token_iterator; + typedef regex_token_iterator u16sregex_token_iterator; + typedef regex_token_iterator u32sregex_token_iterator; +#endif + +#if defined(__cpp_char8_t) + #if defined(__cpp_lib_char8_t) + #define SRELLTMP_U8S_CI std::u8string::const_iterator + #else + #define SRELLTMP_U8S_CI std::basic_string::const_iterator + #endif + typedef sub_match u8csub_match; + typedef sub_match u8ssub_match; + + typedef match_results u8cmatch; + typedef match_results u8smatch; + + typedef basic_regex u8regex; + + typedef regex_iterator u8cregex_iterator; + typedef regex_iterator u8sregex_iterator; + + typedef regex_iterator2 u8cregex_iterator2; + typedef regex_iterator2 u8sregex_iterator2; + + typedef regex_token_iterator u8cregex_token_iterator; + typedef regex_token_iterator u8sregex_token_iterator; + #undef SRELLTMP_U8S_CI +#else + typedef u8ccsub_match u8csub_match; + typedef u8cssub_match u8ssub_match; + + typedef u8ccmatch u8cmatch; + typedef u8csmatch u8smatch; + + typedef u8cregex u8regex; + + typedef u8ccregex_iterator u8cregex_iterator; + typedef u8csregex_iterator u8sregex_iterator; + + typedef u8ccregex_iterator2 u8cregex_iterator2; + typedef u8csregex_iterator2 u8sregex_iterator2; + + typedef u8ccregex_token_iterator u8cregex_token_iterator; + typedef u8csregex_token_iterator u8sregex_token_iterator; +#endif + +#undef SRELL_FORCEINLINE +#undef SRELL_IFCE +#undef SRELL_STACON +#undef SRELL_NOEXCEPT +#undef SRELL_NO_VCWARNING_END +#undef SRELL_NO_VCWARNING + +} // namespace srell + +#undef SRELL_AT_SSE42 +#undef SRELL_HAS_SSE42 +#undef SRELL_HAS_TYPE_TRAITS + +#endif // SRELL_HPP_ diff --git a/pjsonlib/src/third_party/srell/srell_ucfdata2.h b/pjsonlib/src/third_party/srell/srell_ucfdata2.h new file mode 100644 index 0000000..10a8108 --- /dev/null +++ b/pjsonlib/src/third_party/srell/srell_ucfdata2.h @@ -0,0 +1,2614 @@ +// CaseFolding-17.0.0.txt +// Date: 2025-07-30, 23:54:36 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html + +template +struct unicode_casefolding +{ + static const T1 ucf_maxcodepoint = 0x1E921; + static const T2 ucf_deltatablesize = 0x1B00; + static const T1 rev_maxcodepoint = 0x1E943; + static const T2 rev_indextablesize = 0x1D00; + static const T2 rev_charsettablesize = 4477; // 1 + 1482 * 2 + 1512 + static const T2 rev_maxset = 4; + static const T1 eos = 0; + + static const T1 ucf_deltatable[]; + static const T2 ucf_segmenttable[]; + static const T2 rev_indextable[]; + static const T2 rev_segmenttable[]; + static const T1 rev_charsettable[]; +}; +template + const T1 unicode_casefolding::ucf_maxcodepoint; +template + const T2 unicode_casefolding::ucf_deltatablesize; +template + const T1 unicode_casefolding::rev_maxcodepoint; +template + const T2 unicode_casefolding::rev_indextablesize; +template + const T2 unicode_casefolding::rev_charsettablesize; +template + const T2 unicode_casefolding::rev_maxset; +template + const T1 unicode_casefolding::eos; + +template +const T1 unicode_casefolding::ucf_deltatable[] = +{ + // For common (0) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+00xx (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, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 775, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 0, 32, 32, 32, 32, 32, 32, 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, + + // For u+01xx (512) + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, static_cast(-121), 1, 0, 1, 0, 1, 0, static_cast(-268), + 0, 210, 1, 0, 1, 0, 206, 1, 0, 205, 205, 1, 0, 0, 79, 202, + 203, 1, 0, 205, 207, 0, 211, 209, 1, 0, 0, 0, 211, 213, 0, 214, + 1, 0, 1, 0, 1, 0, 218, 1, 0, 218, 0, 0, 1, 0, 218, 1, + 0, 217, 217, 1, 0, 1, 0, 219, 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 2, 1, 0, 2, 1, 0, 2, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 2, 1, 0, 1, 0, static_cast(-97), static_cast(-56), 1, 0, 1, 0, 1, 0, 1, 0, + + // For u+02xx (768) + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + static_cast(-130), 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10795, 1, 0, static_cast(-163), 10792, 0, + 0, 1, 0, static_cast(-195), 69, 71, 1, 0, 1, 0, 1, 0, 1, 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, + + // For u+03xx (1024) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 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, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 116, + 0, 0, 0, 0, 0, 0, 38, 0, 37, 37, 37, 0, 64, 0, 63, 63, + 0, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 0, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, 8, + static_cast(-30), static_cast(-25), 0, 0, 0, static_cast(-15), static_cast(-22), 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + static_cast(-54), static_cast(-48), 0, 0, static_cast(-60), static_cast(-64), 0, 1, 0, static_cast(-7), 1, 0, 0, static_cast(-130), static_cast(-130), static_cast(-130), + + // For u+04xx (1280) + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 15, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + + // For u+05xx (1536) + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+10xx (1792) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, + 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, 7264, + 7264, 7264, 7264, 7264, 7264, 7264, 0, 7264, 0, 0, 0, 0, 0, 7264, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+13xx (2048) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), 0, 0, + + // For u+1Cxx (2304) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + static_cast(-6222), static_cast(-6221), static_cast(-6212), static_cast(-6210), static_cast(-6210), static_cast(-6211), static_cast(-6204), static_cast(-6180), 35267, 1, 0, 0, 0, 0, 0, 0, + static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), + static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), + static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), static_cast(-3008), 0, 0, static_cast(-3008), static_cast(-3008), static_cast(-3008), + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+1Exx (2560) + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, static_cast(-58), 0, 0, static_cast(-7615), 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + + // For u+1Fxx (2816) + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), 0, static_cast(-8), 0, static_cast(-8), 0, static_cast(-8), + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-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, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), static_cast(-8), + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-74), static_cast(-74), static_cast(-9), 0, static_cast(-7173), 0, + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-86), static_cast(-86), static_cast(-86), static_cast(-86), static_cast(-9), 0, 0, 0, + 0, 0, 0, static_cast(-7235), 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-100), static_cast(-100), 0, 0, 0, 0, + 0, 0, 0, static_cast(-7219), 0, 0, 0, 0, static_cast(-8), static_cast(-8), static_cast(-112), static_cast(-112), static_cast(-7), 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-128), static_cast(-128), static_cast(-126), static_cast(-126), static_cast(-9), 0, 0, 0, + + // For u+21xx (3072) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, static_cast(-7517), 0, 0, 0, static_cast(-8383), static_cast(-8262), 0, 0, 0, 0, + 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 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, + + // For u+24xx (3328) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 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, + + // For u+2Cxx (3584) + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 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, static_cast(-10743), static_cast(-3814), static_cast(-10727), 0, 0, 1, 0, 1, 0, 1, 0, static_cast(-10780), static_cast(-10749), static_cast(-10783), + static_cast(-10782), 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, static_cast(-10815), static_cast(-10815), + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+A6xx (3840) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 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, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 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, + + // For u+A7xx (4096) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, static_cast(-35332), 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, static_cast(-42280), 0, 0, + 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, static_cast(-42308), static_cast(-42319), static_cast(-42315), static_cast(-42305), static_cast(-42308), 0, + static_cast(-42258), static_cast(-42282), static_cast(-42261), 928, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, static_cast(-48), static_cast(-42307), static_cast(-35384), 1, 0, 1, 0, static_cast(-42343), 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, static_cast(-42561), 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, + + // For u+ABxx (4352) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), + static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), + static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), + static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), + static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), static_cast(-38864), + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+FBxx (4608) + 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, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+FFxx (4864) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+104xx (5120) + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, + 40, 40, 40, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+105xx (5376) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 0, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 0, 39, 39, 39, 39, + 39, 39, 39, 0, 39, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+10Cxx (5632) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+10Dxx (5888) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 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, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+118xx (6144) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 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, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+16Exx (6400) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 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, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 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, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+1E9xx (6656) + 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, + 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, + 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +template +const T2 unicode_casefolding::ucf_segmenttable[] = +{ + 256, 512, 768, 1024, 1280, 1536, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1792, 0, 0, 2048, 0, 0, 0, 0, 0, 0, 0, 0, 2304, 0, 2560, 2816, + 0, 3072, 0, 0, 3328, 0, 0, 0, 0, 0, 0, 0, 3584, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3840, 4096, 0, 0, 0, 4352, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4608, 0, 0, 0, 4864, + 0, 0, 0, 0, 5120, 5376, 0, 0, 0, 0, 0, 0, 5632, 5888, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 6144, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6400, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 6656 +}; + +template +const T2 unicode_casefolding::rev_indextable[] = +{ + // For common (0) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+00xx (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, 1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 35, 38, 41, 44, + 47, 50, 53, 56, 60, 63, 66, 69, 72, 75, 78, 0, 0, 0, 0, 0, + 0, 1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 35, 38, 41, 44, + 47, 50, 53, 56, 60, 63, 66, 69, 72, 75, 78, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 85, 88, 91, 94, 97, 100, 104, 107, 110, 113, 116, 119, 122, 125, 128, 131, + 134, 137, 140, 143, 146, 149, 152, 0, 155, 158, 161, 164, 167, 170, 173, 1927, + 85, 88, 91, 94, 97, 100, 104, 107, 110, 113, 116, 119, 122, 125, 128, 131, + 134, 137, 140, 143, 146, 149, 152, 0, 155, 158, 161, 164, 167, 170, 173, 350, + + // For u+21xx (512) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 785, 0, 0, 0, 31, 100, 0, 0, 0, 0, + 0, 0, 2368, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2368, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2371, 2374, 2377, 2380, 2383, 2386, 2389, 2392, 2395, 2398, 2401, 2404, 2407, 2410, 2413, 2416, + 2371, 2374, 2377, 2380, 2383, 2386, 2389, 2392, 2395, 2398, 2401, 2404, 2407, 2410, 2413, 2416, + 0, 0, 0, 2419, 2419, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+01xx (768) + 176, 176, 179, 179, 182, 182, 185, 185, 188, 188, 191, 191, 194, 194, 197, 197, + 200, 200, 203, 203, 206, 206, 209, 209, 212, 212, 215, 215, 218, 218, 221, 221, + 224, 224, 227, 227, 230, 230, 233, 233, 236, 236, 239, 239, 242, 242, 245, 245, + 0, 0, 248, 248, 251, 251, 254, 254, 0, 257, 257, 260, 260, 263, 263, 266, + 266, 269, 269, 272, 272, 275, 275, 278, 278, 0, 281, 281, 284, 284, 287, 287, + 290, 290, 293, 293, 296, 296, 299, 299, 302, 302, 305, 305, 308, 308, 311, 311, + 314, 314, 317, 317, 320, 320, 323, 323, 326, 326, 329, 329, 332, 332, 335, 335, + 338, 338, 341, 341, 344, 344, 347, 347, 350, 353, 353, 356, 356, 359, 359, 56, + 651, 362, 365, 365, 368, 368, 371, 374, 374, 377, 380, 383, 383, 0, 386, 389, + 392, 395, 395, 398, 401, 540, 404, 407, 410, 410, 642, 3229, 413, 416, 606, 419, + 422, 422, 425, 425, 428, 428, 431, 434, 434, 437, 0, 0, 440, 440, 443, 446, + 446, 449, 452, 455, 455, 458, 458, 461, 464, 464, 0, 0, 467, 467, 0, 543, + 0, 0, 0, 0, 470, 470, 470, 474, 474, 474, 478, 478, 478, 482, 482, 485, + 485, 488, 488, 491, 491, 494, 494, 497, 497, 500, 500, 503, 503, 386, 506, 506, + 509, 509, 512, 512, 515, 515, 518, 518, 521, 521, 524, 524, 527, 527, 530, 530, + 0, 533, 533, 533, 537, 537, 540, 543, 546, 546, 549, 549, 552, 552, 555, 555, + + // For u+03xx (1024) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 675, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 680, 680, 683, 683, 0, 0, 686, 686, 0, 0, 0, 843, 846, 849, 0, 689, + 0, 0, 0, 0, 0, 0, 692, 0, 695, 698, 701, 0, 704, 0, 707, 710, + 2320, 713, 716, 720, 723, 726, 730, 733, 736, 675, 741, 745, 81, 748, 751, 754, + 757, 761, 0, 765, 769, 772, 775, 779, 782, 785, 789, 792, 692, 695, 698, 701, + 2335, 713, 716, 720, 723, 726, 730, 733, 736, 675, 741, 745, 81, 748, 751, 754, + 757, 761, 765, 765, 769, 772, 775, 779, 782, 785, 789, 792, 704, 707, 710, 795, + 716, 736, 0, 0, 0, 775, 757, 795, 798, 798, 801, 801, 804, 804, 807, 807, + 810, 810, 813, 813, 816, 816, 819, 819, 822, 822, 825, 825, 828, 828, 831, 831, + 741, 761, 837, 689, 736, 726, 0, 834, 834, 837, 840, 840, 0, 843, 846, 849, + + // For u+02xx (1280) + 558, 558, 561, 561, 564, 564, 567, 567, 570, 570, 573, 573, 576, 576, 579, 579, + 582, 582, 585, 585, 588, 588, 591, 591, 594, 594, 597, 597, 600, 600, 603, 603, + 606, 0, 609, 609, 612, 612, 615, 615, 618, 618, 621, 621, 624, 624, 627, 627, + 630, 630, 633, 633, 0, 0, 0, 0, 0, 0, 636, 639, 639, 642, 645, 2683, + 2686, 648, 648, 651, 654, 657, 660, 660, 663, 663, 666, 666, 669, 669, 672, 672, + 2671, 2665, 2674, 362, 371, 0, 377, 380, 0, 389, 0, 392, 3139, 0, 0, 0, + 398, 3142, 0, 401, 3202, 3097, 3136, 0, 407, 404, 3148, 2647, 3145, 0, 0, 413, + 0, 2668, 416, 0, 0, 419, 0, 0, 0, 0, 0, 0, 0, 2653, 0, 0, + 431, 0, 3190, 437, 0, 0, 0, 3154, 443, 654, 449, 452, 657, 0, 0, 0, + 0, 0, 461, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3157, 3151, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+2Cxx (1536) + 2500, 2503, 2506, 2509, 2512, 2515, 2518, 2521, 2524, 2527, 2530, 2533, 2536, 2539, 2542, 2545, + 2548, 2551, 2554, 2557, 2560, 2563, 2566, 2569, 2572, 2575, 2578, 2581, 2584, 2587, 2590, 2593, + 2596, 2599, 2602, 2605, 2608, 2611, 2614, 2617, 2620, 2623, 2626, 2629, 2632, 2635, 2638, 2641, + 2500, 2503, 2506, 2509, 2512, 2515, 2518, 2521, 2524, 2527, 2530, 2533, 2536, 2539, 2542, 2545, + 2548, 2551, 2554, 2557, 2560, 2563, 2566, 2569, 2572, 2575, 2578, 2581, 2584, 2587, 2590, 2593, + 2596, 2599, 2602, 2605, 2608, 2611, 2614, 2617, 2620, 2623, 2626, 2629, 2632, 2635, 2638, 2641, + 2644, 2644, 2647, 2650, 2653, 636, 645, 2656, 2656, 2659, 2659, 2662, 2662, 2665, 2668, 2671, + 2674, 0, 2677, 2677, 0, 2680, 2680, 0, 0, 0, 0, 0, 0, 0, 2683, 2686, + 2689, 2689, 2692, 2692, 2695, 2695, 2698, 2698, 2701, 2701, 2704, 2704, 2707, 2707, 2710, 2710, + 2713, 2713, 2716, 2716, 2719, 2719, 2722, 2722, 2725, 2725, 2728, 2728, 2731, 2731, 2734, 2734, + 2737, 2737, 2740, 2740, 2743, 2743, 2746, 2746, 2749, 2749, 2752, 2752, 2755, 2755, 2758, 2758, + 2761, 2761, 2764, 2764, 2767, 2767, 2770, 2770, 2773, 2773, 2776, 2776, 2779, 2779, 2782, 2782, + 2785, 2785, 2788, 2788, 2791, 2791, 2794, 2794, 2797, 2797, 2800, 2800, 2803, 2803, 2806, 2806, + 2809, 2809, 2812, 2812, 2815, 2815, 2818, 2818, 2821, 2821, 2824, 2824, 2827, 2827, 2830, 2830, + 2833, 2833, 2836, 2836, 0, 0, 0, 0, 0, 0, 0, 2839, 2839, 2842, 2842, 0, + 0, 0, 2845, 2845, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+1Fxx (1792) + 2074, 2077, 2080, 2083, 2086, 2089, 2092, 2095, 2074, 2077, 2080, 2083, 2086, 2089, 2092, 2095, + 2098, 2101, 2104, 2107, 2110, 2113, 0, 0, 2098, 2101, 2104, 2107, 2110, 2113, 0, 0, + 2116, 2119, 2122, 2125, 2128, 2131, 2134, 2137, 2116, 2119, 2122, 2125, 2128, 2131, 2134, 2137, + 2140, 2143, 2146, 2149, 2152, 2155, 2158, 2161, 2140, 2143, 2146, 2149, 2152, 2155, 2158, 2161, + 2164, 2167, 2170, 2173, 2176, 2179, 0, 0, 2164, 2167, 2170, 2173, 2176, 2179, 0, 0, + 0, 2182, 0, 2185, 0, 2188, 0, 2191, 0, 2182, 0, 2185, 0, 2188, 0, 2191, + 2194, 2197, 2200, 2203, 2206, 2209, 2212, 2215, 2194, 2197, 2200, 2203, 2206, 2209, 2212, 2215, + 2296, 2299, 2305, 2308, 2311, 2314, 2329, 2332, 2353, 2356, 2344, 2347, 2359, 2362, 0, 0, + 2218, 2221, 2224, 2227, 2230, 2233, 2236, 2239, 2218, 2221, 2224, 2227, 2230, 2233, 2236, 2239, + 2242, 2245, 2248, 2251, 2254, 2257, 2260, 2263, 2242, 2245, 2248, 2251, 2254, 2257, 2260, 2263, + 2266, 2269, 2272, 2275, 2278, 2281, 2284, 2287, 2266, 2269, 2272, 2275, 2278, 2281, 2284, 2287, + 2290, 2293, 0, 2302, 0, 0, 0, 0, 2290, 2293, 2296, 2299, 2302, 0, 675, 0, + 0, 0, 0, 2317, 0, 0, 0, 0, 2305, 2308, 2311, 2314, 2317, 0, 0, 0, + 2323, 2326, 0, 2320, 0, 0, 0, 0, 2323, 2326, 2329, 2332, 0, 0, 0, 0, + 2338, 2341, 0, 2335, 0, 2350, 0, 0, 2338, 2341, 2344, 2347, 2350, 0, 0, 0, + 0, 0, 0, 2365, 0, 0, 0, 0, 2353, 2356, 2359, 2362, 2365, 0, 0, 0, + + // For u+04xx (2048) + 852, 855, 858, 861, 864, 867, 870, 873, 876, 879, 882, 885, 888, 891, 894, 897, + 900, 903, 906, 910, 913, 917, 920, 923, 926, 929, 932, 935, 938, 941, 944, 948, + 951, 954, 958, 963, 966, 969, 972, 975, 978, 981, 984, 988, 991, 994, 997, 1000, + 900, 903, 906, 910, 913, 917, 920, 923, 926, 929, 932, 935, 938, 941, 944, 948, + 951, 954, 958, 963, 966, 969, 972, 975, 978, 981, 984, 988, 991, 994, 997, 1000, + 852, 855, 858, 861, 864, 867, 870, 873, 876, 879, 882, 885, 888, 891, 894, 897, + 1003, 1003, 1006, 1006, 1010, 1010, 1013, 1013, 1016, 1016, 1019, 1019, 1022, 1022, 1025, 1025, + 1028, 1028, 1031, 1031, 1034, 1034, 1037, 1037, 1040, 1040, 1043, 1043, 1046, 1046, 1049, 1049, + 1052, 1052, 0, 0, 0, 0, 0, 0, 0, 0, 1055, 1055, 1058, 1058, 1061, 1061, + 1064, 1064, 1067, 1067, 1070, 1070, 1073, 1073, 1076, 1076, 1079, 1079, 1082, 1082, 1085, 1085, + 1088, 1088, 1091, 1091, 1094, 1094, 1097, 1097, 1100, 1100, 1103, 1103, 1106, 1106, 1109, 1109, + 1112, 1112, 1115, 1115, 1118, 1118, 1121, 1121, 1124, 1124, 1127, 1127, 1130, 1130, 1133, 1133, + 1136, 1139, 1139, 1142, 1142, 1145, 1145, 1148, 1148, 1151, 1151, 1154, 1154, 1157, 1157, 1136, + 1160, 1160, 1163, 1163, 1166, 1166, 1169, 1169, 1172, 1172, 1175, 1175, 1178, 1178, 1181, 1181, + 1184, 1184, 1187, 1187, 1190, 1190, 1193, 1193, 1196, 1196, 1199, 1199, 1202, 1202, 1205, 1205, + 1208, 1208, 1211, 1211, 1214, 1214, 1217, 1217, 1220, 1220, 1223, 1223, 1226, 1226, 1229, 1229, + + // For u+1Cxx (2304) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 906, 913, 944, 954, 958, 958, 984, 1006, 1556, 1560, 1560, 0, 0, 0, 0, 0, + 1563, 1566, 1569, 1572, 1575, 1578, 1581, 1584, 1587, 1590, 1593, 1596, 1599, 1602, 1605, 1608, + 1611, 1614, 1617, 1620, 1623, 1626, 1629, 1632, 1635, 1638, 1641, 1644, 1647, 1650, 1653, 1656, + 1659, 1662, 1665, 1668, 1671, 1674, 1677, 1680, 1683, 1686, 1689, 0, 0, 1692, 1695, 1698, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+05xx (2560) + 1232, 1232, 1235, 1235, 1238, 1238, 1241, 1241, 1244, 1244, 1247, 1247, 1250, 1250, 1253, 1253, + 1256, 1256, 1259, 1259, 1262, 1262, 1265, 1265, 1268, 1268, 1271, 1271, 1274, 1274, 1277, 1277, + 1280, 1280, 1283, 1283, 1286, 1286, 1289, 1289, 1292, 1292, 1295, 1295, 1298, 1298, 1301, 1301, + 0, 1304, 1307, 1310, 1313, 1316, 1319, 1322, 1325, 1328, 1331, 1334, 1337, 1340, 1343, 1346, + 1349, 1352, 1355, 1358, 1361, 1364, 1367, 1370, 1373, 1376, 1379, 1382, 1385, 1388, 1391, 1394, + 1397, 1400, 1403, 1406, 1409, 1412, 1415, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1304, 1307, 1310, 1313, 1316, 1319, 1322, 1325, 1328, 1331, 1334, 1337, 1340, 1343, 1346, + 1349, 1352, 1355, 1358, 1361, 1364, 1367, 1370, 1373, 1376, 1379, 1382, 1385, 1388, 1391, 1394, + 1397, 1400, 1403, 1406, 1409, 1412, 1415, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+2Dxx (2816) + 1418, 1421, 1424, 1427, 1430, 1433, 1436, 1439, 1442, 1445, 1448, 1451, 1454, 1457, 1460, 1463, + 1466, 1469, 1472, 1475, 1478, 1481, 1484, 1487, 1490, 1493, 1496, 1499, 1502, 1505, 1508, 1511, + 1514, 1517, 1520, 1523, 1526, 1529, 0, 1532, 0, 0, 0, 0, 0, 1535, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+10xx (3072) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1418, 1421, 1424, 1427, 1430, 1433, 1436, 1439, 1442, 1445, 1448, 1451, 1454, 1457, 1460, 1463, + 1466, 1469, 1472, 1475, 1478, 1481, 1484, 1487, 1490, 1493, 1496, 1499, 1502, 1505, 1508, 1511, + 1514, 1517, 1520, 1523, 1526, 1529, 0, 1532, 0, 0, 0, 0, 0, 1535, 0, 0, + 1563, 1566, 1569, 1572, 1575, 1578, 1581, 1584, 1587, 1590, 1593, 1596, 1599, 1602, 1605, 1608, + 1611, 1614, 1617, 1620, 1623, 1626, 1629, 1632, 1635, 1638, 1641, 1644, 1647, 1650, 1653, 1656, + 1659, 1662, 1665, 1668, 1671, 1674, 1677, 1680, 1683, 1686, 1689, 0, 0, 1692, 1695, 1698, + + // For u+13xx (3328) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3235, 3238, 3241, 3244, 3247, 3250, 3253, 3256, 3259, 3262, 3265, 3268, 3271, 3274, 3277, 3280, + 3283, 3286, 3289, 3292, 3295, 3298, 3301, 3304, 3307, 3310, 3313, 3316, 3319, 3322, 3325, 3328, + 3331, 3334, 3337, 3340, 3343, 3346, 3349, 3352, 3355, 3358, 3361, 3364, 3367, 3370, 3373, 3376, + 3379, 3382, 3385, 3388, 3391, 3394, 3397, 3400, 3403, 3406, 3409, 3412, 3415, 3418, 3421, 3424, + 3427, 3430, 3433, 3436, 3439, 3442, 3445, 3448, 3451, 3454, 3457, 3460, 3463, 3466, 3469, 3472, + 1538, 1541, 1544, 1547, 1550, 1553, 0, 0, 1538, 1541, 1544, 1547, 1550, 1553, 0, 0, + + // For u+A6xx (3584) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2848, 2848, 2851, 2851, 2854, 2854, 2857, 2857, 2860, 2860, 1556, 1556, 2863, 2863, 2866, 2866, + 2869, 2869, 2872, 2872, 2875, 2875, 2878, 2878, 2881, 2881, 2884, 2884, 2887, 2887, 2890, 2890, + 2893, 2893, 2896, 2896, 2899, 2899, 2902, 2902, 2905, 2905, 2908, 2908, 2911, 2911, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2914, 2914, 2917, 2917, 2920, 2920, 2923, 2923, 2926, 2926, 2929, 2929, 2932, 2932, 2935, 2935, + 2938, 2938, 2941, 2941, 2944, 2944, 2947, 2947, 2950, 2950, 2953, 2953, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+1Exx (3840) + 1701, 1701, 1704, 1704, 1707, 1707, 1710, 1710, 1713, 1713, 1716, 1716, 1719, 1719, 1722, 1722, + 1725, 1725, 1728, 1728, 1731, 1731, 1734, 1734, 1737, 1737, 1740, 1740, 1743, 1743, 1746, 1746, + 1749, 1749, 1752, 1752, 1755, 1755, 1758, 1758, 1761, 1761, 1764, 1764, 1767, 1767, 1770, 1770, + 1773, 1773, 1776, 1776, 1779, 1779, 1782, 1782, 1785, 1785, 1788, 1788, 1791, 1791, 1794, 1794, + 1797, 1797, 1800, 1800, 1803, 1803, 1806, 1806, 1809, 1809, 1812, 1812, 1815, 1815, 1818, 1818, + 1821, 1821, 1824, 1824, 1827, 1827, 1830, 1830, 1833, 1833, 1836, 1836, 1839, 1839, 1842, 1842, + 1845, 1845, 1849, 1849, 1852, 1852, 1855, 1855, 1858, 1858, 1861, 1861, 1864, 1864, 1867, 1867, + 1870, 1870, 1873, 1873, 1876, 1876, 1879, 1879, 1882, 1882, 1885, 1885, 1888, 1888, 1891, 1891, + 1894, 1894, 1897, 1897, 1900, 1900, 1903, 1903, 1906, 1906, 1909, 1909, 1912, 1912, 1915, 1915, + 1918, 1918, 1921, 1921, 1924, 1924, 0, 0, 0, 0, 0, 1845, 0, 0, 1927, 0, + 1930, 1930, 1933, 1933, 1936, 1936, 1939, 1939, 1942, 1942, 1945, 1945, 1948, 1948, 1951, 1951, + 1954, 1954, 1957, 1957, 1960, 1960, 1963, 1963, 1966, 1966, 1969, 1969, 1972, 1972, 1975, 1975, + 1978, 1978, 1981, 1981, 1984, 1984, 1987, 1987, 1990, 1990, 1993, 1993, 1996, 1996, 1999, 1999, + 2002, 2002, 2005, 2005, 2008, 2008, 2011, 2011, 2014, 2014, 2017, 2017, 2020, 2020, 2023, 2023, + 2026, 2026, 2029, 2029, 2032, 2032, 2035, 2035, 2038, 2038, 2041, 2041, 2044, 2044, 2047, 2047, + 2050, 2050, 2053, 2053, 2056, 2056, 2059, 2059, 2062, 2062, 2065, 2065, 2068, 2068, 2071, 2071, + + // For u+24xx (4096) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 2422, 2425, 2428, 2431, 2434, 2437, 2440, 2443, 2446, 2449, + 2452, 2455, 2458, 2461, 2464, 2467, 2470, 2473, 2476, 2479, 2482, 2485, 2488, 2491, 2494, 2497, + 2422, 2425, 2428, 2431, 2434, 2437, 2440, 2443, 2446, 2449, 2452, 2455, 2458, 2461, 2464, 2467, + 2470, 2473, 2476, 2479, 2482, 2485, 2488, 2491, 2494, 2497, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+1Dxx (4352) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3076, 0, 0, 0, 2650, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3193, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+A7xx (4608) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2956, 2956, 2959, 2959, 2962, 2962, 2965, 2965, 2968, 2968, 2971, 2971, 2974, 2974, + 0, 0, 2977, 2977, 2980, 2980, 2983, 2983, 2986, 2986, 2989, 2989, 2992, 2992, 2995, 2995, + 2998, 2998, 3001, 3001, 3004, 3004, 3007, 3007, 3010, 3010, 3013, 3013, 3016, 3016, 3019, 3019, + 3022, 3022, 3025, 3025, 3028, 3028, 3031, 3031, 3034, 3034, 3037, 3037, 3040, 3040, 3043, 3043, + 3046, 3046, 3049, 3049, 3052, 3052, 3055, 3055, 3058, 3058, 3061, 3061, 3064, 3064, 3067, 3067, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3070, 3070, 3073, 3073, 3076, 3079, 3079, + 3082, 3082, 3085, 3085, 3088, 3088, 3091, 3091, 0, 0, 0, 3094, 3094, 3097, 0, 0, + 3100, 3100, 3103, 3103, 3187, 0, 3106, 3106, 3109, 3109, 3112, 3112, 3115, 3115, 3118, 3118, + 3121, 3121, 3124, 3124, 3127, 3127, 3130, 3130, 3133, 3133, 3136, 3139, 3142, 3145, 3148, 0, + 3151, 3154, 3157, 3160, 3163, 3163, 3166, 3166, 3169, 3169, 3172, 3172, 3175, 3175, 3178, 3178, + 3181, 3181, 3184, 3184, 3187, 3190, 3193, 3196, 3196, 3199, 3199, 3202, 3205, 3205, 3208, 3208, + 3211, 3211, 3214, 3214, 3217, 3217, 3220, 3220, 3223, 3223, 3226, 3226, 3229, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 3232, 3232, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+ABxx (4864) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3235, 3238, 3241, 3244, 3247, 3250, 3253, 3256, 3259, 3262, 3265, 3268, 3271, 3274, 3277, 3280, + 3283, 3286, 3289, 3292, 3295, 3298, 3301, 3304, 3307, 3310, 3313, 3316, 3319, 3322, 3325, 3328, + 3331, 3334, 3337, 3340, 3343, 3346, 3349, 3352, 3355, 3358, 3361, 3364, 3367, 3370, 3373, 3376, + 3379, 3382, 3385, 3388, 3391, 3394, 3397, 3400, 3403, 3406, 3409, 3412, 3415, 3418, 3421, 3424, + 3427, 3430, 3433, 3436, 3439, 3442, 3445, 3448, 3451, 3454, 3457, 3460, 3463, 3466, 3469, 3472, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+FBxx (5120) + 0, 0, 0, 0, 0, 3475, 3475, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+FFxx (5376) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3478, 3481, 3484, 3487, 3490, 3493, 3496, 3499, 3502, 3505, 3508, 3511, 3514, 3517, 3520, + 3523, 3526, 3529, 3532, 3535, 3538, 3541, 3544, 3547, 3550, 3553, 0, 0, 0, 0, 0, + 0, 3478, 3481, 3484, 3487, 3490, 3493, 3496, 3499, 3502, 3505, 3508, 3511, 3514, 3517, 3520, + 3523, 3526, 3529, 3532, 3535, 3538, 3541, 3544, 3547, 3550, 3553, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+104xx (5632) + 3556, 3559, 3562, 3565, 3568, 3571, 3574, 3577, 3580, 3583, 3586, 3589, 3592, 3595, 3598, 3601, + 3604, 3607, 3610, 3613, 3616, 3619, 3622, 3625, 3628, 3631, 3634, 3637, 3640, 3643, 3646, 3649, + 3652, 3655, 3658, 3661, 3664, 3667, 3670, 3673, 3556, 3559, 3562, 3565, 3568, 3571, 3574, 3577, + 3580, 3583, 3586, 3589, 3592, 3595, 3598, 3601, 3604, 3607, 3610, 3613, 3616, 3619, 3622, 3625, + 3628, 3631, 3634, 3637, 3640, 3643, 3646, 3649, 3652, 3655, 3658, 3661, 3664, 3667, 3670, 3673, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3676, 3679, 3682, 3685, 3688, 3691, 3694, 3697, 3700, 3703, 3706, 3709, 3712, 3715, 3718, 3721, + 3724, 3727, 3730, 3733, 3736, 3739, 3742, 3745, 3748, 3751, 3754, 3757, 3760, 3763, 3766, 3769, + 3772, 3775, 3778, 3781, 0, 0, 0, 0, 3676, 3679, 3682, 3685, 3688, 3691, 3694, 3697, + 3700, 3703, 3706, 3709, 3712, 3715, 3718, 3721, 3724, 3727, 3730, 3733, 3736, 3739, 3742, 3745, + 3748, 3751, 3754, 3757, 3760, 3763, 3766, 3769, 3772, 3775, 3778, 3781, 0, 0, 0, 0, + + // For u+105xx (5888) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3784, 3787, 3790, 3793, 3796, 3799, 3802, 3805, 3808, 3811, 3814, 0, 3817, 3820, 3823, 3826, + 3829, 3832, 3835, 3838, 3841, 3844, 3847, 3850, 3853, 3856, 3859, 0, 3862, 3865, 3868, 3871, + 3874, 3877, 3880, 0, 3883, 3886, 0, 3784, 3787, 3790, 3793, 3796, 3799, 3802, 3805, 3808, + 3811, 3814, 0, 3817, 3820, 3823, 3826, 3829, 3832, 3835, 3838, 3841, 3844, 3847, 3850, 3853, + 3856, 3859, 0, 3862, 3865, 3868, 3871, 3874, 3877, 3880, 0, 3883, 3886, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+10Cxx (6144) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3889, 3892, 3895, 3898, 3901, 3904, 3907, 3910, 3913, 3916, 3919, 3922, 3925, 3928, 3931, 3934, + 3937, 3940, 3943, 3946, 3949, 3952, 3955, 3958, 3961, 3964, 3967, 3970, 3973, 3976, 3979, 3982, + 3985, 3988, 3991, 3994, 3997, 4000, 4003, 4006, 4009, 4012, 4015, 4018, 4021, 4024, 4027, 4030, + 4033, 4036, 4039, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3889, 3892, 3895, 3898, 3901, 3904, 3907, 3910, 3913, 3916, 3919, 3922, 3925, 3928, 3931, 3934, + 3937, 3940, 3943, 3946, 3949, 3952, 3955, 3958, 3961, 3964, 3967, 3970, 3973, 3976, 3979, 3982, + 3985, 3988, 3991, 3994, 3997, 4000, 4003, 4006, 4009, 4012, 4015, 4018, 4021, 4024, 4027, 4030, + 4033, 4036, 4039, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+10Dxx (6400) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4042, 4045, 4048, 4051, 4054, 4057, 4060, 4063, 4066, 4069, 4072, 4075, 4078, 4081, 4084, 4087, + 4090, 4093, 4096, 4099, 4102, 4105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4042, 4045, 4048, 4051, 4054, 4057, 4060, 4063, 4066, 4069, 4072, 4075, 4078, 4081, 4084, 4087, + 4090, 4093, 4096, 4099, 4102, 4105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+118xx (6656) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4108, 4111, 4114, 4117, 4120, 4123, 4126, 4129, 4132, 4135, 4138, 4141, 4144, 4147, 4150, 4153, + 4156, 4159, 4162, 4165, 4168, 4171, 4174, 4177, 4180, 4183, 4186, 4189, 4192, 4195, 4198, 4201, + 4108, 4111, 4114, 4117, 4120, 4123, 4126, 4129, 4132, 4135, 4138, 4141, 4144, 4147, 4150, 4153, + 4156, 4159, 4162, 4165, 4168, 4171, 4174, 4177, 4180, 4183, 4186, 4189, 4192, 4195, 4198, 4201, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+16Exx (6912) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4204, 4207, 4210, 4213, 4216, 4219, 4222, 4225, 4228, 4231, 4234, 4237, 4240, 4243, 4246, 4249, + 4252, 4255, 4258, 4261, 4264, 4267, 4270, 4273, 4276, 4279, 4282, 4285, 4288, 4291, 4294, 4297, + 4204, 4207, 4210, 4213, 4216, 4219, 4222, 4225, 4228, 4231, 4234, 4237, 4240, 4243, 4246, 4249, + 4252, 4255, 4258, 4261, 4264, 4267, 4270, 4273, 4276, 4279, 4282, 4285, 4288, 4291, 4294, 4297, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4300, 4303, 4306, 4309, 4312, 4315, 4318, 4321, 4324, 4327, 4330, 4333, 4336, 4339, 4342, 4345, + 4348, 4351, 4354, 4357, 4360, 4363, 4366, 4369, 4372, 0, 0, 4300, 4303, 4306, 4309, 4312, + 4315, 4318, 4321, 4324, 4327, 4330, 4333, 4336, 4339, 4342, 4345, 4348, 4351, 4354, 4357, 4360, + 4363, 4366, 4369, 4372, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + // For u+1E9xx (7168) + 4375, 4378, 4381, 4384, 4387, 4390, 4393, 4396, 4399, 4402, 4405, 4408, 4411, 4414, 4417, 4420, + 4423, 4426, 4429, 4432, 4435, 4438, 4441, 4444, 4447, 4450, 4453, 4456, 4459, 4462, 4465, 4468, + 4471, 4474, 4375, 4378, 4381, 4384, 4387, 4390, 4393, 4396, 4399, 4402, 4405, 4408, 4411, 4414, + 4417, 4420, 4423, 4426, 4429, 4432, 4435, 4438, 4441, 4444, 4447, 4450, 4453, 4456, 4459, 4462, + 4465, 4468, 4471, 4474, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +template +const T2 unicode_casefolding::rev_segmenttable[] = +{ + 256, 768, 1280, 1024, 2048, 2560, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3072, 0, 0, 3328, 0, 0, 0, 0, 0, 0, 0, 0, 2304, 4352, 3840, 1792, + 0, 512, 0, 0, 4096, 0, 0, 0, 0, 0, 0, 0, 1536, 2816, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3584, 4608, 0, 0, 0, 4864, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5120, 0, 0, 0, 5376, + 0, 0, 0, 0, 5632, 5888, 0, 0, 0, 0, 0, 0, 6144, 6400, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 6656, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6912, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 7168 +}; + +template +const T1 unicode_casefolding::rev_charsettable[] = +{ + eos, // 0 + 0x0061, 0x0041, eos, + 0x0062, 0x0042, eos, + 0x0063, 0x0043, eos, + 0x0064, 0x0044, eos, // 10 + 0x0065, 0x0045, eos, + 0x0066, 0x0046, eos, + 0x0067, 0x0047, eos, + 0x0068, 0x0048, eos, // 22 + 0x0069, 0x0049, eos, + 0x006A, 0x004A, eos, + 0x006B, 0x004B, 0x212A, eos, // 31 + 0x006C, 0x004C, eos, + 0x006D, 0x004D, eos, + 0x006E, 0x004E, eos, // 41 + 0x006F, 0x004F, eos, + 0x0070, 0x0050, eos, + 0x0071, 0x0051, eos, // 50 + 0x0072, 0x0052, eos, + 0x0073, 0x0053, 0x017F, eos, + 0x0074, 0x0054, eos, // 60 + 0x0075, 0x0055, eos, + 0x0076, 0x0056, eos, + 0x0077, 0x0057, eos, + 0x0078, 0x0058, eos, // 72 + 0x0079, 0x0059, eos, + 0x007A, 0x005A, eos, + 0x03BC, 0x00B5, 0x039C, eos, // 81 + 0x00E0, 0x00C0, eos, + 0x00E1, 0x00C1, eos, + 0x00E2, 0x00C2, eos, // 91 + 0x00E3, 0x00C3, eos, + 0x00E4, 0x00C4, eos, + 0x00E5, 0x00C5, 0x212B, eos, // 100 + 0x00E6, 0x00C6, eos, + 0x00E7, 0x00C7, eos, + 0x00E8, 0x00C8, eos, // 110 + 0x00E9, 0x00C9, eos, + 0x00EA, 0x00CA, eos, + 0x00EB, 0x00CB, eos, + 0x00EC, 0x00CC, eos, // 122 + 0x00ED, 0x00CD, eos, + 0x00EE, 0x00CE, eos, + 0x00EF, 0x00CF, eos, // 131 + 0x00F0, 0x00D0, eos, + 0x00F1, 0x00D1, eos, + 0x00F2, 0x00D2, eos, // 140 + 0x00F3, 0x00D3, eos, + 0x00F4, 0x00D4, eos, + 0x00F5, 0x00D5, eos, + 0x00F6, 0x00D6, eos, // 152 + 0x00F8, 0x00D8, eos, + 0x00F9, 0x00D9, eos, + 0x00FA, 0x00DA, eos, // 161 + 0x00FB, 0x00DB, eos, + 0x00FC, 0x00DC, eos, + 0x00FD, 0x00DD, eos, // 170 + 0x00FE, 0x00DE, eos, + 0x0101, 0x0100, eos, + 0x0103, 0x0102, eos, + 0x0105, 0x0104, eos, // 182 + 0x0107, 0x0106, eos, + 0x0109, 0x0108, eos, + 0x010B, 0x010A, eos, // 191 + 0x010D, 0x010C, eos, + 0x010F, 0x010E, eos, + 0x0111, 0x0110, eos, // 200 + 0x0113, 0x0112, eos, + 0x0115, 0x0114, eos, + 0x0117, 0x0116, eos, + 0x0119, 0x0118, eos, // 212 + 0x011B, 0x011A, eos, + 0x011D, 0x011C, eos, + 0x011F, 0x011E, eos, // 221 + 0x0121, 0x0120, eos, + 0x0123, 0x0122, eos, + 0x0125, 0x0124, eos, // 230 + 0x0127, 0x0126, eos, + 0x0129, 0x0128, eos, + 0x012B, 0x012A, eos, + 0x012D, 0x012C, eos, // 242 + 0x012F, 0x012E, eos, + 0x0133, 0x0132, eos, + 0x0135, 0x0134, eos, // 251 + 0x0137, 0x0136, eos, + 0x013A, 0x0139, eos, + 0x013C, 0x013B, eos, // 260 + 0x013E, 0x013D, eos, + 0x0140, 0x013F, eos, + 0x0142, 0x0141, eos, + 0x0144, 0x0143, eos, // 272 + 0x0146, 0x0145, eos, + 0x0148, 0x0147, eos, + 0x014B, 0x014A, eos, // 281 + 0x014D, 0x014C, eos, + 0x014F, 0x014E, eos, + 0x0151, 0x0150, eos, // 290 + 0x0153, 0x0152, eos, + 0x0155, 0x0154, eos, + 0x0157, 0x0156, eos, + 0x0159, 0x0158, eos, // 302 + 0x015B, 0x015A, eos, + 0x015D, 0x015C, eos, + 0x015F, 0x015E, eos, // 311 + 0x0161, 0x0160, eos, + 0x0163, 0x0162, eos, + 0x0165, 0x0164, eos, // 320 + 0x0167, 0x0166, eos, + 0x0169, 0x0168, eos, + 0x016B, 0x016A, eos, + 0x016D, 0x016C, eos, // 332 + 0x016F, 0x016E, eos, + 0x0171, 0x0170, eos, + 0x0173, 0x0172, eos, // 341 + 0x0175, 0x0174, eos, + 0x0177, 0x0176, eos, + 0x00FF, 0x0178, eos, // 350 + 0x017A, 0x0179, eos, + 0x017C, 0x017B, eos, + 0x017E, 0x017D, eos, + 0x0253, 0x0181, eos, // 362 + 0x0183, 0x0182, eos, + 0x0185, 0x0184, eos, + 0x0254, 0x0186, eos, // 371 + 0x0188, 0x0187, eos, + 0x0256, 0x0189, eos, + 0x0257, 0x018A, eos, // 380 + 0x018C, 0x018B, eos, + 0x01DD, 0x018E, eos, + 0x0259, 0x018F, eos, + 0x025B, 0x0190, eos, // 392 + 0x0192, 0x0191, eos, + 0x0260, 0x0193, eos, + 0x0263, 0x0194, eos, // 401 + 0x0269, 0x0196, eos, + 0x0268, 0x0197, eos, + 0x0199, 0x0198, eos, // 410 + 0x026F, 0x019C, eos, + 0x0272, 0x019D, eos, + 0x0275, 0x019F, eos, + 0x01A1, 0x01A0, eos, // 422 + 0x01A3, 0x01A2, eos, + 0x01A5, 0x01A4, eos, + 0x0280, 0x01A6, eos, // 431 + 0x01A8, 0x01A7, eos, + 0x0283, 0x01A9, eos, + 0x01AD, 0x01AC, eos, // 440 + 0x0288, 0x01AE, eos, + 0x01B0, 0x01AF, eos, + 0x028A, 0x01B1, eos, + 0x028B, 0x01B2, eos, // 452 + 0x01B4, 0x01B3, eos, + 0x01B6, 0x01B5, eos, + 0x0292, 0x01B7, eos, // 461 + 0x01B9, 0x01B8, eos, + 0x01BD, 0x01BC, eos, + 0x01C6, 0x01C4, 0x01C5, eos, // 470 + 0x01C9, 0x01C7, 0x01C8, eos, + 0x01CC, 0x01CA, 0x01CB, eos, + 0x01CE, 0x01CD, eos, // 482 + 0x01D0, 0x01CF, eos, + 0x01D2, 0x01D1, eos, + 0x01D4, 0x01D3, eos, // 491 + 0x01D6, 0x01D5, eos, + 0x01D8, 0x01D7, eos, + 0x01DA, 0x01D9, eos, // 500 + 0x01DC, 0x01DB, eos, + 0x01DF, 0x01DE, eos, + 0x01E1, 0x01E0, eos, + 0x01E3, 0x01E2, eos, // 512 + 0x01E5, 0x01E4, eos, + 0x01E7, 0x01E6, eos, + 0x01E9, 0x01E8, eos, // 521 + 0x01EB, 0x01EA, eos, + 0x01ED, 0x01EC, eos, + 0x01EF, 0x01EE, eos, // 530 + 0x01F3, 0x01F1, 0x01F2, eos, + 0x01F5, 0x01F4, eos, + 0x0195, 0x01F6, eos, // 540 + 0x01BF, 0x01F7, eos, + 0x01F9, 0x01F8, eos, + 0x01FB, 0x01FA, eos, + 0x01FD, 0x01FC, eos, // 552 + 0x01FF, 0x01FE, eos, + 0x0201, 0x0200, eos, + 0x0203, 0x0202, eos, // 561 + 0x0205, 0x0204, eos, + 0x0207, 0x0206, eos, + 0x0209, 0x0208, eos, // 570 + 0x020B, 0x020A, eos, + 0x020D, 0x020C, eos, + 0x020F, 0x020E, eos, + 0x0211, 0x0210, eos, // 582 + 0x0213, 0x0212, eos, + 0x0215, 0x0214, eos, + 0x0217, 0x0216, eos, // 591 + 0x0219, 0x0218, eos, + 0x021B, 0x021A, eos, + 0x021D, 0x021C, eos, // 600 + 0x021F, 0x021E, eos, + 0x019E, 0x0220, eos, + 0x0223, 0x0222, eos, + 0x0225, 0x0224, eos, // 612 + 0x0227, 0x0226, eos, + 0x0229, 0x0228, eos, + 0x022B, 0x022A, eos, // 621 + 0x022D, 0x022C, eos, + 0x022F, 0x022E, eos, + 0x0231, 0x0230, eos, // 630 + 0x0233, 0x0232, eos, + 0x2C65, 0x023A, eos, + 0x023C, 0x023B, eos, + 0x019A, 0x023D, eos, // 642 + 0x2C66, 0x023E, eos, + 0x0242, 0x0241, eos, + 0x0180, 0x0243, eos, // 651 + 0x0289, 0x0244, eos, + 0x028C, 0x0245, eos, + 0x0247, 0x0246, eos, // 660 + 0x0249, 0x0248, eos, + 0x024B, 0x024A, eos, + 0x024D, 0x024C, eos, + 0x024F, 0x024E, eos, // 672 + 0x03B9, 0x0345, 0x0399, 0x1FBE, eos, + 0x0371, 0x0370, eos, // 680 + 0x0373, 0x0372, eos, + 0x0377, 0x0376, eos, + 0x03F3, 0x037F, eos, + 0x03AC, 0x0386, eos, // 692 + 0x03AD, 0x0388, eos, + 0x03AE, 0x0389, eos, + 0x03AF, 0x038A, eos, // 701 + 0x03CC, 0x038C, eos, + 0x03CD, 0x038E, eos, + 0x03CE, 0x038F, eos, // 710 + 0x03B1, 0x0391, eos, + 0x03B2, 0x0392, 0x03D0, eos, + 0x03B3, 0x0393, eos, // 720 + 0x03B4, 0x0394, eos, + 0x03B5, 0x0395, 0x03F5, eos, + 0x03B6, 0x0396, eos, // 730 + 0x03B7, 0x0397, eos, + 0x03B8, 0x0398, 0x03D1, 0x03F4, eos, + 0x03BA, 0x039A, 0x03F0, eos, // 741 + 0x03BB, 0x039B, eos, + 0x03BD, 0x039D, eos, + 0x03BE, 0x039E, eos, // 751 + 0x03BF, 0x039F, eos, + 0x03C0, 0x03A0, 0x03D6, eos, + 0x03C1, 0x03A1, 0x03F1, eos, // 761 + 0x03C3, 0x03A3, 0x03C2, eos, + 0x03C4, 0x03A4, eos, + 0x03C5, 0x03A5, eos, // 772 + 0x03C6, 0x03A6, 0x03D5, eos, + 0x03C7, 0x03A7, eos, + 0x03C8, 0x03A8, eos, // 782 + 0x03C9, 0x03A9, 0x2126, eos, + 0x03CA, 0x03AA, eos, + 0x03CB, 0x03AB, eos, // 792 + 0x03D7, 0x03CF, eos, + 0x03D9, 0x03D8, eos, + 0x03DB, 0x03DA, eos, // 801 + 0x03DD, 0x03DC, eos, + 0x03DF, 0x03DE, eos, + 0x03E1, 0x03E0, eos, // 810 + 0x03E3, 0x03E2, eos, + 0x03E5, 0x03E4, eos, + 0x03E7, 0x03E6, eos, + 0x03E9, 0x03E8, eos, // 822 + 0x03EB, 0x03EA, eos, + 0x03ED, 0x03EC, eos, + 0x03EF, 0x03EE, eos, // 831 + 0x03F8, 0x03F7, eos, + 0x03F2, 0x03F9, eos, + 0x03FB, 0x03FA, eos, // 840 + 0x037B, 0x03FD, eos, + 0x037C, 0x03FE, eos, + 0x037D, 0x03FF, eos, + 0x0450, 0x0400, eos, // 852 + 0x0451, 0x0401, eos, + 0x0452, 0x0402, eos, + 0x0453, 0x0403, eos, // 861 + 0x0454, 0x0404, eos, + 0x0455, 0x0405, eos, + 0x0456, 0x0406, eos, // 870 + 0x0457, 0x0407, eos, + 0x0458, 0x0408, eos, + 0x0459, 0x0409, eos, + 0x045A, 0x040A, eos, // 882 + 0x045B, 0x040B, eos, + 0x045C, 0x040C, eos, + 0x045D, 0x040D, eos, // 891 + 0x045E, 0x040E, eos, + 0x045F, 0x040F, eos, + 0x0430, 0x0410, eos, // 900 + 0x0431, 0x0411, eos, + 0x0432, 0x0412, 0x1C80, eos, + 0x0433, 0x0413, eos, // 910 + 0x0434, 0x0414, 0x1C81, eos, + 0x0435, 0x0415, eos, + 0x0436, 0x0416, eos, // 920 + 0x0437, 0x0417, eos, + 0x0438, 0x0418, eos, + 0x0439, 0x0419, eos, + 0x043A, 0x041A, eos, // 932 + 0x043B, 0x041B, eos, + 0x043C, 0x041C, eos, + 0x043D, 0x041D, eos, // 941 + 0x043E, 0x041E, 0x1C82, eos, + 0x043F, 0x041F, eos, + 0x0440, 0x0420, eos, // 951 + 0x0441, 0x0421, 0x1C83, eos, + 0x0442, 0x0422, 0x1C84, 0x1C85, eos, + 0x0443, 0x0423, eos, // 963 + 0x0444, 0x0424, eos, + 0x0445, 0x0425, eos, + 0x0446, 0x0426, eos, // 972 + 0x0447, 0x0427, eos, + 0x0448, 0x0428, eos, + 0x0449, 0x0429, eos, // 981 + 0x044A, 0x042A, 0x1C86, eos, + 0x044B, 0x042B, eos, + 0x044C, 0x042C, eos, // 991 + 0x044D, 0x042D, eos, + 0x044E, 0x042E, eos, + 0x044F, 0x042F, eos, // 1000 + 0x0461, 0x0460, eos, + 0x0463, 0x0462, 0x1C87, eos, + 0x0465, 0x0464, eos, // 1010 + 0x0467, 0x0466, eos, + 0x0469, 0x0468, eos, + 0x046B, 0x046A, eos, + 0x046D, 0x046C, eos, // 1022 + 0x046F, 0x046E, eos, + 0x0471, 0x0470, eos, + 0x0473, 0x0472, eos, // 1031 + 0x0475, 0x0474, eos, + 0x0477, 0x0476, eos, + 0x0479, 0x0478, eos, // 1040 + 0x047B, 0x047A, eos, + 0x047D, 0x047C, eos, + 0x047F, 0x047E, eos, + 0x0481, 0x0480, eos, // 1052 + 0x048B, 0x048A, eos, + 0x048D, 0x048C, eos, + 0x048F, 0x048E, eos, // 1061 + 0x0491, 0x0490, eos, + 0x0493, 0x0492, eos, + 0x0495, 0x0494, eos, // 1070 + 0x0497, 0x0496, eos, + 0x0499, 0x0498, eos, + 0x049B, 0x049A, eos, + 0x049D, 0x049C, eos, // 1082 + 0x049F, 0x049E, eos, + 0x04A1, 0x04A0, eos, + 0x04A3, 0x04A2, eos, // 1091 + 0x04A5, 0x04A4, eos, + 0x04A7, 0x04A6, eos, + 0x04A9, 0x04A8, eos, // 1100 + 0x04AB, 0x04AA, eos, + 0x04AD, 0x04AC, eos, + 0x04AF, 0x04AE, eos, + 0x04B1, 0x04B0, eos, // 1112 + 0x04B3, 0x04B2, eos, + 0x04B5, 0x04B4, eos, + 0x04B7, 0x04B6, eos, // 1121 + 0x04B9, 0x04B8, eos, + 0x04BB, 0x04BA, eos, + 0x04BD, 0x04BC, eos, // 1130 + 0x04BF, 0x04BE, eos, + 0x04CF, 0x04C0, eos, + 0x04C2, 0x04C1, eos, + 0x04C4, 0x04C3, eos, // 1142 + 0x04C6, 0x04C5, eos, + 0x04C8, 0x04C7, eos, + 0x04CA, 0x04C9, eos, // 1151 + 0x04CC, 0x04CB, eos, + 0x04CE, 0x04CD, eos, + 0x04D1, 0x04D0, eos, // 1160 + 0x04D3, 0x04D2, eos, + 0x04D5, 0x04D4, eos, + 0x04D7, 0x04D6, eos, + 0x04D9, 0x04D8, eos, // 1172 + 0x04DB, 0x04DA, eos, + 0x04DD, 0x04DC, eos, + 0x04DF, 0x04DE, eos, // 1181 + 0x04E1, 0x04E0, eos, + 0x04E3, 0x04E2, eos, + 0x04E5, 0x04E4, eos, // 1190 + 0x04E7, 0x04E6, eos, + 0x04E9, 0x04E8, eos, + 0x04EB, 0x04EA, eos, + 0x04ED, 0x04EC, eos, // 1202 + 0x04EF, 0x04EE, eos, + 0x04F1, 0x04F0, eos, + 0x04F3, 0x04F2, eos, // 1211 + 0x04F5, 0x04F4, eos, + 0x04F7, 0x04F6, eos, + 0x04F9, 0x04F8, eos, // 1220 + 0x04FB, 0x04FA, eos, + 0x04FD, 0x04FC, eos, + 0x04FF, 0x04FE, eos, + 0x0501, 0x0500, eos, // 1232 + 0x0503, 0x0502, eos, + 0x0505, 0x0504, eos, + 0x0507, 0x0506, eos, // 1241 + 0x0509, 0x0508, eos, + 0x050B, 0x050A, eos, + 0x050D, 0x050C, eos, // 1250 + 0x050F, 0x050E, eos, + 0x0511, 0x0510, eos, + 0x0513, 0x0512, eos, + 0x0515, 0x0514, eos, // 1262 + 0x0517, 0x0516, eos, + 0x0519, 0x0518, eos, + 0x051B, 0x051A, eos, // 1271 + 0x051D, 0x051C, eos, + 0x051F, 0x051E, eos, + 0x0521, 0x0520, eos, // 1280 + 0x0523, 0x0522, eos, + 0x0525, 0x0524, eos, + 0x0527, 0x0526, eos, + 0x0529, 0x0528, eos, // 1292 + 0x052B, 0x052A, eos, + 0x052D, 0x052C, eos, + 0x052F, 0x052E, eos, // 1301 + 0x0561, 0x0531, eos, + 0x0562, 0x0532, eos, + 0x0563, 0x0533, eos, // 1310 + 0x0564, 0x0534, eos, + 0x0565, 0x0535, eos, + 0x0566, 0x0536, eos, + 0x0567, 0x0537, eos, // 1322 + 0x0568, 0x0538, eos, + 0x0569, 0x0539, eos, + 0x056A, 0x053A, eos, // 1331 + 0x056B, 0x053B, eos, + 0x056C, 0x053C, eos, + 0x056D, 0x053D, eos, // 1340 + 0x056E, 0x053E, eos, + 0x056F, 0x053F, eos, + 0x0570, 0x0540, eos, + 0x0571, 0x0541, eos, // 1352 + 0x0572, 0x0542, eos, + 0x0573, 0x0543, eos, + 0x0574, 0x0544, eos, // 1361 + 0x0575, 0x0545, eos, + 0x0576, 0x0546, eos, + 0x0577, 0x0547, eos, // 1370 + 0x0578, 0x0548, eos, + 0x0579, 0x0549, eos, + 0x057A, 0x054A, eos, + 0x057B, 0x054B, eos, // 1382 + 0x057C, 0x054C, eos, + 0x057D, 0x054D, eos, + 0x057E, 0x054E, eos, // 1391 + 0x057F, 0x054F, eos, + 0x0580, 0x0550, eos, + 0x0581, 0x0551, eos, // 1400 + 0x0582, 0x0552, eos, + 0x0583, 0x0553, eos, + 0x0584, 0x0554, eos, + 0x0585, 0x0555, eos, // 1412 + 0x0586, 0x0556, eos, + 0x2D00, 0x10A0, eos, + 0x2D01, 0x10A1, eos, // 1421 + 0x2D02, 0x10A2, eos, + 0x2D03, 0x10A3, eos, + 0x2D04, 0x10A4, eos, // 1430 + 0x2D05, 0x10A5, eos, + 0x2D06, 0x10A6, eos, + 0x2D07, 0x10A7, eos, + 0x2D08, 0x10A8, eos, // 1442 + 0x2D09, 0x10A9, eos, + 0x2D0A, 0x10AA, eos, + 0x2D0B, 0x10AB, eos, // 1451 + 0x2D0C, 0x10AC, eos, + 0x2D0D, 0x10AD, eos, + 0x2D0E, 0x10AE, eos, // 1460 + 0x2D0F, 0x10AF, eos, + 0x2D10, 0x10B0, eos, + 0x2D11, 0x10B1, eos, + 0x2D12, 0x10B2, eos, // 1472 + 0x2D13, 0x10B3, eos, + 0x2D14, 0x10B4, eos, + 0x2D15, 0x10B5, eos, // 1481 + 0x2D16, 0x10B6, eos, + 0x2D17, 0x10B7, eos, + 0x2D18, 0x10B8, eos, // 1490 + 0x2D19, 0x10B9, eos, + 0x2D1A, 0x10BA, eos, + 0x2D1B, 0x10BB, eos, + 0x2D1C, 0x10BC, eos, // 1502 + 0x2D1D, 0x10BD, eos, + 0x2D1E, 0x10BE, eos, + 0x2D1F, 0x10BF, eos, // 1511 + 0x2D20, 0x10C0, eos, + 0x2D21, 0x10C1, eos, + 0x2D22, 0x10C2, eos, // 1520 + 0x2D23, 0x10C3, eos, + 0x2D24, 0x10C4, eos, + 0x2D25, 0x10C5, eos, + 0x2D27, 0x10C7, eos, // 1532 + 0x2D2D, 0x10CD, eos, + 0x13F0, 0x13F8, eos, + 0x13F1, 0x13F9, eos, // 1541 + 0x13F2, 0x13FA, eos, + 0x13F3, 0x13FB, eos, + 0x13F4, 0x13FC, eos, // 1550 + 0x13F5, 0x13FD, eos, + 0xA64B, 0x1C88, 0xA64A, eos, + 0x1C8A, 0x1C89, eos, // 1560 + 0x10D0, 0x1C90, eos, + 0x10D1, 0x1C91, eos, + 0x10D2, 0x1C92, eos, + 0x10D3, 0x1C93, eos, // 1572 + 0x10D4, 0x1C94, eos, + 0x10D5, 0x1C95, eos, + 0x10D6, 0x1C96, eos, // 1581 + 0x10D7, 0x1C97, eos, + 0x10D8, 0x1C98, eos, + 0x10D9, 0x1C99, eos, // 1590 + 0x10DA, 0x1C9A, eos, + 0x10DB, 0x1C9B, eos, + 0x10DC, 0x1C9C, eos, + 0x10DD, 0x1C9D, eos, // 1602 + 0x10DE, 0x1C9E, eos, + 0x10DF, 0x1C9F, eos, + 0x10E0, 0x1CA0, eos, // 1611 + 0x10E1, 0x1CA1, eos, + 0x10E2, 0x1CA2, eos, + 0x10E3, 0x1CA3, eos, // 1620 + 0x10E4, 0x1CA4, eos, + 0x10E5, 0x1CA5, eos, + 0x10E6, 0x1CA6, eos, + 0x10E7, 0x1CA7, eos, // 1632 + 0x10E8, 0x1CA8, eos, + 0x10E9, 0x1CA9, eos, + 0x10EA, 0x1CAA, eos, // 1641 + 0x10EB, 0x1CAB, eos, + 0x10EC, 0x1CAC, eos, + 0x10ED, 0x1CAD, eos, // 1650 + 0x10EE, 0x1CAE, eos, + 0x10EF, 0x1CAF, eos, + 0x10F0, 0x1CB0, eos, + 0x10F1, 0x1CB1, eos, // 1662 + 0x10F2, 0x1CB2, eos, + 0x10F3, 0x1CB3, eos, + 0x10F4, 0x1CB4, eos, // 1671 + 0x10F5, 0x1CB5, eos, + 0x10F6, 0x1CB6, eos, + 0x10F7, 0x1CB7, eos, // 1680 + 0x10F8, 0x1CB8, eos, + 0x10F9, 0x1CB9, eos, + 0x10FA, 0x1CBA, eos, + 0x10FD, 0x1CBD, eos, // 1692 + 0x10FE, 0x1CBE, eos, + 0x10FF, 0x1CBF, eos, + 0x1E01, 0x1E00, eos, // 1701 + 0x1E03, 0x1E02, eos, + 0x1E05, 0x1E04, eos, + 0x1E07, 0x1E06, eos, // 1710 + 0x1E09, 0x1E08, eos, + 0x1E0B, 0x1E0A, eos, + 0x1E0D, 0x1E0C, eos, + 0x1E0F, 0x1E0E, eos, // 1722 + 0x1E11, 0x1E10, eos, + 0x1E13, 0x1E12, eos, + 0x1E15, 0x1E14, eos, // 1731 + 0x1E17, 0x1E16, eos, + 0x1E19, 0x1E18, eos, + 0x1E1B, 0x1E1A, eos, // 1740 + 0x1E1D, 0x1E1C, eos, + 0x1E1F, 0x1E1E, eos, + 0x1E21, 0x1E20, eos, + 0x1E23, 0x1E22, eos, // 1752 + 0x1E25, 0x1E24, eos, + 0x1E27, 0x1E26, eos, + 0x1E29, 0x1E28, eos, // 1761 + 0x1E2B, 0x1E2A, eos, + 0x1E2D, 0x1E2C, eos, + 0x1E2F, 0x1E2E, eos, // 1770 + 0x1E31, 0x1E30, eos, + 0x1E33, 0x1E32, eos, + 0x1E35, 0x1E34, eos, + 0x1E37, 0x1E36, eos, // 1782 + 0x1E39, 0x1E38, eos, + 0x1E3B, 0x1E3A, eos, + 0x1E3D, 0x1E3C, eos, // 1791 + 0x1E3F, 0x1E3E, eos, + 0x1E41, 0x1E40, eos, + 0x1E43, 0x1E42, eos, // 1800 + 0x1E45, 0x1E44, eos, + 0x1E47, 0x1E46, eos, + 0x1E49, 0x1E48, eos, + 0x1E4B, 0x1E4A, eos, // 1812 + 0x1E4D, 0x1E4C, eos, + 0x1E4F, 0x1E4E, eos, + 0x1E51, 0x1E50, eos, // 1821 + 0x1E53, 0x1E52, eos, + 0x1E55, 0x1E54, eos, + 0x1E57, 0x1E56, eos, // 1830 + 0x1E59, 0x1E58, eos, + 0x1E5B, 0x1E5A, eos, + 0x1E5D, 0x1E5C, eos, + 0x1E5F, 0x1E5E, eos, // 1842 + 0x1E61, 0x1E60, 0x1E9B, eos, + 0x1E63, 0x1E62, eos, + 0x1E65, 0x1E64, eos, // 1852 + 0x1E67, 0x1E66, eos, + 0x1E69, 0x1E68, eos, + 0x1E6B, 0x1E6A, eos, // 1861 + 0x1E6D, 0x1E6C, eos, + 0x1E6F, 0x1E6E, eos, + 0x1E71, 0x1E70, eos, // 1870 + 0x1E73, 0x1E72, eos, + 0x1E75, 0x1E74, eos, + 0x1E77, 0x1E76, eos, + 0x1E79, 0x1E78, eos, // 1882 + 0x1E7B, 0x1E7A, eos, + 0x1E7D, 0x1E7C, eos, + 0x1E7F, 0x1E7E, eos, // 1891 + 0x1E81, 0x1E80, eos, + 0x1E83, 0x1E82, eos, + 0x1E85, 0x1E84, eos, // 1900 + 0x1E87, 0x1E86, eos, + 0x1E89, 0x1E88, eos, + 0x1E8B, 0x1E8A, eos, + 0x1E8D, 0x1E8C, eos, // 1912 + 0x1E8F, 0x1E8E, eos, + 0x1E91, 0x1E90, eos, + 0x1E93, 0x1E92, eos, // 1921 + 0x1E95, 0x1E94, eos, + 0x00DF, 0x1E9E, eos, + 0x1EA1, 0x1EA0, eos, // 1930 + 0x1EA3, 0x1EA2, eos, + 0x1EA5, 0x1EA4, eos, + 0x1EA7, 0x1EA6, eos, + 0x1EA9, 0x1EA8, eos, // 1942 + 0x1EAB, 0x1EAA, eos, + 0x1EAD, 0x1EAC, eos, + 0x1EAF, 0x1EAE, eos, // 1951 + 0x1EB1, 0x1EB0, eos, + 0x1EB3, 0x1EB2, eos, + 0x1EB5, 0x1EB4, eos, // 1960 + 0x1EB7, 0x1EB6, eos, + 0x1EB9, 0x1EB8, eos, + 0x1EBB, 0x1EBA, eos, + 0x1EBD, 0x1EBC, eos, // 1972 + 0x1EBF, 0x1EBE, eos, + 0x1EC1, 0x1EC0, eos, + 0x1EC3, 0x1EC2, eos, // 1981 + 0x1EC5, 0x1EC4, eos, + 0x1EC7, 0x1EC6, eos, + 0x1EC9, 0x1EC8, eos, // 1990 + 0x1ECB, 0x1ECA, eos, + 0x1ECD, 0x1ECC, eos, + 0x1ECF, 0x1ECE, eos, + 0x1ED1, 0x1ED0, eos, // 2002 + 0x1ED3, 0x1ED2, eos, + 0x1ED5, 0x1ED4, eos, + 0x1ED7, 0x1ED6, eos, // 2011 + 0x1ED9, 0x1ED8, eos, + 0x1EDB, 0x1EDA, eos, + 0x1EDD, 0x1EDC, eos, // 2020 + 0x1EDF, 0x1EDE, eos, + 0x1EE1, 0x1EE0, eos, + 0x1EE3, 0x1EE2, eos, + 0x1EE5, 0x1EE4, eos, // 2032 + 0x1EE7, 0x1EE6, eos, + 0x1EE9, 0x1EE8, eos, + 0x1EEB, 0x1EEA, eos, // 2041 + 0x1EED, 0x1EEC, eos, + 0x1EEF, 0x1EEE, eos, + 0x1EF1, 0x1EF0, eos, // 2050 + 0x1EF3, 0x1EF2, eos, + 0x1EF5, 0x1EF4, eos, + 0x1EF7, 0x1EF6, eos, + 0x1EF9, 0x1EF8, eos, // 2062 + 0x1EFB, 0x1EFA, eos, + 0x1EFD, 0x1EFC, eos, + 0x1EFF, 0x1EFE, eos, // 2071 + 0x1F00, 0x1F08, eos, + 0x1F01, 0x1F09, eos, + 0x1F02, 0x1F0A, eos, // 2080 + 0x1F03, 0x1F0B, eos, + 0x1F04, 0x1F0C, eos, + 0x1F05, 0x1F0D, eos, + 0x1F06, 0x1F0E, eos, // 2092 + 0x1F07, 0x1F0F, eos, + 0x1F10, 0x1F18, eos, + 0x1F11, 0x1F19, eos, // 2101 + 0x1F12, 0x1F1A, eos, + 0x1F13, 0x1F1B, eos, + 0x1F14, 0x1F1C, eos, // 2110 + 0x1F15, 0x1F1D, eos, + 0x1F20, 0x1F28, eos, + 0x1F21, 0x1F29, eos, + 0x1F22, 0x1F2A, eos, // 2122 + 0x1F23, 0x1F2B, eos, + 0x1F24, 0x1F2C, eos, + 0x1F25, 0x1F2D, eos, // 2131 + 0x1F26, 0x1F2E, eos, + 0x1F27, 0x1F2F, eos, + 0x1F30, 0x1F38, eos, // 2140 + 0x1F31, 0x1F39, eos, + 0x1F32, 0x1F3A, eos, + 0x1F33, 0x1F3B, eos, + 0x1F34, 0x1F3C, eos, // 2152 + 0x1F35, 0x1F3D, eos, + 0x1F36, 0x1F3E, eos, + 0x1F37, 0x1F3F, eos, // 2161 + 0x1F40, 0x1F48, eos, + 0x1F41, 0x1F49, eos, + 0x1F42, 0x1F4A, eos, // 2170 + 0x1F43, 0x1F4B, eos, + 0x1F44, 0x1F4C, eos, + 0x1F45, 0x1F4D, eos, + 0x1F51, 0x1F59, eos, // 2182 + 0x1F53, 0x1F5B, eos, + 0x1F55, 0x1F5D, eos, + 0x1F57, 0x1F5F, eos, // 2191 + 0x1F60, 0x1F68, eos, + 0x1F61, 0x1F69, eos, + 0x1F62, 0x1F6A, eos, // 2200 + 0x1F63, 0x1F6B, eos, + 0x1F64, 0x1F6C, eos, + 0x1F65, 0x1F6D, eos, + 0x1F66, 0x1F6E, eos, // 2212 + 0x1F67, 0x1F6F, eos, + 0x1F80, 0x1F88, eos, + 0x1F81, 0x1F89, eos, // 2221 + 0x1F82, 0x1F8A, eos, + 0x1F83, 0x1F8B, eos, + 0x1F84, 0x1F8C, eos, // 2230 + 0x1F85, 0x1F8D, eos, + 0x1F86, 0x1F8E, eos, + 0x1F87, 0x1F8F, eos, + 0x1F90, 0x1F98, eos, // 2242 + 0x1F91, 0x1F99, eos, + 0x1F92, 0x1F9A, eos, + 0x1F93, 0x1F9B, eos, // 2251 + 0x1F94, 0x1F9C, eos, + 0x1F95, 0x1F9D, eos, + 0x1F96, 0x1F9E, eos, // 2260 + 0x1F97, 0x1F9F, eos, + 0x1FA0, 0x1FA8, eos, + 0x1FA1, 0x1FA9, eos, + 0x1FA2, 0x1FAA, eos, // 2272 + 0x1FA3, 0x1FAB, eos, + 0x1FA4, 0x1FAC, eos, + 0x1FA5, 0x1FAD, eos, // 2281 + 0x1FA6, 0x1FAE, eos, + 0x1FA7, 0x1FAF, eos, + 0x1FB0, 0x1FB8, eos, // 2290 + 0x1FB1, 0x1FB9, eos, + 0x1F70, 0x1FBA, eos, + 0x1F71, 0x1FBB, eos, + 0x1FB3, 0x1FBC, eos, // 2302 + 0x1F72, 0x1FC8, eos, + 0x1F73, 0x1FC9, eos, + 0x1F74, 0x1FCA, eos, // 2311 + 0x1F75, 0x1FCB, eos, + 0x1FC3, 0x1FCC, eos, + 0x0390, 0x1FD3, eos, // 2320 + 0x1FD0, 0x1FD8, eos, + 0x1FD1, 0x1FD9, eos, + 0x1F76, 0x1FDA, eos, + 0x1F77, 0x1FDB, eos, // 2332 + 0x03B0, 0x1FE3, eos, + 0x1FE0, 0x1FE8, eos, + 0x1FE1, 0x1FE9, eos, // 2341 + 0x1F7A, 0x1FEA, eos, + 0x1F7B, 0x1FEB, eos, + 0x1FE5, 0x1FEC, eos, // 2350 + 0x1F78, 0x1FF8, eos, + 0x1F79, 0x1FF9, eos, + 0x1F7C, 0x1FFA, eos, + 0x1F7D, 0x1FFB, eos, // 2362 + 0x1FF3, 0x1FFC, eos, + 0x214E, 0x2132, eos, + 0x2170, 0x2160, eos, // 2371 + 0x2171, 0x2161, eos, + 0x2172, 0x2162, eos, + 0x2173, 0x2163, eos, // 2380 + 0x2174, 0x2164, eos, + 0x2175, 0x2165, eos, + 0x2176, 0x2166, eos, + 0x2177, 0x2167, eos, // 2392 + 0x2178, 0x2168, eos, + 0x2179, 0x2169, eos, + 0x217A, 0x216A, eos, // 2401 + 0x217B, 0x216B, eos, + 0x217C, 0x216C, eos, + 0x217D, 0x216D, eos, // 2410 + 0x217E, 0x216E, eos, + 0x217F, 0x216F, eos, + 0x2184, 0x2183, eos, + 0x24D0, 0x24B6, eos, // 2422 + 0x24D1, 0x24B7, eos, + 0x24D2, 0x24B8, eos, + 0x24D3, 0x24B9, eos, // 2431 + 0x24D4, 0x24BA, eos, + 0x24D5, 0x24BB, eos, + 0x24D6, 0x24BC, eos, // 2440 + 0x24D7, 0x24BD, eos, + 0x24D8, 0x24BE, eos, + 0x24D9, 0x24BF, eos, + 0x24DA, 0x24C0, eos, // 2452 + 0x24DB, 0x24C1, eos, + 0x24DC, 0x24C2, eos, + 0x24DD, 0x24C3, eos, // 2461 + 0x24DE, 0x24C4, eos, + 0x24DF, 0x24C5, eos, + 0x24E0, 0x24C6, eos, // 2470 + 0x24E1, 0x24C7, eos, + 0x24E2, 0x24C8, eos, + 0x24E3, 0x24C9, eos, + 0x24E4, 0x24CA, eos, // 2482 + 0x24E5, 0x24CB, eos, + 0x24E6, 0x24CC, eos, + 0x24E7, 0x24CD, eos, // 2491 + 0x24E8, 0x24CE, eos, + 0x24E9, 0x24CF, eos, + 0x2C30, 0x2C00, eos, // 2500 + 0x2C31, 0x2C01, eos, + 0x2C32, 0x2C02, eos, + 0x2C33, 0x2C03, eos, + 0x2C34, 0x2C04, eos, // 2512 + 0x2C35, 0x2C05, eos, + 0x2C36, 0x2C06, eos, + 0x2C37, 0x2C07, eos, // 2521 + 0x2C38, 0x2C08, eos, + 0x2C39, 0x2C09, eos, + 0x2C3A, 0x2C0A, eos, // 2530 + 0x2C3B, 0x2C0B, eos, + 0x2C3C, 0x2C0C, eos, + 0x2C3D, 0x2C0D, eos, + 0x2C3E, 0x2C0E, eos, // 2542 + 0x2C3F, 0x2C0F, eos, + 0x2C40, 0x2C10, eos, + 0x2C41, 0x2C11, eos, // 2551 + 0x2C42, 0x2C12, eos, + 0x2C43, 0x2C13, eos, + 0x2C44, 0x2C14, eos, // 2560 + 0x2C45, 0x2C15, eos, + 0x2C46, 0x2C16, eos, + 0x2C47, 0x2C17, eos, + 0x2C48, 0x2C18, eos, // 2572 + 0x2C49, 0x2C19, eos, + 0x2C4A, 0x2C1A, eos, + 0x2C4B, 0x2C1B, eos, // 2581 + 0x2C4C, 0x2C1C, eos, + 0x2C4D, 0x2C1D, eos, + 0x2C4E, 0x2C1E, eos, // 2590 + 0x2C4F, 0x2C1F, eos, + 0x2C50, 0x2C20, eos, + 0x2C51, 0x2C21, eos, + 0x2C52, 0x2C22, eos, // 2602 + 0x2C53, 0x2C23, eos, + 0x2C54, 0x2C24, eos, + 0x2C55, 0x2C25, eos, // 2611 + 0x2C56, 0x2C26, eos, + 0x2C57, 0x2C27, eos, + 0x2C58, 0x2C28, eos, // 2620 + 0x2C59, 0x2C29, eos, + 0x2C5A, 0x2C2A, eos, + 0x2C5B, 0x2C2B, eos, + 0x2C5C, 0x2C2C, eos, // 2632 + 0x2C5D, 0x2C2D, eos, + 0x2C5E, 0x2C2E, eos, + 0x2C5F, 0x2C2F, eos, // 2641 + 0x2C61, 0x2C60, eos, + 0x026B, 0x2C62, eos, + 0x1D7D, 0x2C63, eos, // 2650 + 0x027D, 0x2C64, eos, + 0x2C68, 0x2C67, eos, + 0x2C6A, 0x2C69, eos, + 0x2C6C, 0x2C6B, eos, // 2662 + 0x0251, 0x2C6D, eos, + 0x0271, 0x2C6E, eos, + 0x0250, 0x2C6F, eos, // 2671 + 0x0252, 0x2C70, eos, + 0x2C73, 0x2C72, eos, + 0x2C76, 0x2C75, eos, // 2680 + 0x023F, 0x2C7E, eos, + 0x0240, 0x2C7F, eos, + 0x2C81, 0x2C80, eos, + 0x2C83, 0x2C82, eos, // 2692 + 0x2C85, 0x2C84, eos, + 0x2C87, 0x2C86, eos, + 0x2C89, 0x2C88, eos, // 2701 + 0x2C8B, 0x2C8A, eos, + 0x2C8D, 0x2C8C, eos, + 0x2C8F, 0x2C8E, eos, // 2710 + 0x2C91, 0x2C90, eos, + 0x2C93, 0x2C92, eos, + 0x2C95, 0x2C94, eos, + 0x2C97, 0x2C96, eos, // 2722 + 0x2C99, 0x2C98, eos, + 0x2C9B, 0x2C9A, eos, + 0x2C9D, 0x2C9C, eos, // 2731 + 0x2C9F, 0x2C9E, eos, + 0x2CA1, 0x2CA0, eos, + 0x2CA3, 0x2CA2, eos, // 2740 + 0x2CA5, 0x2CA4, eos, + 0x2CA7, 0x2CA6, eos, + 0x2CA9, 0x2CA8, eos, + 0x2CAB, 0x2CAA, eos, // 2752 + 0x2CAD, 0x2CAC, eos, + 0x2CAF, 0x2CAE, eos, + 0x2CB1, 0x2CB0, eos, // 2761 + 0x2CB3, 0x2CB2, eos, + 0x2CB5, 0x2CB4, eos, + 0x2CB7, 0x2CB6, eos, // 2770 + 0x2CB9, 0x2CB8, eos, + 0x2CBB, 0x2CBA, eos, + 0x2CBD, 0x2CBC, eos, + 0x2CBF, 0x2CBE, eos, // 2782 + 0x2CC1, 0x2CC0, eos, + 0x2CC3, 0x2CC2, eos, + 0x2CC5, 0x2CC4, eos, // 2791 + 0x2CC7, 0x2CC6, eos, + 0x2CC9, 0x2CC8, eos, + 0x2CCB, 0x2CCA, eos, // 2800 + 0x2CCD, 0x2CCC, eos, + 0x2CCF, 0x2CCE, eos, + 0x2CD1, 0x2CD0, eos, + 0x2CD3, 0x2CD2, eos, // 2812 + 0x2CD5, 0x2CD4, eos, + 0x2CD7, 0x2CD6, eos, + 0x2CD9, 0x2CD8, eos, // 2821 + 0x2CDB, 0x2CDA, eos, + 0x2CDD, 0x2CDC, eos, + 0x2CDF, 0x2CDE, eos, // 2830 + 0x2CE1, 0x2CE0, eos, + 0x2CE3, 0x2CE2, eos, + 0x2CEC, 0x2CEB, eos, + 0x2CEE, 0x2CED, eos, // 2842 + 0x2CF3, 0x2CF2, eos, + 0xA641, 0xA640, eos, + 0xA643, 0xA642, eos, // 2851 + 0xA645, 0xA644, eos, + 0xA647, 0xA646, eos, + 0xA649, 0xA648, eos, // 2860 + 0xA64D, 0xA64C, eos, + 0xA64F, 0xA64E, eos, + 0xA651, 0xA650, eos, + 0xA653, 0xA652, eos, // 2872 + 0xA655, 0xA654, eos, + 0xA657, 0xA656, eos, + 0xA659, 0xA658, eos, // 2881 + 0xA65B, 0xA65A, eos, + 0xA65D, 0xA65C, eos, + 0xA65F, 0xA65E, eos, // 2890 + 0xA661, 0xA660, eos, + 0xA663, 0xA662, eos, + 0xA665, 0xA664, eos, + 0xA667, 0xA666, eos, // 2902 + 0xA669, 0xA668, eos, + 0xA66B, 0xA66A, eos, + 0xA66D, 0xA66C, eos, // 2911 + 0xA681, 0xA680, eos, + 0xA683, 0xA682, eos, + 0xA685, 0xA684, eos, // 2920 + 0xA687, 0xA686, eos, + 0xA689, 0xA688, eos, + 0xA68B, 0xA68A, eos, + 0xA68D, 0xA68C, eos, // 2932 + 0xA68F, 0xA68E, eos, + 0xA691, 0xA690, eos, + 0xA693, 0xA692, eos, // 2941 + 0xA695, 0xA694, eos, + 0xA697, 0xA696, eos, + 0xA699, 0xA698, eos, // 2950 + 0xA69B, 0xA69A, eos, + 0xA723, 0xA722, eos, + 0xA725, 0xA724, eos, + 0xA727, 0xA726, eos, // 2962 + 0xA729, 0xA728, eos, + 0xA72B, 0xA72A, eos, + 0xA72D, 0xA72C, eos, // 2971 + 0xA72F, 0xA72E, eos, + 0xA733, 0xA732, eos, + 0xA735, 0xA734, eos, // 2980 + 0xA737, 0xA736, eos, + 0xA739, 0xA738, eos, + 0xA73B, 0xA73A, eos, + 0xA73D, 0xA73C, eos, // 2992 + 0xA73F, 0xA73E, eos, + 0xA741, 0xA740, eos, + 0xA743, 0xA742, eos, // 3001 + 0xA745, 0xA744, eos, + 0xA747, 0xA746, eos, + 0xA749, 0xA748, eos, // 3010 + 0xA74B, 0xA74A, eos, + 0xA74D, 0xA74C, eos, + 0xA74F, 0xA74E, eos, + 0xA751, 0xA750, eos, // 3022 + 0xA753, 0xA752, eos, + 0xA755, 0xA754, eos, + 0xA757, 0xA756, eos, // 3031 + 0xA759, 0xA758, eos, + 0xA75B, 0xA75A, eos, + 0xA75D, 0xA75C, eos, // 3040 + 0xA75F, 0xA75E, eos, + 0xA761, 0xA760, eos, + 0xA763, 0xA762, eos, + 0xA765, 0xA764, eos, // 3052 + 0xA767, 0xA766, eos, + 0xA769, 0xA768, eos, + 0xA76B, 0xA76A, eos, // 3061 + 0xA76D, 0xA76C, eos, + 0xA76F, 0xA76E, eos, + 0xA77A, 0xA779, eos, // 3070 + 0xA77C, 0xA77B, eos, + 0x1D79, 0xA77D, eos, + 0xA77F, 0xA77E, eos, + 0xA781, 0xA780, eos, // 3082 + 0xA783, 0xA782, eos, + 0xA785, 0xA784, eos, + 0xA787, 0xA786, eos, // 3091 + 0xA78C, 0xA78B, eos, + 0x0265, 0xA78D, eos, + 0xA791, 0xA790, eos, // 3100 + 0xA793, 0xA792, eos, + 0xA797, 0xA796, eos, + 0xA799, 0xA798, eos, + 0xA79B, 0xA79A, eos, // 3112 + 0xA79D, 0xA79C, eos, + 0xA79F, 0xA79E, eos, + 0xA7A1, 0xA7A0, eos, // 3121 + 0xA7A3, 0xA7A2, eos, + 0xA7A5, 0xA7A4, eos, + 0xA7A7, 0xA7A6, eos, // 3130 + 0xA7A9, 0xA7A8, eos, + 0x0266, 0xA7AA, eos, + 0x025C, 0xA7AB, eos, + 0x0261, 0xA7AC, eos, // 3142 + 0x026C, 0xA7AD, eos, + 0x026A, 0xA7AE, eos, + 0x029E, 0xA7B0, eos, // 3151 + 0x0287, 0xA7B1, eos, + 0x029D, 0xA7B2, eos, + 0xAB53, 0xA7B3, eos, // 3160 + 0xA7B5, 0xA7B4, eos, + 0xA7B7, 0xA7B6, eos, + 0xA7B9, 0xA7B8, eos, + 0xA7BB, 0xA7BA, eos, // 3172 + 0xA7BD, 0xA7BC, eos, + 0xA7BF, 0xA7BE, eos, + 0xA7C1, 0xA7C0, eos, // 3181 + 0xA7C3, 0xA7C2, eos, + 0xA794, 0xA7C4, eos, + 0x0282, 0xA7C5, eos, // 3190 + 0x1D8E, 0xA7C6, eos, + 0xA7C8, 0xA7C7, eos, + 0xA7CA, 0xA7C9, eos, + 0x0264, 0xA7CB, eos, // 3202 + 0xA7CD, 0xA7CC, eos, + 0xA7CF, 0xA7CE, eos, + 0xA7D1, 0xA7D0, eos, // 3211 + 0xA7D3, 0xA7D2, eos, + 0xA7D5, 0xA7D4, eos, + 0xA7D7, 0xA7D6, eos, // 3220 + 0xA7D9, 0xA7D8, eos, + 0xA7DB, 0xA7DA, eos, + 0x019B, 0xA7DC, eos, + 0xA7F6, 0xA7F5, eos, // 3232 + 0x13A0, 0xAB70, eos, + 0x13A1, 0xAB71, eos, + 0x13A2, 0xAB72, eos, // 3241 + 0x13A3, 0xAB73, eos, + 0x13A4, 0xAB74, eos, + 0x13A5, 0xAB75, eos, // 3250 + 0x13A6, 0xAB76, eos, + 0x13A7, 0xAB77, eos, + 0x13A8, 0xAB78, eos, + 0x13A9, 0xAB79, eos, // 3262 + 0x13AA, 0xAB7A, eos, + 0x13AB, 0xAB7B, eos, + 0x13AC, 0xAB7C, eos, // 3271 + 0x13AD, 0xAB7D, eos, + 0x13AE, 0xAB7E, eos, + 0x13AF, 0xAB7F, eos, // 3280 + 0x13B0, 0xAB80, eos, + 0x13B1, 0xAB81, eos, + 0x13B2, 0xAB82, eos, + 0x13B3, 0xAB83, eos, // 3292 + 0x13B4, 0xAB84, eos, + 0x13B5, 0xAB85, eos, + 0x13B6, 0xAB86, eos, // 3301 + 0x13B7, 0xAB87, eos, + 0x13B8, 0xAB88, eos, + 0x13B9, 0xAB89, eos, // 3310 + 0x13BA, 0xAB8A, eos, + 0x13BB, 0xAB8B, eos, + 0x13BC, 0xAB8C, eos, + 0x13BD, 0xAB8D, eos, // 3322 + 0x13BE, 0xAB8E, eos, + 0x13BF, 0xAB8F, eos, + 0x13C0, 0xAB90, eos, // 3331 + 0x13C1, 0xAB91, eos, + 0x13C2, 0xAB92, eos, + 0x13C3, 0xAB93, eos, // 3340 + 0x13C4, 0xAB94, eos, + 0x13C5, 0xAB95, eos, + 0x13C6, 0xAB96, eos, + 0x13C7, 0xAB97, eos, // 3352 + 0x13C8, 0xAB98, eos, + 0x13C9, 0xAB99, eos, + 0x13CA, 0xAB9A, eos, // 3361 + 0x13CB, 0xAB9B, eos, + 0x13CC, 0xAB9C, eos, + 0x13CD, 0xAB9D, eos, // 3370 + 0x13CE, 0xAB9E, eos, + 0x13CF, 0xAB9F, eos, + 0x13D0, 0xABA0, eos, + 0x13D1, 0xABA1, eos, // 3382 + 0x13D2, 0xABA2, eos, + 0x13D3, 0xABA3, eos, + 0x13D4, 0xABA4, eos, // 3391 + 0x13D5, 0xABA5, eos, + 0x13D6, 0xABA6, eos, + 0x13D7, 0xABA7, eos, // 3400 + 0x13D8, 0xABA8, eos, + 0x13D9, 0xABA9, eos, + 0x13DA, 0xABAA, eos, + 0x13DB, 0xABAB, eos, // 3412 + 0x13DC, 0xABAC, eos, + 0x13DD, 0xABAD, eos, + 0x13DE, 0xABAE, eos, // 3421 + 0x13DF, 0xABAF, eos, + 0x13E0, 0xABB0, eos, + 0x13E1, 0xABB1, eos, // 3430 + 0x13E2, 0xABB2, eos, + 0x13E3, 0xABB3, eos, + 0x13E4, 0xABB4, eos, + 0x13E5, 0xABB5, eos, // 3442 + 0x13E6, 0xABB6, eos, + 0x13E7, 0xABB7, eos, + 0x13E8, 0xABB8, eos, // 3451 + 0x13E9, 0xABB9, eos, + 0x13EA, 0xABBA, eos, + 0x13EB, 0xABBB, eos, // 3460 + 0x13EC, 0xABBC, eos, + 0x13ED, 0xABBD, eos, + 0x13EE, 0xABBE, eos, + 0x13EF, 0xABBF, eos, // 3472 + 0xFB06, 0xFB05, eos, + 0xFF41, 0xFF21, eos, + 0xFF42, 0xFF22, eos, // 3481 + 0xFF43, 0xFF23, eos, + 0xFF44, 0xFF24, eos, + 0xFF45, 0xFF25, eos, // 3490 + 0xFF46, 0xFF26, eos, + 0xFF47, 0xFF27, eos, + 0xFF48, 0xFF28, eos, + 0xFF49, 0xFF29, eos, // 3502 + 0xFF4A, 0xFF2A, eos, + 0xFF4B, 0xFF2B, eos, + 0xFF4C, 0xFF2C, eos, // 3511 + 0xFF4D, 0xFF2D, eos, + 0xFF4E, 0xFF2E, eos, + 0xFF4F, 0xFF2F, eos, // 3520 + 0xFF50, 0xFF30, eos, + 0xFF51, 0xFF31, eos, + 0xFF52, 0xFF32, eos, + 0xFF53, 0xFF33, eos, // 3532 + 0xFF54, 0xFF34, eos, + 0xFF55, 0xFF35, eos, + 0xFF56, 0xFF36, eos, // 3541 + 0xFF57, 0xFF37, eos, + 0xFF58, 0xFF38, eos, + 0xFF59, 0xFF39, eos, // 3550 + 0xFF5A, 0xFF3A, eos, + 0x10428, 0x10400, eos, + 0x10429, 0x10401, eos, + 0x1042A, 0x10402, eos, // 3562 + 0x1042B, 0x10403, eos, + 0x1042C, 0x10404, eos, + 0x1042D, 0x10405, eos, // 3571 + 0x1042E, 0x10406, eos, + 0x1042F, 0x10407, eos, + 0x10430, 0x10408, eos, // 3580 + 0x10431, 0x10409, eos, + 0x10432, 0x1040A, eos, + 0x10433, 0x1040B, eos, + 0x10434, 0x1040C, eos, // 3592 + 0x10435, 0x1040D, eos, + 0x10436, 0x1040E, eos, + 0x10437, 0x1040F, eos, // 3601 + 0x10438, 0x10410, eos, + 0x10439, 0x10411, eos, + 0x1043A, 0x10412, eos, // 3610 + 0x1043B, 0x10413, eos, + 0x1043C, 0x10414, eos, + 0x1043D, 0x10415, eos, + 0x1043E, 0x10416, eos, // 3622 + 0x1043F, 0x10417, eos, + 0x10440, 0x10418, eos, + 0x10441, 0x10419, eos, // 3631 + 0x10442, 0x1041A, eos, + 0x10443, 0x1041B, eos, + 0x10444, 0x1041C, eos, // 3640 + 0x10445, 0x1041D, eos, + 0x10446, 0x1041E, eos, + 0x10447, 0x1041F, eos, + 0x10448, 0x10420, eos, // 3652 + 0x10449, 0x10421, eos, + 0x1044A, 0x10422, eos, + 0x1044B, 0x10423, eos, // 3661 + 0x1044C, 0x10424, eos, + 0x1044D, 0x10425, eos, + 0x1044E, 0x10426, eos, // 3670 + 0x1044F, 0x10427, eos, + 0x104D8, 0x104B0, eos, + 0x104D9, 0x104B1, eos, + 0x104DA, 0x104B2, eos, // 3682 + 0x104DB, 0x104B3, eos, + 0x104DC, 0x104B4, eos, + 0x104DD, 0x104B5, eos, // 3691 + 0x104DE, 0x104B6, eos, + 0x104DF, 0x104B7, eos, + 0x104E0, 0x104B8, eos, // 3700 + 0x104E1, 0x104B9, eos, + 0x104E2, 0x104BA, eos, + 0x104E3, 0x104BB, eos, + 0x104E4, 0x104BC, eos, // 3712 + 0x104E5, 0x104BD, eos, + 0x104E6, 0x104BE, eos, + 0x104E7, 0x104BF, eos, // 3721 + 0x104E8, 0x104C0, eos, + 0x104E9, 0x104C1, eos, + 0x104EA, 0x104C2, eos, // 3730 + 0x104EB, 0x104C3, eos, + 0x104EC, 0x104C4, eos, + 0x104ED, 0x104C5, eos, + 0x104EE, 0x104C6, eos, // 3742 + 0x104EF, 0x104C7, eos, + 0x104F0, 0x104C8, eos, + 0x104F1, 0x104C9, eos, // 3751 + 0x104F2, 0x104CA, eos, + 0x104F3, 0x104CB, eos, + 0x104F4, 0x104CC, eos, // 3760 + 0x104F5, 0x104CD, eos, + 0x104F6, 0x104CE, eos, + 0x104F7, 0x104CF, eos, + 0x104F8, 0x104D0, eos, // 3772 + 0x104F9, 0x104D1, eos, + 0x104FA, 0x104D2, eos, + 0x104FB, 0x104D3, eos, // 3781 + 0x10597, 0x10570, eos, + 0x10598, 0x10571, eos, + 0x10599, 0x10572, eos, // 3790 + 0x1059A, 0x10573, eos, + 0x1059B, 0x10574, eos, + 0x1059C, 0x10575, eos, + 0x1059D, 0x10576, eos, // 3802 + 0x1059E, 0x10577, eos, + 0x1059F, 0x10578, eos, + 0x105A0, 0x10579, eos, // 3811 + 0x105A1, 0x1057A, eos, + 0x105A3, 0x1057C, eos, + 0x105A4, 0x1057D, eos, // 3820 + 0x105A5, 0x1057E, eos, + 0x105A6, 0x1057F, eos, + 0x105A7, 0x10580, eos, + 0x105A8, 0x10581, eos, // 3832 + 0x105A9, 0x10582, eos, + 0x105AA, 0x10583, eos, + 0x105AB, 0x10584, eos, // 3841 + 0x105AC, 0x10585, eos, + 0x105AD, 0x10586, eos, + 0x105AE, 0x10587, eos, // 3850 + 0x105AF, 0x10588, eos, + 0x105B0, 0x10589, eos, + 0x105B1, 0x1058A, eos, + 0x105B3, 0x1058C, eos, // 3862 + 0x105B4, 0x1058D, eos, + 0x105B5, 0x1058E, eos, + 0x105B6, 0x1058F, eos, // 3871 + 0x105B7, 0x10590, eos, + 0x105B8, 0x10591, eos, + 0x105B9, 0x10592, eos, // 3880 + 0x105BB, 0x10594, eos, + 0x105BC, 0x10595, eos, + 0x10CC0, 0x10C80, eos, + 0x10CC1, 0x10C81, eos, // 3892 + 0x10CC2, 0x10C82, eos, + 0x10CC3, 0x10C83, eos, + 0x10CC4, 0x10C84, eos, // 3901 + 0x10CC5, 0x10C85, eos, + 0x10CC6, 0x10C86, eos, + 0x10CC7, 0x10C87, eos, // 3910 + 0x10CC8, 0x10C88, eos, + 0x10CC9, 0x10C89, eos, + 0x10CCA, 0x10C8A, eos, + 0x10CCB, 0x10C8B, eos, // 3922 + 0x10CCC, 0x10C8C, eos, + 0x10CCD, 0x10C8D, eos, + 0x10CCE, 0x10C8E, eos, // 3931 + 0x10CCF, 0x10C8F, eos, + 0x10CD0, 0x10C90, eos, + 0x10CD1, 0x10C91, eos, // 3940 + 0x10CD2, 0x10C92, eos, + 0x10CD3, 0x10C93, eos, + 0x10CD4, 0x10C94, eos, + 0x10CD5, 0x10C95, eos, // 3952 + 0x10CD6, 0x10C96, eos, + 0x10CD7, 0x10C97, eos, + 0x10CD8, 0x10C98, eos, // 3961 + 0x10CD9, 0x10C99, eos, + 0x10CDA, 0x10C9A, eos, + 0x10CDB, 0x10C9B, eos, // 3970 + 0x10CDC, 0x10C9C, eos, + 0x10CDD, 0x10C9D, eos, + 0x10CDE, 0x10C9E, eos, + 0x10CDF, 0x10C9F, eos, // 3982 + 0x10CE0, 0x10CA0, eos, + 0x10CE1, 0x10CA1, eos, + 0x10CE2, 0x10CA2, eos, // 3991 + 0x10CE3, 0x10CA3, eos, + 0x10CE4, 0x10CA4, eos, + 0x10CE5, 0x10CA5, eos, // 4000 + 0x10CE6, 0x10CA6, eos, + 0x10CE7, 0x10CA7, eos, + 0x10CE8, 0x10CA8, eos, + 0x10CE9, 0x10CA9, eos, // 4012 + 0x10CEA, 0x10CAA, eos, + 0x10CEB, 0x10CAB, eos, + 0x10CEC, 0x10CAC, eos, // 4021 + 0x10CED, 0x10CAD, eos, + 0x10CEE, 0x10CAE, eos, + 0x10CEF, 0x10CAF, eos, // 4030 + 0x10CF0, 0x10CB0, eos, + 0x10CF1, 0x10CB1, eos, + 0x10CF2, 0x10CB2, eos, + 0x10D70, 0x10D50, eos, // 4042 + 0x10D71, 0x10D51, eos, + 0x10D72, 0x10D52, eos, + 0x10D73, 0x10D53, eos, // 4051 + 0x10D74, 0x10D54, eos, + 0x10D75, 0x10D55, eos, + 0x10D76, 0x10D56, eos, // 4060 + 0x10D77, 0x10D57, eos, + 0x10D78, 0x10D58, eos, + 0x10D79, 0x10D59, eos, + 0x10D7A, 0x10D5A, eos, // 4072 + 0x10D7B, 0x10D5B, eos, + 0x10D7C, 0x10D5C, eos, + 0x10D7D, 0x10D5D, eos, // 4081 + 0x10D7E, 0x10D5E, eos, + 0x10D7F, 0x10D5F, eos, + 0x10D80, 0x10D60, eos, // 4090 + 0x10D81, 0x10D61, eos, + 0x10D82, 0x10D62, eos, + 0x10D83, 0x10D63, eos, + 0x10D84, 0x10D64, eos, // 4102 + 0x10D85, 0x10D65, eos, + 0x118C0, 0x118A0, eos, + 0x118C1, 0x118A1, eos, // 4111 + 0x118C2, 0x118A2, eos, + 0x118C3, 0x118A3, eos, + 0x118C4, 0x118A4, eos, // 4120 + 0x118C5, 0x118A5, eos, + 0x118C6, 0x118A6, eos, + 0x118C7, 0x118A7, eos, + 0x118C8, 0x118A8, eos, // 4132 + 0x118C9, 0x118A9, eos, + 0x118CA, 0x118AA, eos, + 0x118CB, 0x118AB, eos, // 4141 + 0x118CC, 0x118AC, eos, + 0x118CD, 0x118AD, eos, + 0x118CE, 0x118AE, eos, // 4150 + 0x118CF, 0x118AF, eos, + 0x118D0, 0x118B0, eos, + 0x118D1, 0x118B1, eos, + 0x118D2, 0x118B2, eos, // 4162 + 0x118D3, 0x118B3, eos, + 0x118D4, 0x118B4, eos, + 0x118D5, 0x118B5, eos, // 4171 + 0x118D6, 0x118B6, eos, + 0x118D7, 0x118B7, eos, + 0x118D8, 0x118B8, eos, // 4180 + 0x118D9, 0x118B9, eos, + 0x118DA, 0x118BA, eos, + 0x118DB, 0x118BB, eos, + 0x118DC, 0x118BC, eos, // 4192 + 0x118DD, 0x118BD, eos, + 0x118DE, 0x118BE, eos, + 0x118DF, 0x118BF, eos, // 4201 + 0x16E60, 0x16E40, eos, + 0x16E61, 0x16E41, eos, + 0x16E62, 0x16E42, eos, // 4210 + 0x16E63, 0x16E43, eos, + 0x16E64, 0x16E44, eos, + 0x16E65, 0x16E45, eos, + 0x16E66, 0x16E46, eos, // 4222 + 0x16E67, 0x16E47, eos, + 0x16E68, 0x16E48, eos, + 0x16E69, 0x16E49, eos, // 4231 + 0x16E6A, 0x16E4A, eos, + 0x16E6B, 0x16E4B, eos, + 0x16E6C, 0x16E4C, eos, // 4240 + 0x16E6D, 0x16E4D, eos, + 0x16E6E, 0x16E4E, eos, + 0x16E6F, 0x16E4F, eos, + 0x16E70, 0x16E50, eos, // 4252 + 0x16E71, 0x16E51, eos, + 0x16E72, 0x16E52, eos, + 0x16E73, 0x16E53, eos, // 4261 + 0x16E74, 0x16E54, eos, + 0x16E75, 0x16E55, eos, + 0x16E76, 0x16E56, eos, // 4270 + 0x16E77, 0x16E57, eos, + 0x16E78, 0x16E58, eos, + 0x16E79, 0x16E59, eos, + 0x16E7A, 0x16E5A, eos, // 4282 + 0x16E7B, 0x16E5B, eos, + 0x16E7C, 0x16E5C, eos, + 0x16E7D, 0x16E5D, eos, // 4291 + 0x16E7E, 0x16E5E, eos, + 0x16E7F, 0x16E5F, eos, + 0x16EBB, 0x16EA0, eos, // 4300 + 0x16EBC, 0x16EA1, eos, + 0x16EBD, 0x16EA2, eos, + 0x16EBE, 0x16EA3, eos, + 0x16EBF, 0x16EA4, eos, // 4312 + 0x16EC0, 0x16EA5, eos, + 0x16EC1, 0x16EA6, eos, + 0x16EC2, 0x16EA7, eos, // 4321 + 0x16EC3, 0x16EA8, eos, + 0x16EC4, 0x16EA9, eos, + 0x16EC5, 0x16EAA, eos, // 4330 + 0x16EC6, 0x16EAB, eos, + 0x16EC7, 0x16EAC, eos, + 0x16EC8, 0x16EAD, eos, + 0x16EC9, 0x16EAE, eos, // 4342 + 0x16ECA, 0x16EAF, eos, + 0x16ECB, 0x16EB0, eos, + 0x16ECC, 0x16EB1, eos, // 4351 + 0x16ECD, 0x16EB2, eos, + 0x16ECE, 0x16EB3, eos, + 0x16ECF, 0x16EB4, eos, // 4360 + 0x16ED0, 0x16EB5, eos, + 0x16ED1, 0x16EB6, eos, + 0x16ED2, 0x16EB7, eos, + 0x16ED3, 0x16EB8, eos, // 4372 + 0x1E922, 0x1E900, eos, + 0x1E923, 0x1E901, eos, + 0x1E924, 0x1E902, eos, // 4381 + 0x1E925, 0x1E903, eos, + 0x1E926, 0x1E904, eos, + 0x1E927, 0x1E905, eos, // 4390 + 0x1E928, 0x1E906, eos, + 0x1E929, 0x1E907, eos, + 0x1E92A, 0x1E908, eos, + 0x1E92B, 0x1E909, eos, // 4402 + 0x1E92C, 0x1E90A, eos, + 0x1E92D, 0x1E90B, eos, + 0x1E92E, 0x1E90C, eos, // 4411 + 0x1E92F, 0x1E90D, eos, + 0x1E930, 0x1E90E, eos, + 0x1E931, 0x1E90F, eos, // 4420 + 0x1E932, 0x1E910, eos, + 0x1E933, 0x1E911, eos, + 0x1E934, 0x1E912, eos, + 0x1E935, 0x1E913, eos, // 4432 + 0x1E936, 0x1E914, eos, + 0x1E937, 0x1E915, eos, + 0x1E938, 0x1E916, eos, // 4441 + 0x1E939, 0x1E917, eos, + 0x1E93A, 0x1E918, eos, + 0x1E93B, 0x1E919, eos, // 4450 + 0x1E93C, 0x1E91A, eos, + 0x1E93D, 0x1E91B, eos, + 0x1E93E, 0x1E91C, eos, + 0x1E93F, 0x1E91D, eos, // 4462 + 0x1E940, 0x1E91E, eos, + 0x1E941, 0x1E91F, eos, + 0x1E942, 0x1E920, eos, // 4471 + 0x1E943, 0x1E921, eos // 4474 +}; +#define SRELL_UCFDATA_VERSION 201 diff --git a/pjsonlib/src/third_party/srell/srell_updata3.h b/pjsonlib/src/third_party/srell/srell_updata3.h new file mode 100644 index 0000000..9b982e8 --- /dev/null +++ b/pjsonlib/src/third_party/srell/srell_updata3.h @@ -0,0 +1,10129 @@ +// UnicodeData.txt +// +// PropList-17.0.0.txt +// Date: 2025-06-30, 06:19:01 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html +// +// DerivedCoreProperties-17.0.0.txt +// Date: 2025-07-30, 23:55:08 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html +// +// emoji-data.txt +// Date: 2025-07-25, 17:54:31 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html +// +// DerivedNormalizationProps-17.0.0.txt +// Date: 2025-01-27, 18:09:14 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html +// +// emoji-sequences.txt +// Date: 2025-07-25, 17:54:32 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html +// +// emoji-zwj-sequences.txt +// Date: 2025-01-08, 04:57:12 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html +// +// PropertyValueAliases-17.0.0.txt +// Date: 2025-06-30, 06:16:21 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html +// +// Scripts-17.0.0.txt +// Date: 2025-07-24, 13:28:55 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html +// +// ScriptExtensions-17.0.0.txt +// Date: 2025-08-01, 21:42:00 GMT +// © 2025 Unicode®, Inc. +// Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +// For terms of use and license, see https://www.unicode.org/terms_of_use.html +// + +enum upid_type +{ + upid_unknown = 0, + upid_invalid = 0, + upid_error = 0, + uptype_bp = 1, + uptype_gc = 2, + uptype_sc = 3, + uptype_scx = 4, + gc_Other = 5, + gc_Control = 6, + gc_Format = 7, + gc_Unassigned = 8, + gc_Private_Use = 9, + gc_Surrogate = 10, + gc_Letter = 11, + gc_Cased_Letter = 12, + gc_Lowercase_Letter = 13, + gc_Titlecase_Letter = 14, + gc_Uppercase_Letter = 15, + gc_Modifier_Letter = 16, + gc_Other_Letter = 17, + gc_Mark = 18, + gc_Spacing_Mark = 19, + gc_Enclosing_Mark = 20, + gc_Nonspacing_Mark = 21, + gc_Number = 22, + gc_Decimal_Number = 23, + gc_Letter_Number = 24, + gc_Other_Number = 25, + gc_Punctuation = 26, + gc_Connector_Punctuation = 27, + gc_Dash_Punctuation = 28, + gc_Close_Punctuation = 29, + gc_Final_Punctuation = 30, + gc_Initial_Punctuation = 31, + gc_Other_Punctuation = 32, + gc_Open_Punctuation = 33, + gc_Symbol = 34, + gc_Currency_Symbol = 35, + gc_Modifier_Symbol = 36, + gc_Math_Symbol = 37, + gc_Other_Symbol = 38, + gc_Separator = 39, + gc_Line_Separator = 40, + gc_Paragraph_Separator = 41, + gc_Space_Separator = 42, + bp_ASCII = 43, + bp_ASCII_Hex_Digit = 44, + bp_Alphabetic = 45, + bp_Any = 46, + bp_Assigned = 47, + bp_Bidi_Control = 48, + bp_Bidi_Mirrored = 49, + bp_Case_Ignorable = 50, + bp_Cased = 51, + bp_Changes_When_Casefolded = 52, + bp_Changes_When_Casemapped = 53, + bp_Changes_When_Lowercased = 54, + bp_Changes_When_NFKC_Casefolded = 55, + bp_Changes_When_Titlecased = 56, + bp_Changes_When_Uppercased = 57, + bp_Dash = 58, + bp_Default_Ignorable_Code_Point = 59, + bp_Deprecated = 60, + bp_Diacritic = 61, + bp_Emoji = 62, + bp_Emoji_Component = 63, + bp_Emoji_Modifier = 64, + bp_Emoji_Modifier_Base = 65, + bp_Emoji_Presentation = 66, + bp_Extended_Pictographic = 67, + bp_Extender = 68, + bp_Grapheme_Base = 69, + bp_Grapheme_Extend = 70, + bp_Hex_Digit = 71, + bp_IDS_Binary_Operator = 72, + bp_IDS_Trinary_Operator = 73, + bp_ID_Continue = 74, + bp_ID_Start = 75, + bp_Ideographic = 76, + bp_Join_Control = 77, + bp_Logical_Order_Exception = 78, + bp_Lowercase = 79, + bp_Math = 80, + bp_Noncharacter_Code_Point = 81, + bp_Pattern_Syntax = 82, + bp_Pattern_White_Space = 83, + bp_Quotation_Mark = 84, + bp_Radical = 85, + bp_Regional_Indicator = 86, + bp_Sentence_Terminal = 87, + bp_Soft_Dotted = 88, + bp_Terminal_Punctuation = 89, + bp_Unified_Ideograph = 90, + bp_Uppercase = 91, + bp_Variation_Selector = 92, + bp_White_Space = 93, + bp_XID_Continue = 94, + bp_XID_Start = 95, + sc_Common = 96, + sc_Latin = 97, + sc_Greek = 98, + sc_Cyrillic = 99, + sc_Armenian = 100, + sc_Hebrew = 101, + sc_Arabic = 102, + sc_Syriac = 103, + sc_Thaana = 104, + sc_Devanagari = 105, + sc_Bengali = 106, + sc_Gurmukhi = 107, + sc_Gujarati = 108, + sc_Oriya = 109, + sc_Tamil = 110, + sc_Telugu = 111, + sc_Kannada = 112, + sc_Malayalam = 113, + sc_Sinhala = 114, + sc_Thai = 115, + sc_Lao = 116, + sc_Tibetan = 117, + sc_Myanmar = 118, + sc_Georgian = 119, + sc_Hangul = 120, + sc_Ethiopic = 121, + sc_Cherokee = 122, + sc_Canadian_Aboriginal = 123, + sc_Ogham = 124, + sc_Runic = 125, + sc_Khmer = 126, + sc_Mongolian = 127, + sc_Hiragana = 128, + sc_Katakana = 129, + sc_Bopomofo = 130, + sc_Han = 131, + sc_Yi = 132, + sc_Old_Italic = 133, + sc_Gothic = 134, + sc_Deseret = 135, + sc_Inherited = 136, + sc_Tagalog = 137, + sc_Hanunoo = 138, + sc_Buhid = 139, + sc_Tagbanwa = 140, + sc_Limbu = 141, + sc_Tai_Le = 142, + sc_Linear_B = 143, + sc_Ugaritic = 144, + sc_Shavian = 145, + sc_Osmanya = 146, + sc_Cypriot = 147, + sc_Braille = 148, + sc_Buginese = 149, + sc_Coptic = 150, + sc_New_Tai_Lue = 151, + sc_Glagolitic = 152, + sc_Tifinagh = 153, + sc_Syloti_Nagri = 154, + sc_Old_Persian = 155, + sc_Kharoshthi = 156, + sc_Balinese = 157, + sc_Cuneiform = 158, + sc_Phoenician = 159, + sc_Phags_Pa = 160, + sc_Nko = 161, + sc_Sundanese = 162, + sc_Lepcha = 163, + sc_Ol_Chiki = 164, + sc_Vai = 165, + sc_Saurashtra = 166, + sc_Kayah_Li = 167, + sc_Rejang = 168, + sc_Lycian = 169, + sc_Carian = 170, + sc_Lydian = 171, + sc_Cham = 172, + sc_Tai_Tham = 173, + sc_Tai_Viet = 174, + sc_Avestan = 175, + sc_Egyptian_Hieroglyphs = 176, + sc_Samaritan = 177, + sc_Lisu = 178, + sc_Bamum = 179, + sc_Javanese = 180, + sc_Meetei_Mayek = 181, + sc_Imperial_Aramaic = 182, + sc_Old_South_Arabian = 183, + sc_Inscriptional_Parthian = 184, + sc_Inscriptional_Pahlavi = 185, + sc_Old_Turkic = 186, + sc_Kaithi = 187, + sc_Batak = 188, + sc_Brahmi = 189, + sc_Mandaic = 190, + sc_Chakma = 191, + sc_Meroitic_Cursive = 192, + sc_Meroitic_Hieroglyphs = 193, + sc_Miao = 194, + sc_Sharada = 195, + sc_Sora_Sompeng = 196, + sc_Takri = 197, + sc_Caucasian_Albanian = 198, + sc_Bassa_Vah = 199, + sc_Duployan = 200, + sc_Elbasan = 201, + sc_Grantha = 202, + sc_Pahawh_Hmong = 203, + sc_Khojki = 204, + sc_Linear_A = 205, + sc_Mahajani = 206, + sc_Manichaean = 207, + sc_Mende_Kikakui = 208, + sc_Modi = 209, + sc_Mro = 210, + sc_Old_North_Arabian = 211, + sc_Nabataean = 212, + sc_Palmyrene = 213, + sc_Pau_Cin_Hau = 214, + sc_Old_Permic = 215, + sc_Psalter_Pahlavi = 216, + sc_Siddham = 217, + sc_Khudawadi = 218, + sc_Tirhuta = 219, + sc_Warang_Citi = 220, + sc_Ahom = 221, + sc_Anatolian_Hieroglyphs = 222, + sc_Hatran = 223, + sc_Multani = 224, + sc_Old_Hungarian = 225, + sc_SignWriting = 226, + sc_Adlam = 227, + sc_Bhaiksuki = 228, + sc_Marchen = 229, + sc_Newa = 230, + sc_Osage = 231, + sc_Tangut = 232, + sc_Masaram_Gondi = 233, + sc_Nushu = 234, + sc_Soyombo = 235, + sc_Zanabazar_Square = 236, + sc_Dogra = 237, + sc_Gunjala_Gondi = 238, + sc_Makasar = 239, + sc_Medefaidrin = 240, + sc_Hanifi_Rohingya = 241, + sc_Sogdian = 242, + sc_Old_Sogdian = 243, + sc_Elymaic = 244, + sc_Nandinagari = 245, + sc_Nyiakeng_Puachue_Hmong = 246, + sc_Wancho = 247, + sc_Chorasmian = 248, + sc_Dives_Akuru = 249, + sc_Khitan_Small_Script = 250, + sc_Yezidi = 251, + sc_Cypro_Minoan = 252, + sc_Old_Uyghur = 253, + sc_Tangsa = 254, + sc_Toto = 255, + sc_Vithkuqi = 256, + sc_Kawi = 257, + sc_Nag_Mundari = 258, + sc_Garay = 259, + sc_Gurung_Khema = 260, + sc_Kirat_Rai = 261, + sc_Ol_Onal = 262, + sc_Sunuwar = 263, + sc_Todhri = 264, + sc_Tulu_Tigalari = 265, + sc_Sidetic = 266, + sc_Tai_Yo = 267, + sc_Tolong_Siki = 268, + sc_Beria_Erfe = 269, + sc_Unknown = 270, + scx_Common = 271, + scx_Latin = 272, + scx_Greek = 273, + scx_Cyrillic = 274, + scx_Armenian = 275, + scx_Hebrew = 276, + scx_Arabic = 277, + scx_Syriac = 278, + scx_Thaana = 279, + scx_Devanagari = 280, + scx_Bengali = 281, + scx_Gurmukhi = 282, + scx_Gujarati = 283, + scx_Oriya = 284, + scx_Tamil = 285, + scx_Telugu = 286, + scx_Kannada = 287, + scx_Malayalam = 288, + scx_Sinhala = 289, + scx_Thai = 290, + scx_Lao = 116, // #291 + scx_Tibetan = 291, // #292 + scx_Myanmar = 292, // #293 + scx_Georgian = 293, // #294 + scx_Hangul = 294, // #295 + scx_Ethiopic = 295, // #296 + scx_Cherokee = 296, // #297 + scx_Canadian_Aboriginal = 123, // #298 + scx_Ogham = 124, // #299 + scx_Runic = 297, // #300 + scx_Khmer = 126, // #301 + scx_Mongolian = 298, // #302 + scx_Hiragana = 299, // #303 + scx_Katakana = 300, // #304 + scx_Bopomofo = 301, // #305 + scx_Han = 302, // #306 + scx_Yi = 303, // #307 + scx_Old_Italic = 133, // #308 + scx_Gothic = 304, // #309 + scx_Deseret = 135, // #310 + scx_Inherited = 305, // #311 + scx_Tagalog = 306, // #312 + scx_Hanunoo = 307, // #313 + scx_Buhid = 308, // #314 + scx_Tagbanwa = 309, // #315 + scx_Limbu = 310, // #316 + scx_Tai_Le = 311, // #317 + scx_Linear_B = 312, // #318 + scx_Ugaritic = 144, // #319 + scx_Shavian = 313, // #320 + scx_Osmanya = 146, // #321 + scx_Cypriot = 314, // #322 + scx_Braille = 148, // #323 + scx_Buginese = 315, // #324 + scx_Coptic = 316, // #325 + scx_New_Tai_Lue = 151, // #326 + scx_Glagolitic = 317, // #327 + scx_Tifinagh = 318, // #328 + scx_Syloti_Nagri = 319, // #329 + scx_Old_Persian = 155, // #330 + scx_Kharoshthi = 156, // #331 + scx_Balinese = 157, // #332 + scx_Cuneiform = 158, // #333 + scx_Phoenician = 159, // #334 + scx_Phags_Pa = 320, // #335 + scx_Nko = 321, // #336 + scx_Sundanese = 162, // #337 + scx_Lepcha = 163, // #338 + scx_Ol_Chiki = 164, // #339 + scx_Vai = 165, // #340 + scx_Saurashtra = 166, // #341 + scx_Kayah_Li = 322, // #342 + scx_Rejang = 168, // #343 + scx_Lycian = 323, // #344 + scx_Carian = 324, // #345 + scx_Lydian = 325, // #346 + scx_Cham = 172, // #347 + scx_Tai_Tham = 173, // #348 + scx_Tai_Viet = 174, // #349 + scx_Avestan = 326, // #350 + scx_Egyptian_Hieroglyphs = 176, // #351 + scx_Samaritan = 327, // #352 + scx_Lisu = 328, // #353 + scx_Bamum = 179, // #354 + scx_Javanese = 329, // #355 + scx_Meetei_Mayek = 181, // #356 + scx_Imperial_Aramaic = 182, // #357 + scx_Old_South_Arabian = 183, // #358 + scx_Inscriptional_Parthian = 184, // #359 + scx_Inscriptional_Pahlavi = 185, // #360 + scx_Old_Turkic = 330, // #361 + scx_Kaithi = 331, // #362 + scx_Batak = 188, // #363 + scx_Brahmi = 189, // #364 + scx_Mandaic = 332, // #365 + scx_Chakma = 333, // #366 + scx_Meroitic_Cursive = 192, // #367 + scx_Meroitic_Hieroglyphs = 334, // #368 + scx_Miao = 194, // #369 + scx_Sharada = 335, // #370 + scx_Sora_Sompeng = 196, // #371 + scx_Takri = 336, // #372 + scx_Caucasian_Albanian = 337, // #373 + scx_Bassa_Vah = 199, // #374 + scx_Duployan = 338, // #375 + scx_Elbasan = 339, // #376 + scx_Grantha = 340, // #377 + scx_Pahawh_Hmong = 203, // #378 + scx_Khojki = 341, // #379 + scx_Linear_A = 342, // #380 + scx_Mahajani = 343, // #381 + scx_Manichaean = 344, // #382 + scx_Mende_Kikakui = 208, // #383 + scx_Modi = 345, // #384 + scx_Mro = 210, // #385 + scx_Old_North_Arabian = 211, // #386 + scx_Nabataean = 212, // #387 + scx_Palmyrene = 213, // #388 + scx_Pau_Cin_Hau = 214, // #389 + scx_Old_Permic = 346, // #390 + scx_Psalter_Pahlavi = 347, // #391 + scx_Siddham = 217, // #392 + scx_Khudawadi = 348, // #393 + scx_Tirhuta = 349, // #394 + scx_Warang_Citi = 220, // #395 + scx_Ahom = 221, // #396 + scx_Anatolian_Hieroglyphs = 222, // #397 + scx_Hatran = 223, // #398 + scx_Multani = 350, // #399 + scx_Old_Hungarian = 351, // #400 + scx_SignWriting = 226, // #401 + scx_Adlam = 352, // #402 + scx_Bhaiksuki = 228, // #403 + scx_Marchen = 229, // #404 + scx_Newa = 353, // #405 + scx_Osage = 354, // #406 + scx_Tangut = 355, // #407 + scx_Masaram_Gondi = 356, // #408 + scx_Nushu = 234, // #409 + scx_Soyombo = 235, // #410 + scx_Zanabazar_Square = 236, // #411 + scx_Dogra = 357, // #412 + scx_Gunjala_Gondi = 358, // #413 + scx_Makasar = 239, // #414 + scx_Medefaidrin = 240, // #415 + scx_Hanifi_Rohingya = 359, // #416 + scx_Sogdian = 360, // #417 + scx_Old_Sogdian = 243, // #418 + scx_Elymaic = 244, // #419 + scx_Nandinagari = 361, // #420 + scx_Nyiakeng_Puachue_Hmong = 246, // #421 + scx_Wancho = 247, // #422 + scx_Chorasmian = 248, // #423 + scx_Dives_Akuru = 249, // #424 + scx_Khitan_Small_Script = 250, // #425 + scx_Yezidi = 362, // #426 + scx_Cypro_Minoan = 363, // #427 + scx_Old_Uyghur = 364, // #428 + scx_Tangsa = 254, // #429 + scx_Toto = 365, // #430 + scx_Vithkuqi = 256, // #431 + scx_Kawi = 257, // #432 + scx_Nag_Mundari = 258, // #433 + scx_Garay = 366, // #434 + scx_Gurung_Khema = 367, // #435 + scx_Kirat_Rai = 261, // #436 + scx_Ol_Onal = 368, // #437 + scx_Sunuwar = 369, // #438 + scx_Todhri = 370, // #439 + scx_Tulu_Tigalari = 371, // #440 + scx_Sidetic = 266, // #441 + scx_Tai_Yo = 267, // #442 + scx_Tolong_Siki = 268, // #443 + scx_Beria_Erfe = 269, // #444 + scx_Unknown = 270, // #445 + upid_max_property_number = 371, + bp_RGI_Emoji = 372, // #446 + bp_Basic_Emoji = 373, // #447 + bp_Emoji_Keycap_Sequence = 374, // #448 + bp_RGI_Emoji_Modifier_Sequence = 375, // #449 + bp_RGI_Emoji_Flag_Sequence = 376, // #450 + bp_RGI_Emoji_Tag_Sequence = 377, // #451 + bp_RGI_Emoji_ZWJ_Sequence = 378, // #452 + upid_max_pos_number = 378 +}; + +template +struct unicode_property_data +{ + static const T1 propertynumbertable[]; + static const T2 positiontable[]; + static const T3 rangetable[]; +}; + +template +const T1 unicode_property_data::propertynumbertable[] = +{ + { "", 6 }, + { "\x47\x65\x6E\x65\x72\x61\x6C\x5F\x43\x61\x74\x65\x67\x6F\x72\x79", 2 }, + { "\x53\x63\x72\x69\x70\x74", 3 }, + { "\x53\x63\x72\x69\x70\x74\x5F\x45\x78\x74\x65\x6E\x73\x69\x6F\x6E\x73", 4 }, + { "\x67\x63", 2 }, + { "\x73\x63", 3 }, + { "\x73\x63\x78", 4 }, + // gc: 80 + { "\x43", 5 }, + { "\x43\x61\x73\x65\x64\x5F\x4C\x65\x74\x74\x65\x72", 12 }, + { "\x43\x63", 6 }, + { "\x43\x66", 7 }, + { "\x43\x6C\x6F\x73\x65\x5F\x50\x75\x6E\x63\x74\x75\x61\x74\x69\x6F\x6E", 29 }, + { "\x43\x6E", 8 }, + { "\x43\x6F", 9 }, + { "\x43\x6F\x6D\x62\x69\x6E\x69\x6E\x67\x5F\x4D\x61\x72\x6B", 18 }, + { "\x43\x6F\x6E\x6E\x65\x63\x74\x6F\x72\x5F\x50\x75\x6E\x63\x74\x75\x61\x74\x69\x6F\x6E", 27 }, + { "\x43\x6F\x6E\x74\x72\x6F\x6C", 6 }, + { "\x43\x73", 10 }, + { "\x43\x75\x72\x72\x65\x6E\x63\x79\x5F\x53\x79\x6D\x62\x6F\x6C", 35 }, + { "\x44\x61\x73\x68\x5F\x50\x75\x6E\x63\x74\x75\x61\x74\x69\x6F\x6E", 28 }, + { "\x44\x65\x63\x69\x6D\x61\x6C\x5F\x4E\x75\x6D\x62\x65\x72", 23 }, + { "\x45\x6E\x63\x6C\x6F\x73\x69\x6E\x67\x5F\x4D\x61\x72\x6B", 20 }, + { "\x46\x69\x6E\x61\x6C\x5F\x50\x75\x6E\x63\x74\x75\x61\x74\x69\x6F\x6E", 30 }, + { "\x46\x6F\x72\x6D\x61\x74", 7 }, + { "\x49\x6E\x69\x74\x69\x61\x6C\x5F\x50\x75\x6E\x63\x74\x75\x61\x74\x69\x6F\x6E", 31 }, + { "\x4C", 11 }, + { "\x4C\x43", 12 }, + { "\x4C\x65\x74\x74\x65\x72", 11 }, + { "\x4C\x65\x74\x74\x65\x72\x5F\x4E\x75\x6D\x62\x65\x72", 24 }, + { "\x4C\x69\x6E\x65\x5F\x53\x65\x70\x61\x72\x61\x74\x6F\x72", 40 }, + { "\x4C\x6C", 13 }, + { "\x4C\x6D", 16 }, + { "\x4C\x6F", 17 }, + { "\x4C\x6F\x77\x65\x72\x63\x61\x73\x65\x5F\x4C\x65\x74\x74\x65\x72", 13 }, + { "\x4C\x74", 14 }, + { "\x4C\x75", 15 }, + { "\x4D", 18 }, + { "\x4D\x61\x72\x6B", 18 }, + { "\x4D\x61\x74\x68\x5F\x53\x79\x6D\x62\x6F\x6C", 37 }, + { "\x4D\x63", 19 }, + { "\x4D\x65", 20 }, + { "\x4D\x6E", 21 }, + { "\x4D\x6F\x64\x69\x66\x69\x65\x72\x5F\x4C\x65\x74\x74\x65\x72", 16 }, + { "\x4D\x6F\x64\x69\x66\x69\x65\x72\x5F\x53\x79\x6D\x62\x6F\x6C", 36 }, + { "\x4E", 22 }, + { "\x4E\x64", 23 }, + { "\x4E\x6C", 24 }, + { "\x4E\x6F", 25 }, + { "\x4E\x6F\x6E\x73\x70\x61\x63\x69\x6E\x67\x5F\x4D\x61\x72\x6B", 21 }, + { "\x4E\x75\x6D\x62\x65\x72", 22 }, + { "\x4F\x70\x65\x6E\x5F\x50\x75\x6E\x63\x74\x75\x61\x74\x69\x6F\x6E", 33 }, + { "\x4F\x74\x68\x65\x72", 5 }, + { "\x4F\x74\x68\x65\x72\x5F\x4C\x65\x74\x74\x65\x72", 17 }, + { "\x4F\x74\x68\x65\x72\x5F\x4E\x75\x6D\x62\x65\x72", 25 }, + { "\x4F\x74\x68\x65\x72\x5F\x50\x75\x6E\x63\x74\x75\x61\x74\x69\x6F\x6E", 32 }, + { "\x4F\x74\x68\x65\x72\x5F\x53\x79\x6D\x62\x6F\x6C", 38 }, + { "\x50", 26 }, + { "\x50\x61\x72\x61\x67\x72\x61\x70\x68\x5F\x53\x65\x70\x61\x72\x61\x74\x6F\x72", 41 }, + { "\x50\x63", 27 }, + { "\x50\x64", 28 }, + { "\x50\x65", 29 }, + { "\x50\x66", 30 }, + { "\x50\x69", 31 }, + { "\x50\x6F", 32 }, + { "\x50\x72\x69\x76\x61\x74\x65\x5F\x55\x73\x65", 9 }, + { "\x50\x73", 33 }, + { "\x50\x75\x6E\x63\x74\x75\x61\x74\x69\x6F\x6E", 26 }, + { "\x53", 34 }, + { "\x53\x63", 35 }, + { "\x53\x65\x70\x61\x72\x61\x74\x6F\x72", 39 }, + { "\x53\x6B", 36 }, + { "\x53\x6D", 37 }, + { "\x53\x6F", 38 }, + { "\x53\x70\x61\x63\x65\x5F\x53\x65\x70\x61\x72\x61\x74\x6F\x72", 42 }, + { "\x53\x70\x61\x63\x69\x6E\x67\x5F\x4D\x61\x72\x6B", 19 }, + { "\x53\x75\x72\x72\x6F\x67\x61\x74\x65", 10 }, + { "\x53\x79\x6D\x62\x6F\x6C", 34 }, + { "\x54\x69\x74\x6C\x65\x63\x61\x73\x65\x5F\x4C\x65\x74\x74\x65\x72", 14 }, + { "\x55\x6E\x61\x73\x73\x69\x67\x6E\x65\x64", 8 }, + { "\x55\x70\x70\x65\x72\x63\x61\x73\x65\x5F\x4C\x65\x74\x74\x65\x72", 15 }, + { "\x5A", 39 }, + { "\x5A\x6C", 40 }, + { "\x5A\x70", 41 }, + { "\x5A\x73", 42 }, + { "\x63\x6E\x74\x72\x6C", 6 }, + { "\x64\x69\x67\x69\x74", 23 }, + { "\x70\x75\x6E\x63\x74", 26 }, + // bp: 105 + { "\x41\x48\x65\x78", 44 }, + { "\x41\x53\x43\x49\x49", 43 }, + { "\x41\x53\x43\x49\x49\x5F\x48\x65\x78\x5F\x44\x69\x67\x69\x74", 44 }, + { "\x41\x6C\x70\x68\x61", 45 }, + { "\x41\x6C\x70\x68\x61\x62\x65\x74\x69\x63", 45 }, + { "\x41\x6E\x79", 46 }, + { "\x41\x73\x73\x69\x67\x6E\x65\x64", 47 }, + { "\x42\x61\x73\x69\x63\x5F\x45\x6D\x6F\x6A\x69", 373 }, + { "\x42\x69\x64\x69\x5F\x43", 48 }, + { "\x42\x69\x64\x69\x5F\x43\x6F\x6E\x74\x72\x6F\x6C", 48 }, + { "\x42\x69\x64\x69\x5F\x4D", 49 }, + { "\x42\x69\x64\x69\x5F\x4D\x69\x72\x72\x6F\x72\x65\x64", 49 }, + { "\x43\x49", 50 }, + { "\x43\x57\x43\x46", 52 }, + { "\x43\x57\x43\x4D", 53 }, + { "\x43\x57\x4B\x43\x46", 55 }, + { "\x43\x57\x4C", 54 }, + { "\x43\x57\x54", 56 }, + { "\x43\x57\x55", 57 }, + { "\x43\x61\x73\x65\x5F\x49\x67\x6E\x6F\x72\x61\x62\x6C\x65", 50 }, + { "\x43\x61\x73\x65\x64", 51 }, + { "\x43\x68\x61\x6E\x67\x65\x73\x5F\x57\x68\x65\x6E\x5F\x43\x61\x73\x65\x66\x6F\x6C\x64\x65\x64", 52 }, + { "\x43\x68\x61\x6E\x67\x65\x73\x5F\x57\x68\x65\x6E\x5F\x43\x61\x73\x65\x6D\x61\x70\x70\x65\x64", 53 }, + { "\x43\x68\x61\x6E\x67\x65\x73\x5F\x57\x68\x65\x6E\x5F\x4C\x6F\x77\x65\x72\x63\x61\x73\x65\x64", 54 }, + { "\x43\x68\x61\x6E\x67\x65\x73\x5F\x57\x68\x65\x6E\x5F\x4E\x46\x4B\x43\x5F\x43\x61\x73\x65\x66\x6F\x6C\x64\x65\x64", 55 }, + { "\x43\x68\x61\x6E\x67\x65\x73\x5F\x57\x68\x65\x6E\x5F\x54\x69\x74\x6C\x65\x63\x61\x73\x65\x64", 56 }, + { "\x43\x68\x61\x6E\x67\x65\x73\x5F\x57\x68\x65\x6E\x5F\x55\x70\x70\x65\x72\x63\x61\x73\x65\x64", 57 }, + { "\x44\x49", 59 }, + { "\x44\x61\x73\x68", 58 }, + { "\x44\x65\x66\x61\x75\x6C\x74\x5F\x49\x67\x6E\x6F\x72\x61\x62\x6C\x65\x5F\x43\x6F\x64\x65\x5F\x50\x6F\x69\x6E\x74", 59 }, + { "\x44\x65\x70", 60 }, + { "\x44\x65\x70\x72\x65\x63\x61\x74\x65\x64", 60 }, + { "\x44\x69\x61", 61 }, + { "\x44\x69\x61\x63\x72\x69\x74\x69\x63", 61 }, + { "\x45\x42\x61\x73\x65", 65 }, + { "\x45\x43\x6F\x6D\x70", 63 }, + { "\x45\x4D\x6F\x64", 64 }, + { "\x45\x50\x72\x65\x73", 66 }, + { "\x45\x6D\x6F\x6A\x69", 62 }, + { "\x45\x6D\x6F\x6A\x69\x5F\x43\x6F\x6D\x70\x6F\x6E\x65\x6E\x74", 63 }, + { "\x45\x6D\x6F\x6A\x69\x5F\x4B\x65\x79\x63\x61\x70\x5F\x53\x65\x71\x75\x65\x6E\x63\x65", 374 }, + { "\x45\x6D\x6F\x6A\x69\x5F\x4D\x6F\x64\x69\x66\x69\x65\x72", 64 }, + { "\x45\x6D\x6F\x6A\x69\x5F\x4D\x6F\x64\x69\x66\x69\x65\x72\x5F\x42\x61\x73\x65", 65 }, + { "\x45\x6D\x6F\x6A\x69\x5F\x50\x72\x65\x73\x65\x6E\x74\x61\x74\x69\x6F\x6E", 66 }, + { "\x45\x78\x74", 68 }, + { "\x45\x78\x74\x50\x69\x63\x74", 67 }, + { "\x45\x78\x74\x65\x6E\x64\x65\x64\x5F\x50\x69\x63\x74\x6F\x67\x72\x61\x70\x68\x69\x63", 67 }, + { "\x45\x78\x74\x65\x6E\x64\x65\x72", 68 }, + { "\x47\x72\x5F\x42\x61\x73\x65", 69 }, + { "\x47\x72\x5F\x45\x78\x74", 70 }, + { "\x47\x72\x61\x70\x68\x65\x6D\x65\x5F\x42\x61\x73\x65", 69 }, + { "\x47\x72\x61\x70\x68\x65\x6D\x65\x5F\x45\x78\x74\x65\x6E\x64", 70 }, + { "\x48\x65\x78", 71 }, + { "\x48\x65\x78\x5F\x44\x69\x67\x69\x74", 71 }, + { "\x49\x44\x43", 74 }, + { "\x49\x44\x53", 75 }, + { "\x49\x44\x53\x42", 72 }, + { "\x49\x44\x53\x54", 73 }, + { "\x49\x44\x53\x5F\x42\x69\x6E\x61\x72\x79\x5F\x4F\x70\x65\x72\x61\x74\x6F\x72", 72 }, + { "\x49\x44\x53\x5F\x54\x72\x69\x6E\x61\x72\x79\x5F\x4F\x70\x65\x72\x61\x74\x6F\x72", 73 }, + { "\x49\x44\x5F\x43\x6F\x6E\x74\x69\x6E\x75\x65", 74 }, + { "\x49\x44\x5F\x53\x74\x61\x72\x74", 75 }, + { "\x49\x64\x65\x6F", 76 }, + { "\x49\x64\x65\x6F\x67\x72\x61\x70\x68\x69\x63", 76 }, + { "\x4A\x6F\x69\x6E\x5F\x43", 77 }, + { "\x4A\x6F\x69\x6E\x5F\x43\x6F\x6E\x74\x72\x6F\x6C", 77 }, + { "\x4C\x4F\x45", 78 }, + { "\x4C\x6F\x67\x69\x63\x61\x6C\x5F\x4F\x72\x64\x65\x72\x5F\x45\x78\x63\x65\x70\x74\x69\x6F\x6E", 78 }, + { "\x4C\x6F\x77\x65\x72", 79 }, + { "\x4C\x6F\x77\x65\x72\x63\x61\x73\x65", 79 }, + { "\x4D\x61\x74\x68", 80 }, + { "\x4E\x43\x68\x61\x72", 81 }, + { "\x4E\x6F\x6E\x63\x68\x61\x72\x61\x63\x74\x65\x72\x5F\x43\x6F\x64\x65\x5F\x50\x6F\x69\x6E\x74", 81 }, + { "\x50\x61\x74\x5F\x53\x79\x6E", 82 }, + { "\x50\x61\x74\x5F\x57\x53", 83 }, + { "\x50\x61\x74\x74\x65\x72\x6E\x5F\x53\x79\x6E\x74\x61\x78", 82 }, + { "\x50\x61\x74\x74\x65\x72\x6E\x5F\x57\x68\x69\x74\x65\x5F\x53\x70\x61\x63\x65", 83 }, + { "\x51\x4D\x61\x72\x6B", 84 }, + { "\x51\x75\x6F\x74\x61\x74\x69\x6F\x6E\x5F\x4D\x61\x72\x6B", 84 }, + { "\x52\x47\x49\x5F\x45\x6D\x6F\x6A\x69", 372 }, + { "\x52\x47\x49\x5F\x45\x6D\x6F\x6A\x69\x5F\x46\x6C\x61\x67\x5F\x53\x65\x71\x75\x65\x6E\x63\x65", 376 }, + { "\x52\x47\x49\x5F\x45\x6D\x6F\x6A\x69\x5F\x4D\x6F\x64\x69\x66\x69\x65\x72\x5F\x53\x65\x71\x75\x65\x6E\x63\x65", 375 }, + { "\x52\x47\x49\x5F\x45\x6D\x6F\x6A\x69\x5F\x54\x61\x67\x5F\x53\x65\x71\x75\x65\x6E\x63\x65", 377 }, + { "\x52\x47\x49\x5F\x45\x6D\x6F\x6A\x69\x5F\x5A\x57\x4A\x5F\x53\x65\x71\x75\x65\x6E\x63\x65", 378 }, + { "\x52\x49", 86 }, + { "\x52\x61\x64\x69\x63\x61\x6C", 85 }, + { "\x52\x65\x67\x69\x6F\x6E\x61\x6C\x5F\x49\x6E\x64\x69\x63\x61\x74\x6F\x72", 86 }, + { "\x53\x44", 88 }, + { "\x53\x54\x65\x72\x6D", 87 }, + { "\x53\x65\x6E\x74\x65\x6E\x63\x65\x5F\x54\x65\x72\x6D\x69\x6E\x61\x6C", 87 }, + { "\x53\x6F\x66\x74\x5F\x44\x6F\x74\x74\x65\x64", 88 }, + { "\x54\x65\x72\x6D", 89 }, + { "\x54\x65\x72\x6D\x69\x6E\x61\x6C\x5F\x50\x75\x6E\x63\x74\x75\x61\x74\x69\x6F\x6E", 89 }, + { "\x55\x49\x64\x65\x6F", 90 }, + { "\x55\x6E\x69\x66\x69\x65\x64\x5F\x49\x64\x65\x6F\x67\x72\x61\x70\x68", 90 }, + { "\x55\x70\x70\x65\x72", 91 }, + { "\x55\x70\x70\x65\x72\x63\x61\x73\x65", 91 }, + { "\x56\x53", 92 }, + { "\x56\x61\x72\x69\x61\x74\x69\x6F\x6E\x5F\x53\x65\x6C\x65\x63\x74\x6F\x72", 92 }, + { "\x57\x68\x69\x74\x65\x5F\x53\x70\x61\x63\x65", 93 }, + { "\x58\x49\x44\x43", 94 }, + { "\x58\x49\x44\x53", 95 }, + { "\x58\x49\x44\x5F\x43\x6F\x6E\x74\x69\x6E\x75\x65", 94 }, + { "\x58\x49\x44\x5F\x53\x74\x61\x72\x74", 95 }, + { "\x73\x70\x61\x63\x65", 93 }, + // sc: 344 + { "\x41\x64\x6C\x61\x6D", 227 }, + { "\x41\x64\x6C\x6D", 227 }, + { "\x41\x67\x68\x62", 198 }, + { "\x41\x68\x6F\x6D", 221 }, + { "\x41\x6E\x61\x74\x6F\x6C\x69\x61\x6E\x5F\x48\x69\x65\x72\x6F\x67\x6C\x79\x70\x68\x73", 222 }, + { "\x41\x72\x61\x62", 102 }, + { "\x41\x72\x61\x62\x69\x63", 102 }, + { "\x41\x72\x6D\x65\x6E\x69\x61\x6E", 100 }, + { "\x41\x72\x6D\x69", 182 }, + { "\x41\x72\x6D\x6E", 100 }, + { "\x41\x76\x65\x73\x74\x61\x6E", 175 }, + { "\x41\x76\x73\x74", 175 }, + { "\x42\x61\x6C\x69", 157 }, + { "\x42\x61\x6C\x69\x6E\x65\x73\x65", 157 }, + { "\x42\x61\x6D\x75", 179 }, + { "\x42\x61\x6D\x75\x6D", 179 }, + { "\x42\x61\x73\x73", 199 }, + { "\x42\x61\x73\x73\x61\x5F\x56\x61\x68", 199 }, + { "\x42\x61\x74\x61\x6B", 188 }, + { "\x42\x61\x74\x6B", 188 }, + { "\x42\x65\x6E\x67", 106 }, + { "\x42\x65\x6E\x67\x61\x6C\x69", 106 }, + { "\x42\x65\x72\x66", 269 }, + { "\x42\x65\x72\x69\x61\x5F\x45\x72\x66\x65", 269 }, + { "\x42\x68\x61\x69\x6B\x73\x75\x6B\x69", 228 }, + { "\x42\x68\x6B\x73", 228 }, + { "\x42\x6F\x70\x6F", 130 }, + { "\x42\x6F\x70\x6F\x6D\x6F\x66\x6F", 130 }, + { "\x42\x72\x61\x68", 189 }, + { "\x42\x72\x61\x68\x6D\x69", 189 }, + { "\x42\x72\x61\x69", 148 }, + { "\x42\x72\x61\x69\x6C\x6C\x65", 148 }, + { "\x42\x75\x67\x69", 149 }, + { "\x42\x75\x67\x69\x6E\x65\x73\x65", 149 }, + { "\x42\x75\x68\x64", 139 }, + { "\x42\x75\x68\x69\x64", 139 }, + { "\x43\x61\x6B\x6D", 191 }, + { "\x43\x61\x6E\x61\x64\x69\x61\x6E\x5F\x41\x62\x6F\x72\x69\x67\x69\x6E\x61\x6C", 123 }, + { "\x43\x61\x6E\x73", 123 }, + { "\x43\x61\x72\x69", 170 }, + { "\x43\x61\x72\x69\x61\x6E", 170 }, + { "\x43\x61\x75\x63\x61\x73\x69\x61\x6E\x5F\x41\x6C\x62\x61\x6E\x69\x61\x6E", 198 }, + { "\x43\x68\x61\x6B\x6D\x61", 191 }, + { "\x43\x68\x61\x6D", 172 }, + { "\x43\x68\x65\x72", 122 }, + { "\x43\x68\x65\x72\x6F\x6B\x65\x65", 122 }, + { "\x43\x68\x6F\x72\x61\x73\x6D\x69\x61\x6E", 248 }, + { "\x43\x68\x72\x73", 248 }, + { "\x43\x6F\x6D\x6D\x6F\x6E", 96 }, + { "\x43\x6F\x70\x74", 150 }, + { "\x43\x6F\x70\x74\x69\x63", 150 }, + { "\x43\x70\x6D\x6E", 252 }, + { "\x43\x70\x72\x74", 147 }, + { "\x43\x75\x6E\x65\x69\x66\x6F\x72\x6D", 158 }, + { "\x43\x79\x70\x72\x69\x6F\x74", 147 }, + { "\x43\x79\x70\x72\x6F\x5F\x4D\x69\x6E\x6F\x61\x6E", 252 }, + { "\x43\x79\x72\x69\x6C\x6C\x69\x63", 99 }, + { "\x43\x79\x72\x6C", 99 }, + { "\x44\x65\x73\x65\x72\x65\x74", 135 }, + { "\x44\x65\x76\x61", 105 }, + { "\x44\x65\x76\x61\x6E\x61\x67\x61\x72\x69", 105 }, + { "\x44\x69\x61\x6B", 249 }, + { "\x44\x69\x76\x65\x73\x5F\x41\x6B\x75\x72\x75", 249 }, + { "\x44\x6F\x67\x72", 237 }, + { "\x44\x6F\x67\x72\x61", 237 }, + { "\x44\x73\x72\x74", 135 }, + { "\x44\x75\x70\x6C", 200 }, + { "\x44\x75\x70\x6C\x6F\x79\x61\x6E", 200 }, + { "\x45\x67\x79\x70", 176 }, + { "\x45\x67\x79\x70\x74\x69\x61\x6E\x5F\x48\x69\x65\x72\x6F\x67\x6C\x79\x70\x68\x73", 176 }, + { "\x45\x6C\x62\x61", 201 }, + { "\x45\x6C\x62\x61\x73\x61\x6E", 201 }, + { "\x45\x6C\x79\x6D", 244 }, + { "\x45\x6C\x79\x6D\x61\x69\x63", 244 }, + { "\x45\x74\x68\x69", 121 }, + { "\x45\x74\x68\x69\x6F\x70\x69\x63", 121 }, + { "\x47\x61\x72\x61", 259 }, + { "\x47\x61\x72\x61\x79", 259 }, + { "\x47\x65\x6F\x72", 119 }, + { "\x47\x65\x6F\x72\x67\x69\x61\x6E", 119 }, + { "\x47\x6C\x61\x67", 152 }, + { "\x47\x6C\x61\x67\x6F\x6C\x69\x74\x69\x63", 152 }, + { "\x47\x6F\x6E\x67", 238 }, + { "\x47\x6F\x6E\x6D", 233 }, + { "\x47\x6F\x74\x68", 134 }, + { "\x47\x6F\x74\x68\x69\x63", 134 }, + { "\x47\x72\x61\x6E", 202 }, + { "\x47\x72\x61\x6E\x74\x68\x61", 202 }, + { "\x47\x72\x65\x65\x6B", 98 }, + { "\x47\x72\x65\x6B", 98 }, + { "\x47\x75\x6A\x61\x72\x61\x74\x69", 108 }, + { "\x47\x75\x6A\x72", 108 }, + { "\x47\x75\x6B\x68", 260 }, + { "\x47\x75\x6E\x6A\x61\x6C\x61\x5F\x47\x6F\x6E\x64\x69", 238 }, + { "\x47\x75\x72\x6D\x75\x6B\x68\x69", 107 }, + { "\x47\x75\x72\x75", 107 }, + { "\x47\x75\x72\x75\x6E\x67\x5F\x4B\x68\x65\x6D\x61", 260 }, + { "\x48\x61\x6E", 131 }, + { "\x48\x61\x6E\x67", 120 }, + { "\x48\x61\x6E\x67\x75\x6C", 120 }, + { "\x48\x61\x6E\x69", 131 }, + { "\x48\x61\x6E\x69\x66\x69\x5F\x52\x6F\x68\x69\x6E\x67\x79\x61", 241 }, + { "\x48\x61\x6E\x6F", 138 }, + { "\x48\x61\x6E\x75\x6E\x6F\x6F", 138 }, + { "\x48\x61\x74\x72", 223 }, + { "\x48\x61\x74\x72\x61\x6E", 223 }, + { "\x48\x65\x62\x72", 101 }, + { "\x48\x65\x62\x72\x65\x77", 101 }, + { "\x48\x69\x72\x61", 128 }, + { "\x48\x69\x72\x61\x67\x61\x6E\x61", 128 }, + { "\x48\x6C\x75\x77", 222 }, + { "\x48\x6D\x6E\x67", 203 }, + { "\x48\x6D\x6E\x70", 246 }, + { "\x48\x75\x6E\x67", 225 }, + { "\x49\x6D\x70\x65\x72\x69\x61\x6C\x5F\x41\x72\x61\x6D\x61\x69\x63", 182 }, + { "\x49\x6E\x68\x65\x72\x69\x74\x65\x64", 136 }, + { "\x49\x6E\x73\x63\x72\x69\x70\x74\x69\x6F\x6E\x61\x6C\x5F\x50\x61\x68\x6C\x61\x76\x69", 185 }, + { "\x49\x6E\x73\x63\x72\x69\x70\x74\x69\x6F\x6E\x61\x6C\x5F\x50\x61\x72\x74\x68\x69\x61\x6E", 184 }, + { "\x49\x74\x61\x6C", 133 }, + { "\x4A\x61\x76\x61", 180 }, + { "\x4A\x61\x76\x61\x6E\x65\x73\x65", 180 }, + { "\x4B\x61\x69\x74\x68\x69", 187 }, + { "\x4B\x61\x6C\x69", 167 }, + { "\x4B\x61\x6E\x61", 129 }, + { "\x4B\x61\x6E\x6E\x61\x64\x61", 112 }, + { "\x4B\x61\x74\x61\x6B\x61\x6E\x61", 129 }, + { "\x4B\x61\x77\x69", 257 }, + { "\x4B\x61\x79\x61\x68\x5F\x4C\x69", 167 }, + { "\x4B\x68\x61\x72", 156 }, + { "\x4B\x68\x61\x72\x6F\x73\x68\x74\x68\x69", 156 }, + { "\x4B\x68\x69\x74\x61\x6E\x5F\x53\x6D\x61\x6C\x6C\x5F\x53\x63\x72\x69\x70\x74", 250 }, + { "\x4B\x68\x6D\x65\x72", 126 }, + { "\x4B\x68\x6D\x72", 126 }, + { "\x4B\x68\x6F\x6A", 204 }, + { "\x4B\x68\x6F\x6A\x6B\x69", 204 }, + { "\x4B\x68\x75\x64\x61\x77\x61\x64\x69", 218 }, + { "\x4B\x69\x72\x61\x74\x5F\x52\x61\x69", 261 }, + { "\x4B\x69\x74\x73", 250 }, + { "\x4B\x6E\x64\x61", 112 }, + { "\x4B\x72\x61\x69", 261 }, + { "\x4B\x74\x68\x69", 187 }, + { "\x4C\x61\x6E\x61", 173 }, + { "\x4C\x61\x6F", 116 }, + { "\x4C\x61\x6F\x6F", 116 }, + { "\x4C\x61\x74\x69\x6E", 97 }, + { "\x4C\x61\x74\x6E", 97 }, + { "\x4C\x65\x70\x63", 163 }, + { "\x4C\x65\x70\x63\x68\x61", 163 }, + { "\x4C\x69\x6D\x62", 141 }, + { "\x4C\x69\x6D\x62\x75", 141 }, + { "\x4C\x69\x6E\x61", 205 }, + { "\x4C\x69\x6E\x62", 143 }, + { "\x4C\x69\x6E\x65\x61\x72\x5F\x41", 205 }, + { "\x4C\x69\x6E\x65\x61\x72\x5F\x42", 143 }, + { "\x4C\x69\x73\x75", 178 }, + { "\x4C\x79\x63\x69", 169 }, + { "\x4C\x79\x63\x69\x61\x6E", 169 }, + { "\x4C\x79\x64\x69", 171 }, + { "\x4C\x79\x64\x69\x61\x6E", 171 }, + { "\x4D\x61\x68\x61\x6A\x61\x6E\x69", 206 }, + { "\x4D\x61\x68\x6A", 206 }, + { "\x4D\x61\x6B\x61", 239 }, + { "\x4D\x61\x6B\x61\x73\x61\x72", 239 }, + { "\x4D\x61\x6C\x61\x79\x61\x6C\x61\x6D", 113 }, + { "\x4D\x61\x6E\x64", 190 }, + { "\x4D\x61\x6E\x64\x61\x69\x63", 190 }, + { "\x4D\x61\x6E\x69", 207 }, + { "\x4D\x61\x6E\x69\x63\x68\x61\x65\x61\x6E", 207 }, + { "\x4D\x61\x72\x63", 229 }, + { "\x4D\x61\x72\x63\x68\x65\x6E", 229 }, + { "\x4D\x61\x73\x61\x72\x61\x6D\x5F\x47\x6F\x6E\x64\x69", 233 }, + { "\x4D\x65\x64\x65\x66\x61\x69\x64\x72\x69\x6E", 240 }, + { "\x4D\x65\x64\x66", 240 }, + { "\x4D\x65\x65\x74\x65\x69\x5F\x4D\x61\x79\x65\x6B", 181 }, + { "\x4D\x65\x6E\x64", 208 }, + { "\x4D\x65\x6E\x64\x65\x5F\x4B\x69\x6B\x61\x6B\x75\x69", 208 }, + { "\x4D\x65\x72\x63", 192 }, + { "\x4D\x65\x72\x6F", 193 }, + { "\x4D\x65\x72\x6F\x69\x74\x69\x63\x5F\x43\x75\x72\x73\x69\x76\x65", 192 }, + { "\x4D\x65\x72\x6F\x69\x74\x69\x63\x5F\x48\x69\x65\x72\x6F\x67\x6C\x79\x70\x68\x73", 193 }, + { "\x4D\x69\x61\x6F", 194 }, + { "\x4D\x6C\x79\x6D", 113 }, + { "\x4D\x6F\x64\x69", 209 }, + { "\x4D\x6F\x6E\x67", 127 }, + { "\x4D\x6F\x6E\x67\x6F\x6C\x69\x61\x6E", 127 }, + { "\x4D\x72\x6F", 210 }, + { "\x4D\x72\x6F\x6F", 210 }, + { "\x4D\x74\x65\x69", 181 }, + { "\x4D\x75\x6C\x74", 224 }, + { "\x4D\x75\x6C\x74\x61\x6E\x69", 224 }, + { "\x4D\x79\x61\x6E\x6D\x61\x72", 118 }, + { "\x4D\x79\x6D\x72", 118 }, + { "\x4E\x61\x62\x61\x74\x61\x65\x61\x6E", 212 }, + { "\x4E\x61\x67\x5F\x4D\x75\x6E\x64\x61\x72\x69", 258 }, + { "\x4E\x61\x67\x6D", 258 }, + { "\x4E\x61\x6E\x64", 245 }, + { "\x4E\x61\x6E\x64\x69\x6E\x61\x67\x61\x72\x69", 245 }, + { "\x4E\x61\x72\x62", 211 }, + { "\x4E\x62\x61\x74", 212 }, + { "\x4E\x65\x77\x5F\x54\x61\x69\x5F\x4C\x75\x65", 151 }, + { "\x4E\x65\x77\x61", 230 }, + { "\x4E\x6B\x6F", 161 }, + { "\x4E\x6B\x6F\x6F", 161 }, + { "\x4E\x73\x68\x75", 234 }, + { "\x4E\x75\x73\x68\x75", 234 }, + { "\x4E\x79\x69\x61\x6B\x65\x6E\x67\x5F\x50\x75\x61\x63\x68\x75\x65\x5F\x48\x6D\x6F\x6E\x67", 246 }, + { "\x4F\x67\x61\x6D", 124 }, + { "\x4F\x67\x68\x61\x6D", 124 }, + { "\x4F\x6C\x5F\x43\x68\x69\x6B\x69", 164 }, + { "\x4F\x6C\x5F\x4F\x6E\x61\x6C", 262 }, + { "\x4F\x6C\x63\x6B", 164 }, + { "\x4F\x6C\x64\x5F\x48\x75\x6E\x67\x61\x72\x69\x61\x6E", 225 }, + { "\x4F\x6C\x64\x5F\x49\x74\x61\x6C\x69\x63", 133 }, + { "\x4F\x6C\x64\x5F\x4E\x6F\x72\x74\x68\x5F\x41\x72\x61\x62\x69\x61\x6E", 211 }, + { "\x4F\x6C\x64\x5F\x50\x65\x72\x6D\x69\x63", 215 }, + { "\x4F\x6C\x64\x5F\x50\x65\x72\x73\x69\x61\x6E", 155 }, + { "\x4F\x6C\x64\x5F\x53\x6F\x67\x64\x69\x61\x6E", 243 }, + { "\x4F\x6C\x64\x5F\x53\x6F\x75\x74\x68\x5F\x41\x72\x61\x62\x69\x61\x6E", 183 }, + { "\x4F\x6C\x64\x5F\x54\x75\x72\x6B\x69\x63", 186 }, + { "\x4F\x6C\x64\x5F\x55\x79\x67\x68\x75\x72", 253 }, + { "\x4F\x6E\x61\x6F", 262 }, + { "\x4F\x72\x69\x79\x61", 109 }, + { "\x4F\x72\x6B\x68", 186 }, + { "\x4F\x72\x79\x61", 109 }, + { "\x4F\x73\x61\x67\x65", 231 }, + { "\x4F\x73\x67\x65", 231 }, + { "\x4F\x73\x6D\x61", 146 }, + { "\x4F\x73\x6D\x61\x6E\x79\x61", 146 }, + { "\x4F\x75\x67\x72", 253 }, + { "\x50\x61\x68\x61\x77\x68\x5F\x48\x6D\x6F\x6E\x67", 203 }, + { "\x50\x61\x6C\x6D", 213 }, + { "\x50\x61\x6C\x6D\x79\x72\x65\x6E\x65", 213 }, + { "\x50\x61\x75\x5F\x43\x69\x6E\x5F\x48\x61\x75", 214 }, + { "\x50\x61\x75\x63", 214 }, + { "\x50\x65\x72\x6D", 215 }, + { "\x50\x68\x61\x67", 160 }, + { "\x50\x68\x61\x67\x73\x5F\x50\x61", 160 }, + { "\x50\x68\x6C\x69", 185 }, + { "\x50\x68\x6C\x70", 216 }, + { "\x50\x68\x6E\x78", 159 }, + { "\x50\x68\x6F\x65\x6E\x69\x63\x69\x61\x6E", 159 }, + { "\x50\x6C\x72\x64", 194 }, + { "\x50\x72\x74\x69", 184 }, + { "\x50\x73\x61\x6C\x74\x65\x72\x5F\x50\x61\x68\x6C\x61\x76\x69", 216 }, + { "\x51\x61\x61\x63", 150 }, + { "\x51\x61\x61\x69", 136 }, + { "\x52\x65\x6A\x61\x6E\x67", 168 }, + { "\x52\x6A\x6E\x67", 168 }, + { "\x52\x6F\x68\x67", 241 }, + { "\x52\x75\x6E\x69\x63", 125 }, + { "\x52\x75\x6E\x72", 125 }, + { "\x53\x61\x6D\x61\x72\x69\x74\x61\x6E", 177 }, + { "\x53\x61\x6D\x72", 177 }, + { "\x53\x61\x72\x62", 183 }, + { "\x53\x61\x75\x72", 166 }, + { "\x53\x61\x75\x72\x61\x73\x68\x74\x72\x61", 166 }, + { "\x53\x67\x6E\x77", 226 }, + { "\x53\x68\x61\x72\x61\x64\x61", 195 }, + { "\x53\x68\x61\x76\x69\x61\x6E", 145 }, + { "\x53\x68\x61\x77", 145 }, + { "\x53\x68\x72\x64", 195 }, + { "\x53\x69\x64\x64", 217 }, + { "\x53\x69\x64\x64\x68\x61\x6D", 217 }, + { "\x53\x69\x64\x65\x74\x69\x63", 266 }, + { "\x53\x69\x64\x74", 266 }, + { "\x53\x69\x67\x6E\x57\x72\x69\x74\x69\x6E\x67", 226 }, + { "\x53\x69\x6E\x64", 218 }, + { "\x53\x69\x6E\x68", 114 }, + { "\x53\x69\x6E\x68\x61\x6C\x61", 114 }, + { "\x53\x6F\x67\x64", 242 }, + { "\x53\x6F\x67\x64\x69\x61\x6E", 242 }, + { "\x53\x6F\x67\x6F", 243 }, + { "\x53\x6F\x72\x61", 196 }, + { "\x53\x6F\x72\x61\x5F\x53\x6F\x6D\x70\x65\x6E\x67", 196 }, + { "\x53\x6F\x79\x6F", 235 }, + { "\x53\x6F\x79\x6F\x6D\x62\x6F", 235 }, + { "\x53\x75\x6E\x64", 162 }, + { "\x53\x75\x6E\x64\x61\x6E\x65\x73\x65", 162 }, + { "\x53\x75\x6E\x75", 263 }, + { "\x53\x75\x6E\x75\x77\x61\x72", 263 }, + { "\x53\x79\x6C\x6F", 154 }, + { "\x53\x79\x6C\x6F\x74\x69\x5F\x4E\x61\x67\x72\x69", 154 }, + { "\x53\x79\x72\x63", 103 }, + { "\x53\x79\x72\x69\x61\x63", 103 }, + { "\x54\x61\x67\x61\x6C\x6F\x67", 137 }, + { "\x54\x61\x67\x62", 140 }, + { "\x54\x61\x67\x62\x61\x6E\x77\x61", 140 }, + { "\x54\x61\x69\x5F\x4C\x65", 142 }, + { "\x54\x61\x69\x5F\x54\x68\x61\x6D", 173 }, + { "\x54\x61\x69\x5F\x56\x69\x65\x74", 174 }, + { "\x54\x61\x69\x5F\x59\x6F", 267 }, + { "\x54\x61\x6B\x72", 197 }, + { "\x54\x61\x6B\x72\x69", 197 }, + { "\x54\x61\x6C\x65", 142 }, + { "\x54\x61\x6C\x75", 151 }, + { "\x54\x61\x6D\x69\x6C", 110 }, + { "\x54\x61\x6D\x6C", 110 }, + { "\x54\x61\x6E\x67", 232 }, + { "\x54\x61\x6E\x67\x73\x61", 254 }, + { "\x54\x61\x6E\x67\x75\x74", 232 }, + { "\x54\x61\x76\x74", 174 }, + { "\x54\x61\x79\x6F", 267 }, + { "\x54\x65\x6C\x75", 111 }, + { "\x54\x65\x6C\x75\x67\x75", 111 }, + { "\x54\x66\x6E\x67", 153 }, + { "\x54\x67\x6C\x67", 137 }, + { "\x54\x68\x61\x61", 104 }, + { "\x54\x68\x61\x61\x6E\x61", 104 }, + { "\x54\x68\x61\x69", 115 }, + { "\x54\x69\x62\x65\x74\x61\x6E", 117 }, + { "\x54\x69\x62\x74", 117 }, + { "\x54\x69\x66\x69\x6E\x61\x67\x68", 153 }, + { "\x54\x69\x72\x68", 219 }, + { "\x54\x69\x72\x68\x75\x74\x61", 219 }, + { "\x54\x6E\x73\x61", 254 }, + { "\x54\x6F\x64\x68\x72\x69", 264 }, + { "\x54\x6F\x64\x72", 264 }, + { "\x54\x6F\x6C\x6F\x6E\x67\x5F\x53\x69\x6B\x69", 268 }, + { "\x54\x6F\x6C\x73", 268 }, + { "\x54\x6F\x74\x6F", 255 }, + { "\x54\x75\x6C\x75\x5F\x54\x69\x67\x61\x6C\x61\x72\x69", 265 }, + { "\x54\x75\x74\x67", 265 }, + { "\x55\x67\x61\x72", 144 }, + { "\x55\x67\x61\x72\x69\x74\x69\x63", 144 }, + { "\x55\x6E\x6B\x6E\x6F\x77\x6E", 270 }, + { "\x56\x61\x69", 165 }, + { "\x56\x61\x69\x69", 165 }, + { "\x56\x69\x74\x68", 256 }, + { "\x56\x69\x74\x68\x6B\x75\x71\x69", 256 }, + { "\x57\x61\x6E\x63\x68\x6F", 247 }, + { "\x57\x61\x72\x61", 220 }, + { "\x57\x61\x72\x61\x6E\x67\x5F\x43\x69\x74\x69", 220 }, + { "\x57\x63\x68\x6F", 247 }, + { "\x58\x70\x65\x6F", 155 }, + { "\x58\x73\x75\x78", 158 }, + { "\x59\x65\x7A\x69", 251 }, + { "\x59\x65\x7A\x69\x64\x69", 251 }, + { "\x59\x69", 132 }, + { "\x59\x69\x69\x69", 132 }, + { "\x5A\x61\x6E\x61\x62\x61\x7A\x61\x72\x5F\x53\x71\x75\x61\x72\x65", 236 }, + { "\x5A\x61\x6E\x62", 236 }, + { "\x5A\x69\x6E\x68", 136 }, + { "\x5A\x79\x79\x79", 96 }, + { "\x5A\x7A\x7A\x7A", 270 }, + // scx: 344 + { "\x41\x64\x6C\x61\x6D", 352 }, + { "\x41\x64\x6C\x6D", 352 }, + { "\x41\x67\x68\x62", 337 }, + { "\x41\x68\x6F\x6D", 221 }, + { "\x41\x6E\x61\x74\x6F\x6C\x69\x61\x6E\x5F\x48\x69\x65\x72\x6F\x67\x6C\x79\x70\x68\x73", 222 }, + { "\x41\x72\x61\x62", 277 }, + { "\x41\x72\x61\x62\x69\x63", 277 }, + { "\x41\x72\x6D\x65\x6E\x69\x61\x6E", 275 }, + { "\x41\x72\x6D\x69", 182 }, + { "\x41\x72\x6D\x6E", 275 }, + { "\x41\x76\x65\x73\x74\x61\x6E", 326 }, + { "\x41\x76\x73\x74", 326 }, + { "\x42\x61\x6C\x69", 157 }, + { "\x42\x61\x6C\x69\x6E\x65\x73\x65", 157 }, + { "\x42\x61\x6D\x75", 179 }, + { "\x42\x61\x6D\x75\x6D", 179 }, + { "\x42\x61\x73\x73", 199 }, + { "\x42\x61\x73\x73\x61\x5F\x56\x61\x68", 199 }, + { "\x42\x61\x74\x61\x6B", 188 }, + { "\x42\x61\x74\x6B", 188 }, + { "\x42\x65\x6E\x67", 281 }, + { "\x42\x65\x6E\x67\x61\x6C\x69", 281 }, + { "\x42\x65\x72\x66", 269 }, + { "\x42\x65\x72\x69\x61\x5F\x45\x72\x66\x65", 269 }, + { "\x42\x68\x61\x69\x6B\x73\x75\x6B\x69", 228 }, + { "\x42\x68\x6B\x73", 228 }, + { "\x42\x6F\x70\x6F", 301 }, + { "\x42\x6F\x70\x6F\x6D\x6F\x66\x6F", 301 }, + { "\x42\x72\x61\x68", 189 }, + { "\x42\x72\x61\x68\x6D\x69", 189 }, + { "\x42\x72\x61\x69", 148 }, + { "\x42\x72\x61\x69\x6C\x6C\x65", 148 }, + { "\x42\x75\x67\x69", 315 }, + { "\x42\x75\x67\x69\x6E\x65\x73\x65", 315 }, + { "\x42\x75\x68\x64", 308 }, + { "\x42\x75\x68\x69\x64", 308 }, + { "\x43\x61\x6B\x6D", 333 }, + { "\x43\x61\x6E\x61\x64\x69\x61\x6E\x5F\x41\x62\x6F\x72\x69\x67\x69\x6E\x61\x6C", 123 }, + { "\x43\x61\x6E\x73", 123 }, + { "\x43\x61\x72\x69", 324 }, + { "\x43\x61\x72\x69\x61\x6E", 324 }, + { "\x43\x61\x75\x63\x61\x73\x69\x61\x6E\x5F\x41\x6C\x62\x61\x6E\x69\x61\x6E", 337 }, + { "\x43\x68\x61\x6B\x6D\x61", 333 }, + { "\x43\x68\x61\x6D", 172 }, + { "\x43\x68\x65\x72", 296 }, + { "\x43\x68\x65\x72\x6F\x6B\x65\x65", 296 }, + { "\x43\x68\x6F\x72\x61\x73\x6D\x69\x61\x6E", 248 }, + { "\x43\x68\x72\x73", 248 }, + { "\x43\x6F\x6D\x6D\x6F\x6E", 271 }, + { "\x43\x6F\x70\x74", 316 }, + { "\x43\x6F\x70\x74\x69\x63", 316 }, + { "\x43\x70\x6D\x6E", 363 }, + { "\x43\x70\x72\x74", 314 }, + { "\x43\x75\x6E\x65\x69\x66\x6F\x72\x6D", 158 }, + { "\x43\x79\x70\x72\x69\x6F\x74", 314 }, + { "\x43\x79\x70\x72\x6F\x5F\x4D\x69\x6E\x6F\x61\x6E", 363 }, + { "\x43\x79\x72\x69\x6C\x6C\x69\x63", 274 }, + { "\x43\x79\x72\x6C", 274 }, + { "\x44\x65\x73\x65\x72\x65\x74", 135 }, + { "\x44\x65\x76\x61", 280 }, + { "\x44\x65\x76\x61\x6E\x61\x67\x61\x72\x69", 280 }, + { "\x44\x69\x61\x6B", 249 }, + { "\x44\x69\x76\x65\x73\x5F\x41\x6B\x75\x72\x75", 249 }, + { "\x44\x6F\x67\x72", 357 }, + { "\x44\x6F\x67\x72\x61", 357 }, + { "\x44\x73\x72\x74", 135 }, + { "\x44\x75\x70\x6C", 338 }, + { "\x44\x75\x70\x6C\x6F\x79\x61\x6E", 338 }, + { "\x45\x67\x79\x70", 176 }, + { "\x45\x67\x79\x70\x74\x69\x61\x6E\x5F\x48\x69\x65\x72\x6F\x67\x6C\x79\x70\x68\x73", 176 }, + { "\x45\x6C\x62\x61", 339 }, + { "\x45\x6C\x62\x61\x73\x61\x6E", 339 }, + { "\x45\x6C\x79\x6D", 244 }, + { "\x45\x6C\x79\x6D\x61\x69\x63", 244 }, + { "\x45\x74\x68\x69", 295 }, + { "\x45\x74\x68\x69\x6F\x70\x69\x63", 295 }, + { "\x47\x61\x72\x61", 366 }, + { "\x47\x61\x72\x61\x79", 366 }, + { "\x47\x65\x6F\x72", 293 }, + { "\x47\x65\x6F\x72\x67\x69\x61\x6E", 293 }, + { "\x47\x6C\x61\x67", 317 }, + { "\x47\x6C\x61\x67\x6F\x6C\x69\x74\x69\x63", 317 }, + { "\x47\x6F\x6E\x67", 358 }, + { "\x47\x6F\x6E\x6D", 356 }, + { "\x47\x6F\x74\x68", 304 }, + { "\x47\x6F\x74\x68\x69\x63", 304 }, + { "\x47\x72\x61\x6E", 340 }, + { "\x47\x72\x61\x6E\x74\x68\x61", 340 }, + { "\x47\x72\x65\x65\x6B", 273 }, + { "\x47\x72\x65\x6B", 273 }, + { "\x47\x75\x6A\x61\x72\x61\x74\x69", 283 }, + { "\x47\x75\x6A\x72", 283 }, + { "\x47\x75\x6B\x68", 367 }, + { "\x47\x75\x6E\x6A\x61\x6C\x61\x5F\x47\x6F\x6E\x64\x69", 358 }, + { "\x47\x75\x72\x6D\x75\x6B\x68\x69", 282 }, + { "\x47\x75\x72\x75", 282 }, + { "\x47\x75\x72\x75\x6E\x67\x5F\x4B\x68\x65\x6D\x61", 367 }, + { "\x48\x61\x6E", 302 }, + { "\x48\x61\x6E\x67", 294 }, + { "\x48\x61\x6E\x67\x75\x6C", 294 }, + { "\x48\x61\x6E\x69", 302 }, + { "\x48\x61\x6E\x69\x66\x69\x5F\x52\x6F\x68\x69\x6E\x67\x79\x61", 359 }, + { "\x48\x61\x6E\x6F", 307 }, + { "\x48\x61\x6E\x75\x6E\x6F\x6F", 307 }, + { "\x48\x61\x74\x72", 223 }, + { "\x48\x61\x74\x72\x61\x6E", 223 }, + { "\x48\x65\x62\x72", 276 }, + { "\x48\x65\x62\x72\x65\x77", 276 }, + { "\x48\x69\x72\x61", 299 }, + { "\x48\x69\x72\x61\x67\x61\x6E\x61", 299 }, + { "\x48\x6C\x75\x77", 222 }, + { "\x48\x6D\x6E\x67", 203 }, + { "\x48\x6D\x6E\x70", 246 }, + { "\x48\x75\x6E\x67", 351 }, + { "\x49\x6D\x70\x65\x72\x69\x61\x6C\x5F\x41\x72\x61\x6D\x61\x69\x63", 182 }, + { "\x49\x6E\x68\x65\x72\x69\x74\x65\x64", 305 }, + { "\x49\x6E\x73\x63\x72\x69\x70\x74\x69\x6F\x6E\x61\x6C\x5F\x50\x61\x68\x6C\x61\x76\x69", 185 }, + { "\x49\x6E\x73\x63\x72\x69\x70\x74\x69\x6F\x6E\x61\x6C\x5F\x50\x61\x72\x74\x68\x69\x61\x6E", 184 }, + { "\x49\x74\x61\x6C", 133 }, + { "\x4A\x61\x76\x61", 329 }, + { "\x4A\x61\x76\x61\x6E\x65\x73\x65", 329 }, + { "\x4B\x61\x69\x74\x68\x69", 331 }, + { "\x4B\x61\x6C\x69", 322 }, + { "\x4B\x61\x6E\x61", 300 }, + { "\x4B\x61\x6E\x6E\x61\x64\x61", 287 }, + { "\x4B\x61\x74\x61\x6B\x61\x6E\x61", 300 }, + { "\x4B\x61\x77\x69", 257 }, + { "\x4B\x61\x79\x61\x68\x5F\x4C\x69", 322 }, + { "\x4B\x68\x61\x72", 156 }, + { "\x4B\x68\x61\x72\x6F\x73\x68\x74\x68\x69", 156 }, + { "\x4B\x68\x69\x74\x61\x6E\x5F\x53\x6D\x61\x6C\x6C\x5F\x53\x63\x72\x69\x70\x74", 250 }, + { "\x4B\x68\x6D\x65\x72", 126 }, + { "\x4B\x68\x6D\x72", 126 }, + { "\x4B\x68\x6F\x6A", 341 }, + { "\x4B\x68\x6F\x6A\x6B\x69", 341 }, + { "\x4B\x68\x75\x64\x61\x77\x61\x64\x69", 348 }, + { "\x4B\x69\x72\x61\x74\x5F\x52\x61\x69", 261 }, + { "\x4B\x69\x74\x73", 250 }, + { "\x4B\x6E\x64\x61", 287 }, + { "\x4B\x72\x61\x69", 261 }, + { "\x4B\x74\x68\x69", 331 }, + { "\x4C\x61\x6E\x61", 173 }, + { "\x4C\x61\x6F", 116 }, + { "\x4C\x61\x6F\x6F", 116 }, + { "\x4C\x61\x74\x69\x6E", 272 }, + { "\x4C\x61\x74\x6E", 272 }, + { "\x4C\x65\x70\x63", 163 }, + { "\x4C\x65\x70\x63\x68\x61", 163 }, + { "\x4C\x69\x6D\x62", 310 }, + { "\x4C\x69\x6D\x62\x75", 310 }, + { "\x4C\x69\x6E\x61", 342 }, + { "\x4C\x69\x6E\x62", 312 }, + { "\x4C\x69\x6E\x65\x61\x72\x5F\x41", 342 }, + { "\x4C\x69\x6E\x65\x61\x72\x5F\x42", 312 }, + { "\x4C\x69\x73\x75", 328 }, + { "\x4C\x79\x63\x69", 323 }, + { "\x4C\x79\x63\x69\x61\x6E", 323 }, + { "\x4C\x79\x64\x69", 325 }, + { "\x4C\x79\x64\x69\x61\x6E", 325 }, + { "\x4D\x61\x68\x61\x6A\x61\x6E\x69", 343 }, + { "\x4D\x61\x68\x6A", 343 }, + { "\x4D\x61\x6B\x61", 239 }, + { "\x4D\x61\x6B\x61\x73\x61\x72", 239 }, + { "\x4D\x61\x6C\x61\x79\x61\x6C\x61\x6D", 288 }, + { "\x4D\x61\x6E\x64", 332 }, + { "\x4D\x61\x6E\x64\x61\x69\x63", 332 }, + { "\x4D\x61\x6E\x69", 344 }, + { "\x4D\x61\x6E\x69\x63\x68\x61\x65\x61\x6E", 344 }, + { "\x4D\x61\x72\x63", 229 }, + { "\x4D\x61\x72\x63\x68\x65\x6E", 229 }, + { "\x4D\x61\x73\x61\x72\x61\x6D\x5F\x47\x6F\x6E\x64\x69", 356 }, + { "\x4D\x65\x64\x65\x66\x61\x69\x64\x72\x69\x6E", 240 }, + { "\x4D\x65\x64\x66", 240 }, + { "\x4D\x65\x65\x74\x65\x69\x5F\x4D\x61\x79\x65\x6B", 181 }, + { "\x4D\x65\x6E\x64", 208 }, + { "\x4D\x65\x6E\x64\x65\x5F\x4B\x69\x6B\x61\x6B\x75\x69", 208 }, + { "\x4D\x65\x72\x63", 192 }, + { "\x4D\x65\x72\x6F", 334 }, + { "\x4D\x65\x72\x6F\x69\x74\x69\x63\x5F\x43\x75\x72\x73\x69\x76\x65", 192 }, + { "\x4D\x65\x72\x6F\x69\x74\x69\x63\x5F\x48\x69\x65\x72\x6F\x67\x6C\x79\x70\x68\x73", 334 }, + { "\x4D\x69\x61\x6F", 194 }, + { "\x4D\x6C\x79\x6D", 288 }, + { "\x4D\x6F\x64\x69", 345 }, + { "\x4D\x6F\x6E\x67", 298 }, + { "\x4D\x6F\x6E\x67\x6F\x6C\x69\x61\x6E", 298 }, + { "\x4D\x72\x6F", 210 }, + { "\x4D\x72\x6F\x6F", 210 }, + { "\x4D\x74\x65\x69", 181 }, + { "\x4D\x75\x6C\x74", 350 }, + { "\x4D\x75\x6C\x74\x61\x6E\x69", 350 }, + { "\x4D\x79\x61\x6E\x6D\x61\x72", 292 }, + { "\x4D\x79\x6D\x72", 292 }, + { "\x4E\x61\x62\x61\x74\x61\x65\x61\x6E", 212 }, + { "\x4E\x61\x67\x5F\x4D\x75\x6E\x64\x61\x72\x69", 258 }, + { "\x4E\x61\x67\x6D", 258 }, + { "\x4E\x61\x6E\x64", 361 }, + { "\x4E\x61\x6E\x64\x69\x6E\x61\x67\x61\x72\x69", 361 }, + { "\x4E\x61\x72\x62", 211 }, + { "\x4E\x62\x61\x74", 212 }, + { "\x4E\x65\x77\x5F\x54\x61\x69\x5F\x4C\x75\x65", 151 }, + { "\x4E\x65\x77\x61", 353 }, + { "\x4E\x6B\x6F", 321 }, + { "\x4E\x6B\x6F\x6F", 321 }, + { "\x4E\x73\x68\x75", 234 }, + { "\x4E\x75\x73\x68\x75", 234 }, + { "\x4E\x79\x69\x61\x6B\x65\x6E\x67\x5F\x50\x75\x61\x63\x68\x75\x65\x5F\x48\x6D\x6F\x6E\x67", 246 }, + { "\x4F\x67\x61\x6D", 124 }, + { "\x4F\x67\x68\x61\x6D", 124 }, + { "\x4F\x6C\x5F\x43\x68\x69\x6B\x69", 164 }, + { "\x4F\x6C\x5F\x4F\x6E\x61\x6C", 368 }, + { "\x4F\x6C\x63\x6B", 164 }, + { "\x4F\x6C\x64\x5F\x48\x75\x6E\x67\x61\x72\x69\x61\x6E", 351 }, + { "\x4F\x6C\x64\x5F\x49\x74\x61\x6C\x69\x63", 133 }, + { "\x4F\x6C\x64\x5F\x4E\x6F\x72\x74\x68\x5F\x41\x72\x61\x62\x69\x61\x6E", 211 }, + { "\x4F\x6C\x64\x5F\x50\x65\x72\x6D\x69\x63", 346 }, + { "\x4F\x6C\x64\x5F\x50\x65\x72\x73\x69\x61\x6E", 155 }, + { "\x4F\x6C\x64\x5F\x53\x6F\x67\x64\x69\x61\x6E", 243 }, + { "\x4F\x6C\x64\x5F\x53\x6F\x75\x74\x68\x5F\x41\x72\x61\x62\x69\x61\x6E", 183 }, + { "\x4F\x6C\x64\x5F\x54\x75\x72\x6B\x69\x63", 330 }, + { "\x4F\x6C\x64\x5F\x55\x79\x67\x68\x75\x72", 364 }, + { "\x4F\x6E\x61\x6F", 368 }, + { "\x4F\x72\x69\x79\x61", 284 }, + { "\x4F\x72\x6B\x68", 330 }, + { "\x4F\x72\x79\x61", 284 }, + { "\x4F\x73\x61\x67\x65", 354 }, + { "\x4F\x73\x67\x65", 354 }, + { "\x4F\x73\x6D\x61", 146 }, + { "\x4F\x73\x6D\x61\x6E\x79\x61", 146 }, + { "\x4F\x75\x67\x72", 364 }, + { "\x50\x61\x68\x61\x77\x68\x5F\x48\x6D\x6F\x6E\x67", 203 }, + { "\x50\x61\x6C\x6D", 213 }, + { "\x50\x61\x6C\x6D\x79\x72\x65\x6E\x65", 213 }, + { "\x50\x61\x75\x5F\x43\x69\x6E\x5F\x48\x61\x75", 214 }, + { "\x50\x61\x75\x63", 214 }, + { "\x50\x65\x72\x6D", 346 }, + { "\x50\x68\x61\x67", 320 }, + { "\x50\x68\x61\x67\x73\x5F\x50\x61", 320 }, + { "\x50\x68\x6C\x69", 185 }, + { "\x50\x68\x6C\x70", 347 }, + { "\x50\x68\x6E\x78", 159 }, + { "\x50\x68\x6F\x65\x6E\x69\x63\x69\x61\x6E", 159 }, + { "\x50\x6C\x72\x64", 194 }, + { "\x50\x72\x74\x69", 184 }, + { "\x50\x73\x61\x6C\x74\x65\x72\x5F\x50\x61\x68\x6C\x61\x76\x69", 347 }, + { "\x51\x61\x61\x63", 316 }, + { "\x51\x61\x61\x69", 305 }, + { "\x52\x65\x6A\x61\x6E\x67", 168 }, + { "\x52\x6A\x6E\x67", 168 }, + { "\x52\x6F\x68\x67", 359 }, + { "\x52\x75\x6E\x69\x63", 297 }, + { "\x52\x75\x6E\x72", 297 }, + { "\x53\x61\x6D\x61\x72\x69\x74\x61\x6E", 327 }, + { "\x53\x61\x6D\x72", 327 }, + { "\x53\x61\x72\x62", 183 }, + { "\x53\x61\x75\x72", 166 }, + { "\x53\x61\x75\x72\x61\x73\x68\x74\x72\x61", 166 }, + { "\x53\x67\x6E\x77", 226 }, + { "\x53\x68\x61\x72\x61\x64\x61", 335 }, + { "\x53\x68\x61\x76\x69\x61\x6E", 313 }, + { "\x53\x68\x61\x77", 313 }, + { "\x53\x68\x72\x64", 335 }, + { "\x53\x69\x64\x64", 217 }, + { "\x53\x69\x64\x64\x68\x61\x6D", 217 }, + { "\x53\x69\x64\x65\x74\x69\x63", 266 }, + { "\x53\x69\x64\x74", 266 }, + { "\x53\x69\x67\x6E\x57\x72\x69\x74\x69\x6E\x67", 226 }, + { "\x53\x69\x6E\x64", 348 }, + { "\x53\x69\x6E\x68", 289 }, + { "\x53\x69\x6E\x68\x61\x6C\x61", 289 }, + { "\x53\x6F\x67\x64", 360 }, + { "\x53\x6F\x67\x64\x69\x61\x6E", 360 }, + { "\x53\x6F\x67\x6F", 243 }, + { "\x53\x6F\x72\x61", 196 }, + { "\x53\x6F\x72\x61\x5F\x53\x6F\x6D\x70\x65\x6E\x67", 196 }, + { "\x53\x6F\x79\x6F", 235 }, + { "\x53\x6F\x79\x6F\x6D\x62\x6F", 235 }, + { "\x53\x75\x6E\x64", 162 }, + { "\x53\x75\x6E\x64\x61\x6E\x65\x73\x65", 162 }, + { "\x53\x75\x6E\x75", 369 }, + { "\x53\x75\x6E\x75\x77\x61\x72", 369 }, + { "\x53\x79\x6C\x6F", 319 }, + { "\x53\x79\x6C\x6F\x74\x69\x5F\x4E\x61\x67\x72\x69", 319 }, + { "\x53\x79\x72\x63", 278 }, + { "\x53\x79\x72\x69\x61\x63", 278 }, + { "\x54\x61\x67\x61\x6C\x6F\x67", 306 }, + { "\x54\x61\x67\x62", 309 }, + { "\x54\x61\x67\x62\x61\x6E\x77\x61", 309 }, + { "\x54\x61\x69\x5F\x4C\x65", 311 }, + { "\x54\x61\x69\x5F\x54\x68\x61\x6D", 173 }, + { "\x54\x61\x69\x5F\x56\x69\x65\x74", 174 }, + { "\x54\x61\x69\x5F\x59\x6F", 267 }, + { "\x54\x61\x6B\x72", 336 }, + { "\x54\x61\x6B\x72\x69", 336 }, + { "\x54\x61\x6C\x65", 311 }, + { "\x54\x61\x6C\x75", 151 }, + { "\x54\x61\x6D\x69\x6C", 285 }, + { "\x54\x61\x6D\x6C", 285 }, + { "\x54\x61\x6E\x67", 355 }, + { "\x54\x61\x6E\x67\x73\x61", 254 }, + { "\x54\x61\x6E\x67\x75\x74", 355 }, + { "\x54\x61\x76\x74", 174 }, + { "\x54\x61\x79\x6F", 267 }, + { "\x54\x65\x6C\x75", 286 }, + { "\x54\x65\x6C\x75\x67\x75", 286 }, + { "\x54\x66\x6E\x67", 318 }, + { "\x54\x67\x6C\x67", 306 }, + { "\x54\x68\x61\x61", 279 }, + { "\x54\x68\x61\x61\x6E\x61", 279 }, + { "\x54\x68\x61\x69", 290 }, + { "\x54\x69\x62\x65\x74\x61\x6E", 291 }, + { "\x54\x69\x62\x74", 291 }, + { "\x54\x69\x66\x69\x6E\x61\x67\x68", 318 }, + { "\x54\x69\x72\x68", 349 }, + { "\x54\x69\x72\x68\x75\x74\x61", 349 }, + { "\x54\x6E\x73\x61", 254 }, + { "\x54\x6F\x64\x68\x72\x69", 370 }, + { "\x54\x6F\x64\x72", 370 }, + { "\x54\x6F\x6C\x6F\x6E\x67\x5F\x53\x69\x6B\x69", 268 }, + { "\x54\x6F\x6C\x73", 268 }, + { "\x54\x6F\x74\x6F", 365 }, + { "\x54\x75\x6C\x75\x5F\x54\x69\x67\x61\x6C\x61\x72\x69", 371 }, + { "\x54\x75\x74\x67", 371 }, + { "\x55\x67\x61\x72", 144 }, + { "\x55\x67\x61\x72\x69\x74\x69\x63", 144 }, + { "\x55\x6E\x6B\x6E\x6F\x77\x6E", 270 }, + { "\x56\x61\x69", 165 }, + { "\x56\x61\x69\x69", 165 }, + { "\x56\x69\x74\x68", 256 }, + { "\x56\x69\x74\x68\x6B\x75\x71\x69", 256 }, + { "\x57\x61\x6E\x63\x68\x6F", 247 }, + { "\x57\x61\x72\x61", 220 }, + { "\x57\x61\x72\x61\x6E\x67\x5F\x43\x69\x74\x69", 220 }, + { "\x57\x63\x68\x6F", 247 }, + { "\x58\x70\x65\x6F", 155 }, + { "\x58\x73\x75\x78", 158 }, + { "\x59\x65\x7A\x69", 362 }, + { "\x59\x65\x7A\x69\x64\x69", 362 }, + { "\x59\x69", 303 }, + { "\x59\x69\x69\x69", 303 }, + { "\x5A\x61\x6E\x61\x62\x61\x7A\x61\x72\x5F\x53\x71\x75\x61\x72\x65", 236 }, + { "\x5A\x61\x6E\x62", 236 }, + { "\x5A\x69\x6E\x68", 305 }, + { "\x5A\x79\x79\x79", 271 }, + { "\x5A\x7A\x7A\x7A", 270 } +}; + +template +const T2 unicode_property_data::positiontable[] = +{ + { 0, 0 }, // #0 unknown + { 87, 105 }, // #1 binary + { 7, 80 }, // #2 General_Category:gc + { 192, 344 }, // #3 Script:sc + { 536, 344 }, // #4 Script_Extensions:scx + { 0, 762 }, // #5 gc=Other:C + { 0, 2 }, // #6 gc=Control:Cc:cntrl + { 2, 21 }, // #7 gc=Format:Cf + { 23, 735 }, // #8 gc=Unassigned:Cn + { 758, 3 }, // #9 gc=Private_Use:Co + { 761, 1 }, // #10 gc=Surrogate:Cs + { 762, 1945 }, // #11 gc=Letter:L + { 762, 1329 }, // #12 gc=Cased_Letter:LC + { 762, 664 }, // #13 gc=Lowercase_Letter:Ll + { 1426, 10 }, // #14 gc=Titlecase_Letter:Lt + { 1436, 655 }, // #15 gc=Uppercase_Letter:Lu + { 2091, 79 }, // #16 gc=Modifier_Letter:Lm + { 2170, 537 }, // #17 gc=Other_Letter:Lo + { 2707, 563 }, // #18 gc=Mark:M:Combining_Mark + { 2707, 193 }, // #19 gc=Spacing_Mark:Mc + { 2900, 5 }, // #20 gc=Enclosing_Mark:Me + { 2905, 365 }, // #21 gc=Nonspacing_Mark:Mn + { 3270, 157 }, // #22 gc=Number:N + { 3270, 72 }, // #23 gc=Decimal_Number:Nd:digit + { 3342, 13 }, // #24 gc=Letter_Number:Nl + { 3355, 72 }, // #25 gc=Other_Number:No + { 3427, 396 }, // #26 gc=Punctuation:P:punct + { 3427, 6 }, // #27 gc=Connector_Punctuation:Pc + { 3433, 20 }, // #28 gc=Dash_Punctuation:Pd + { 3453, 76 }, // #29 gc=Close_Punctuation:Pe + { 3529, 10 }, // #30 gc=Final_Punctuation:Pf + { 3539, 11 }, // #31 gc=Initial_Punctuation:Pi + { 3550, 194 }, // #32 gc=Other_Punctuation:Po + { 3744, 79 }, // #33 gc=Open_Punctuation:Ps + { 3823, 312 }, // #34 gc=Symbol:S + { 3823, 21 }, // #35 gc=Currency_Symbol:Sc + { 3844, 31 }, // #36 gc=Modifier_Symbol:Sk + { 3875, 67 }, // #37 gc=Math_Symbol:Sm + { 3942, 193 }, // #38 gc=Other_Symbol:So + { 4135, 9 }, // #39 gc=Separator:Z + { 4135, 1 }, // #40 gc=Line_Separator:Zl + { 4136, 1 }, // #41 gc=Paragraph_Separator:Zp + { 4137, 7 }, // #42 gc=Space_Separator:Zs + { 4144, 1 }, // #43 bp=ASCII + { 4145, 3 }, // #44 bp=ASCII_Hex_Digit:AHex + { 4148, 761 }, // #45 bp=Alphabetic:Alpha + { 4909, 1 }, // #46 bp=Any + { 4910, 0 }, // #47 bp=Assigned + { 4910, 4 }, // #48 bp=Bidi_Control:Bidi_C + { 4914, 114 }, // #49 bp=Bidi_Mirrored:Bidi_M + { 5028, 464 }, // #50 bp=Case_Ignorable:CI + { 5492, 158 }, // #51 bp=Cased + { 5650, 630 }, // #52 bp=Changes_When_Casefolded:CWCF + { 6280, 131 }, // #53 bp=Changes_When_Casemapped:CWCM + { 6411, 618 }, // #54 bp=Changes_When_Lowercased:CWL + { 7029, 848 }, // #55 bp=Changes_When_NFKC_Casefolded:CWKCF + { 7877, 633 }, // #56 bp=Changes_When_Titlecased:CWT + { 8510, 634 }, // #57 bp=Changes_When_Uppercased:CWU + { 9144, 24 }, // #58 bp=Dash + { 9168, 17 }, // #59 bp=Default_Ignorable_Code_Point:DI + { 9185, 8 }, // #60 bp=Deprecated:Dep + { 9193, 220 }, // #61 bp=Diacritic:Dia + { 9413, 151 }, // #62 bp=Emoji + { 9564, 10 }, // #63 bp=Emoji_Component:EComp + { 9574, 1 }, // #64 bp=Emoji_Modifier:EMod + { 9575, 40 }, // #65 bp=Emoji_Modifier_Base:EBase + { 9615, 81 }, // #66 bp=Emoji_Presentation:EPres + { 9696, 156 }, // #67 bp=Extended_Pictographic:ExtPict + { 9852, 43 }, // #68 bp=Extender:Ext + { 9895, 904 }, // #69 bp=Grapheme_Base:Gr_Base + { 10799, 383 }, // #70 bp=Grapheme_Extend:Gr_Ext + { 11182, 6 }, // #71 bp=Hex_Digit:Hex + { 11188, 3 }, // #72 bp=IDS_Binary_Operator:IDSB + { 11191, 1 }, // #73 bp=IDS_Trinary_Operator:IDST + { 11192, 799 }, // #74 bp=ID_Continue:IDC + { 11991, 684 }, // #75 bp=ID_Start:IDS + { 12675, 21 }, // #76 bp=Ideographic:Ideo + { 12696, 1 }, // #77 bp=Join_Control:Join_C + { 12697, 7 }, // #78 bp=Logical_Order_Exception:LOE + { 12704, 677 }, // #79 bp=Lowercase:Lower + { 13381, 141 }, // #80 bp=Math + { 13522, 18 }, // #81 bp=Noncharacter_Code_Point:NChar + { 13540, 28 }, // #82 bp=Pattern_Syntax:Pat_Syn + { 13568, 5 }, // #83 bp=Pattern_White_Space:Pat_WS + { 13573, 13 }, // #84 bp=Quotation_Mark:QMark + { 13586, 3 }, // #85 bp=Radical + { 13589, 1 }, // #86 bp=Regional_Indicator:RI + { 13590, 88 }, // #87 bp=Sentence_Terminal:STerm + { 13678, 34 }, // #88 bp=Soft_Dotted:SD + { 13712, 116 }, // #89 bp=Terminal_Punctuation:Term + { 13828, 16 }, // #90 bp=Unified_Ideograph:UIdeo + { 13844, 660 }, // #91 bp=Uppercase:Upper + { 14504, 4 }, // #92 bp=Variation_Selector:VS + { 14508, 10 }, // #93 bp=White_Space:space + { 14518, 806 }, // #94 bp=XID_Continue:XIDC + { 15324, 691 }, // #95 bp=XID_Start:XIDS + { 16015, 176 }, // #96 sc=Common:Zyyy + { 16191, 36 }, // #97 sc=Latin:Latn + { 16227, 36 }, // #98 sc=Greek:Grek + { 16263, 10 }, // #99 sc=Cyrillic:Cyrl + { 16273, 4 }, // #100 sc=Armenian:Armn + { 16277, 9 }, // #101 sc=Hebrew:Hebr + { 16286, 56 }, // #102 sc=Arabic:Arab + { 16342, 4 }, // #103 sc=Syriac:Syrc + { 16346, 1 }, // #104 sc=Thaana:Thaa + { 16347, 5 }, // #105 sc=Devanagari:Deva + { 16352, 14 }, // #106 sc=Bengali:Beng + { 16366, 16 }, // #107 sc=Gurmukhi:Guru + { 16382, 14 }, // #108 sc=Gujarati:Gujr + { 16396, 14 }, // #109 sc=Oriya:Orya + { 16410, 18 }, // #110 sc=Tamil:Taml + { 16428, 13 }, // #111 sc=Telugu:Telu + { 16441, 13 }, // #112 sc=Kannada:Knda + { 16454, 7 }, // #113 sc=Malayalam:Mlym + { 16461, 13 }, // #114 sc=Sinhala:Sinh + { 16474, 2 }, // #115 sc=Thai + { 16476, 11 }, // #116 sc=Lao:Laoo scx=Lao:Laoo + { 16487, 7 }, // #117 sc=Tibetan:Tibt + { 16494, 4 }, // #118 sc=Myanmar:Mymr + { 16498, 10 }, // #119 sc=Georgian:Geor + { 16508, 14 }, // #120 sc=Hangul:Hang + { 16522, 36 }, // #121 sc=Ethiopic:Ethi + { 16558, 3 }, // #122 sc=Cherokee:Cher + { 16561, 3 }, // #123 sc=Canadian_Aboriginal:Cans scx=Canadian_Aboriginal:Cans + { 16564, 1 }, // #124 sc=Ogham:Ogam scx=Ogham:Ogam + { 16565, 2 }, // #125 sc=Runic:Runr + { 16567, 4 }, // #126 sc=Khmer:Khmr scx=Khmer:Khmr + { 16571, 6 }, // #127 sc=Mongolian:Mong + { 16577, 6 }, // #128 sc=Hiragana:Hira + { 16583, 14 }, // #129 sc=Katakana:Kana + { 16597, 3 }, // #130 sc=Bopomofo:Bopo + { 16600, 21 }, // #131 sc=Han:Hani + { 16621, 2 }, // #132 sc=Yi:Yiii + { 16623, 2 }, // #133 sc=Old_Italic:Ital scx=Old_Italic:Ital + { 16625, 1 }, // #134 sc=Gothic:Goth + { 16626, 1 }, // #135 sc=Deseret:Dsrt scx=Deseret:Dsrt + { 16627, 30 }, // #136 sc=Inherited:Zinh:Qaai + { 16657, 2 }, // #137 sc=Tagalog:Tglg + { 16659, 1 }, // #138 sc=Hanunoo:Hano + { 16660, 1 }, // #139 sc=Buhid:Buhd + { 16661, 3 }, // #140 sc=Tagbanwa:Tagb + { 16664, 5 }, // #141 sc=Limbu:Limb + { 16669, 2 }, // #142 sc=Tai_Le:Tale + { 16671, 7 }, // #143 sc=Linear_B:Linb + { 16678, 2 }, // #144 sc=Ugaritic:Ugar scx=Ugaritic:Ugar + { 16680, 1 }, // #145 sc=Shavian:Shaw + { 16681, 2 }, // #146 sc=Osmanya:Osma scx=Osmanya:Osma + { 16683, 6 }, // #147 sc=Cypriot:Cprt + { 16689, 1 }, // #148 sc=Braille:Brai scx=Braille:Brai + { 16690, 2 }, // #149 sc=Buginese:Bugi + { 16692, 3 }, // #150 sc=Coptic:Copt:Qaac + { 16695, 4 }, // #151 sc=New_Tai_Lue:Talu scx=New_Tai_Lue:Talu + { 16699, 6 }, // #152 sc=Glagolitic:Glag + { 16705, 3 }, // #153 sc=Tifinagh:Tfng + { 16708, 1 }, // #154 sc=Syloti_Nagri:Sylo + { 16709, 2 }, // #155 sc=Old_Persian:Xpeo scx=Old_Persian:Xpeo + { 16711, 8 }, // #156 sc=Kharoshthi:Khar scx=Kharoshthi:Khar + { 16719, 2 }, // #157 sc=Balinese:Bali scx=Balinese:Bali + { 16721, 4 }, // #158 sc=Cuneiform:Xsux scx=Cuneiform:Xsux + { 16725, 2 }, // #159 sc=Phoenician:Phnx scx=Phoenician:Phnx + { 16727, 1 }, // #160 sc=Phags_Pa:Phag + { 16728, 2 }, // #161 sc=Nko:Nkoo + { 16730, 2 }, // #162 sc=Sundanese:Sund scx=Sundanese:Sund + { 16732, 3 }, // #163 sc=Lepcha:Lepc scx=Lepcha:Lepc + { 16735, 1 }, // #164 sc=Ol_Chiki:Olck scx=Ol_Chiki:Olck + { 16736, 1 }, // #165 sc=Vai:Vaii scx=Vai:Vaii + { 16737, 2 }, // #166 sc=Saurashtra:Saur scx=Saurashtra:Saur + { 16739, 2 }, // #167 sc=Kayah_Li:Kali + { 16741, 2 }, // #168 sc=Rejang:Rjng scx=Rejang:Rjng + { 16743, 1 }, // #169 sc=Lycian:Lyci + { 16744, 1 }, // #170 sc=Carian:Cari + { 16745, 2 }, // #171 sc=Lydian:Lydi + { 16747, 4 }, // #172 sc=Cham scx=Cham + { 16751, 5 }, // #173 sc=Tai_Tham:Lana scx=Tai_Tham:Lana + { 16756, 2 }, // #174 sc=Tai_Viet:Tavt scx=Tai_Viet:Tavt + { 16758, 2 }, // #175 sc=Avestan:Avst + { 16760, 2 }, // #176 sc=Egyptian_Hieroglyphs:Egyp scx=Egyptian_Hieroglyphs:Egyp + { 16762, 2 }, // #177 sc=Samaritan:Samr + { 16764, 2 }, // #178 sc=Lisu + { 16766, 2 }, // #179 sc=Bamum:Bamu scx=Bamum:Bamu + { 16768, 3 }, // #180 sc=Javanese:Java + { 16771, 3 }, // #181 sc=Meetei_Mayek:Mtei scx=Meetei_Mayek:Mtei + { 16774, 2 }, // #182 sc=Imperial_Aramaic:Armi scx=Imperial_Aramaic:Armi + { 16776, 1 }, // #183 sc=Old_South_Arabian:Sarb scx=Old_South_Arabian:Sarb + { 16777, 2 }, // #184 sc=Inscriptional_Parthian:Prti scx=Inscriptional_Parthian:Prti + { 16779, 2 }, // #185 sc=Inscriptional_Pahlavi:Phli scx=Inscriptional_Pahlavi:Phli + { 16781, 1 }, // #186 sc=Old_Turkic:Orkh + { 16782, 2 }, // #187 sc=Kaithi:Kthi + { 16784, 2 }, // #188 sc=Batak:Batk scx=Batak:Batk + { 16786, 3 }, // #189 sc=Brahmi:Brah scx=Brahmi:Brah + { 16789, 2 }, // #190 sc=Mandaic:Mand + { 16791, 2 }, // #191 sc=Chakma:Cakm + { 16793, 3 }, // #192 sc=Meroitic_Cursive:Merc scx=Meroitic_Cursive:Merc + { 16796, 1 }, // #193 sc=Meroitic_Hieroglyphs:Mero + { 16797, 3 }, // #194 sc=Miao:Plrd scx=Miao:Plrd + { 16800, 2 }, // #195 sc=Sharada:Shrd + { 16802, 2 }, // #196 sc=Sora_Sompeng:Sora scx=Sora_Sompeng:Sora + { 16804, 2 }, // #197 sc=Takri:Takr + { 16806, 2 }, // #198 sc=Caucasian_Albanian:Aghb + { 16808, 2 }, // #199 sc=Bassa_Vah:Bass scx=Bassa_Vah:Bass + { 16810, 5 }, // #200 sc=Duployan:Dupl + { 16815, 1 }, // #201 sc=Elbasan:Elba + { 16816, 15 }, // #202 sc=Grantha:Gran + { 16831, 5 }, // #203 sc=Pahawh_Hmong:Hmng scx=Pahawh_Hmong:Hmng + { 16836, 2 }, // #204 sc=Khojki:Khoj + { 16838, 3 }, // #205 sc=Linear_A:Lina + { 16841, 1 }, // #206 sc=Mahajani:Mahj + { 16842, 2 }, // #207 sc=Manichaean:Mani + { 16844, 2 }, // #208 sc=Mende_Kikakui:Mend scx=Mende_Kikakui:Mend + { 16846, 2 }, // #209 sc=Modi + { 16848, 3 }, // #210 sc=Mro:Mroo scx=Mro:Mroo + { 16851, 1 }, // #211 sc=Old_North_Arabian:Narb scx=Old_North_Arabian:Narb + { 16852, 2 }, // #212 sc=Nabataean:Nbat scx=Nabataean:Nbat + { 16854, 1 }, // #213 sc=Palmyrene:Palm scx=Palmyrene:Palm + { 16855, 1 }, // #214 sc=Pau_Cin_Hau:Pauc scx=Pau_Cin_Hau:Pauc + { 16856, 1 }, // #215 sc=Old_Permic:Perm + { 16857, 3 }, // #216 sc=Psalter_Pahlavi:Phlp + { 16860, 2 }, // #217 sc=Siddham:Sidd scx=Siddham:Sidd + { 16862, 2 }, // #218 sc=Khudawadi:Sind + { 16864, 2 }, // #219 sc=Tirhuta:Tirh + { 16866, 2 }, // #220 sc=Warang_Citi:Wara scx=Warang_Citi:Wara + { 16868, 3 }, // #221 sc=Ahom scx=Ahom + { 16871, 1 }, // #222 sc=Anatolian_Hieroglyphs:Hluw scx=Anatolian_Hieroglyphs:Hluw + { 16872, 3 }, // #223 sc=Hatran:Hatr scx=Hatran:Hatr + { 16875, 5 }, // #224 sc=Multani:Mult + { 16880, 3 }, // #225 sc=Old_Hungarian:Hung + { 16883, 3 }, // #226 sc=SignWriting:Sgnw scx=SignWriting:Sgnw + { 16886, 3 }, // #227 sc=Adlam:Adlm + { 16889, 4 }, // #228 sc=Bhaiksuki:Bhks scx=Bhaiksuki:Bhks + { 16893, 3 }, // #229 sc=Marchen:Marc scx=Marchen:Marc + { 16896, 2 }, // #230 sc=Newa + { 16898, 2 }, // #231 sc=Osage:Osge + { 16900, 4 }, // #232 sc=Tangut:Tang + { 16904, 7 }, // #233 sc=Masaram_Gondi:Gonm + { 16911, 2 }, // #234 sc=Nushu:Nshu scx=Nushu:Nshu + { 16913, 1 }, // #235 sc=Soyombo:Soyo scx=Soyombo:Soyo + { 16914, 1 }, // #236 sc=Zanabazar_Square:Zanb scx=Zanabazar_Square:Zanb + { 16915, 1 }, // #237 sc=Dogra:Dogr + { 16916, 6 }, // #238 sc=Gunjala_Gondi:Gong + { 16922, 1 }, // #239 sc=Makasar:Maka scx=Makasar:Maka + { 16923, 1 }, // #240 sc=Medefaidrin:Medf scx=Medefaidrin:Medf + { 16924, 2 }, // #241 sc=Hanifi_Rohingya:Rohg + { 16926, 1 }, // #242 sc=Sogdian:Sogd + { 16927, 1 }, // #243 sc=Old_Sogdian:Sogo scx=Old_Sogdian:Sogo + { 16928, 1 }, // #244 sc=Elymaic:Elym scx=Elymaic:Elym + { 16929, 3 }, // #245 sc=Nandinagari:Nand + { 16932, 4 }, // #246 sc=Nyiakeng_Puachue_Hmong:Hmnp scx=Nyiakeng_Puachue_Hmong:Hmnp + { 16936, 2 }, // #247 sc=Wancho:Wcho scx=Wancho:Wcho + { 16938, 1 }, // #248 sc=Chorasmian:Chrs scx=Chorasmian:Chrs + { 16939, 8 }, // #249 sc=Dives_Akuru:Diak scx=Dives_Akuru:Diak + { 16947, 3 }, // #250 sc=Khitan_Small_Script:Kits scx=Khitan_Small_Script:Kits + { 16950, 3 }, // #251 sc=Yezidi:Yezi + { 16953, 1 }, // #252 sc=Cypro_Minoan:Cpmn + { 16954, 1 }, // #253 sc=Old_Uyghur:Ougr + { 16955, 2 }, // #254 sc=Tangsa:Tnsa scx=Tangsa:Tnsa + { 16957, 1 }, // #255 sc=Toto + { 16958, 8 }, // #256 sc=Vithkuqi:Vith scx=Vithkuqi:Vith + { 16966, 3 }, // #257 sc=Kawi scx=Kawi + { 16969, 1 }, // #258 sc=Nag_Mundari:Nagm scx=Nag_Mundari:Nagm + { 16970, 3 }, // #259 sc=Garay:Gara + { 16973, 1 }, // #260 sc=Gurung_Khema:Gukh + { 16974, 1 }, // #261 sc=Kirat_Rai:Krai scx=Kirat_Rai:Krai + { 16975, 2 }, // #262 sc=Ol_Onal:Onao + { 16977, 2 }, // #263 sc=Sunuwar:Sunu + { 16979, 1 }, // #264 sc=Todhri:Todr + { 16980, 11 }, // #265 sc=Tulu_Tigalari:Tutg + { 16991, 1 }, // #266 sc=Sidetic:Sidt scx=Sidetic:Sidt + { 16992, 3 }, // #267 sc=Tai_Yo:Tayo scx=Tai_Yo:Tayo + { 16995, 2 }, // #268 sc=Tolong_Siki:Tols scx=Tolong_Siki:Tols + { 16997, 2 }, // #269 sc=Beria_Erfe:Berf scx=Beria_Erfe:Berf + { 16999, 733 }, // #270 sc=Unknown:Zzzz scx=Unknown:Zzzz + { 17732, 161 }, // #271 scx=Common:Zyyy + { 17893, 61 }, // #272 scx=Latin:Latn + { 17954, 44 }, // #273 scx=Greek:Grek + { 17998, 18 }, // #274 scx=Cyrillic:Cyrl + { 18016, 5 }, // #275 scx=Armenian:Armn + { 18021, 10 }, // #276 scx=Hebrew:Hebr + { 18031, 52 }, // #277 scx=Arabic:Arab + { 18083, 18 }, // #278 scx=Syriac:Syrc + { 18101, 7 }, // #279 scx=Thaana:Thaa + { 18108, 9 }, // #280 scx=Devanagari:Deva + { 18117, 27 }, // #281 scx=Bengali:Beng + { 18144, 19 }, // #282 scx=Gurmukhi:Guru + { 18163, 17 }, // #283 scx=Gujarati:Gujr + { 18180, 18 }, // #284 scx=Oriya:Orya + { 18198, 25 }, // #285 scx=Tamil:Taml + { 18223, 19 }, // #286 scx=Telugu:Telu + { 18242, 21 }, // #287 scx=Kannada:Knda + { 18263, 12 }, // #288 scx=Malayalam:Mlym + { 18275, 15 }, // #289 scx=Sinhala:Sinh + { 18290, 6 }, // #290 scx=Thai + { 18296, 8 }, // #291 scx=Tibetan:Tibt + { 18304, 5 }, // #292 scx=Myanmar:Mymr + { 18309, 13 }, // #293 scx=Georgian:Geor + { 18322, 21 }, // #294 scx=Hangul:Hang + { 18343, 37 }, // #295 scx=Ethiopic:Ethi + { 18380, 8 }, // #296 scx=Cherokee:Cher + { 18388, 1 }, // #297 scx=Runic:Runr + { 18389, 7 }, // #298 scx=Mongolian:Mong + { 18396, 17 }, // #299 scx=Hiragana:Hira + { 18413, 22 }, // #300 scx=Katakana:Kana + { 18435, 15 }, // #301 scx=Bopomofo:Bopo + { 18450, 41 }, // #302 scx=Han:Hani + { 18491, 7 }, // #303 scx=Yi:Yiii + { 18498, 5 }, // #304 scx=Gothic:Goth + { 18503, 28 }, // #305 scx=Inherited:Zinh:Qaai + { 18531, 3 }, // #306 scx=Tagalog:Tglg + { 18534, 1 }, // #307 scx=Hanunoo:Hano + { 18535, 2 }, // #308 scx=Buhid:Buhd + { 18537, 4 }, // #309 scx=Tagbanwa:Tagb + { 18541, 6 }, // #310 scx=Limbu:Limb + { 18547, 6 }, // #311 scx=Tai_Le:Tale + { 18553, 10 }, // #312 scx=Linear_B:Linb + { 18563, 2 }, // #313 scx=Shavian:Shaw + { 18565, 9 }, // #314 scx=Cypriot:Cprt + { 18574, 3 }, // #315 scx=Buginese:Bugi + { 18577, 10 }, // #316 scx=Coptic:Copt:Qaac + { 18587, 16 }, // #317 scx=Glagolitic:Glag + { 18603, 7 }, // #318 scx=Tifinagh:Tfng + { 18610, 3 }, // #319 scx=Syloti_Nagri:Sylo + { 18613, 5 }, // #320 scx=Phags_Pa:Phag + { 18618, 6 }, // #321 scx=Nko:Nkoo + { 18624, 1 }, // #322 scx=Kayah_Li:Kali + { 18625, 2 }, // #323 scx=Lycian:Lyci + { 18627, 5 }, // #324 scx=Carian:Cari + { 18632, 4 }, // #325 scx=Lydian:Lydi + { 18636, 4 }, // #326 scx=Avestan:Avst + { 18640, 3 }, // #327 scx=Samaritan:Samr + { 18643, 5 }, // #328 scx=Lisu + { 18648, 3 }, // #329 scx=Javanese:Java + { 18651, 3 }, // #330 scx=Old_Turkic:Orkh + { 18654, 5 }, // #331 scx=Kaithi:Kthi + { 18659, 3 }, // #332 scx=Mandaic:Mand + { 18662, 4 }, // #333 scx=Chakma:Cakm + { 18666, 2 }, // #334 scx=Meroitic_Hieroglyphs:Mero + { 18668, 11 }, // #335 scx=Sharada:Shrd + { 18679, 4 }, // #336 scx=Takri:Takr + { 18683, 5 }, // #337 scx=Caucasian_Albanian:Aghb + { 18688, 10 }, // #338 scx=Duployan:Dupl + { 18698, 3 }, // #339 scx=Elbasan:Elba + { 18701, 25 }, // #340 scx=Grantha:Gran + { 18726, 4 }, // #341 scx=Khojki:Khoj + { 18730, 4 }, // #342 scx=Linear_A:Lina + { 18734, 4 }, // #343 scx=Mahajani:Mahj + { 18738, 3 }, // #344 scx=Manichaean:Mani + { 18741, 3 }, // #345 scx=Modi + { 18744, 6 }, // #346 scx=Old_Permic:Perm + { 18750, 4 }, // #347 scx=Psalter_Pahlavi:Phlp + { 18754, 4 }, // #348 scx=Khudawadi:Sind + { 18758, 8 }, // #349 scx=Tirhuta:Tirh + { 18766, 6 }, // #350 scx=Multani:Mult + { 18772, 7 }, // #351 scx=Old_Hungarian:Hung + { 18779, 7 }, // #352 scx=Adlam:Adlm + { 18786, 9 }, // #353 scx=Newa + { 18795, 6 }, // #354 scx=Osage:Osge + { 18801, 6 }, // #355 scx=Tangut:Tang + { 18807, 8 }, // #356 scx=Masaram_Gondi:Gonm + { 18815, 3 }, // #357 scx=Dogra:Dogr + { 18818, 8 }, // #358 scx=Gunjala_Gondi:Gong + { 18826, 7 }, // #359 scx=Hanifi_Rohingya:Rohg + { 18833, 2 }, // #360 scx=Sogdian:Sogd + { 18835, 10 }, // #361 scx=Nandinagari:Nand + { 18845, 7 }, // #362 scx=Yezidi:Yezi + { 18852, 2 }, // #363 scx=Cypro_Minoan:Cpmn + { 18854, 3 }, // #364 scx=Old_Uyghur:Ougr + { 18857, 2 }, // #365 scx=Toto + { 18859, 6 }, // #366 scx=Garay:Gara + { 18865, 2 }, // #367 scx=Gurung_Khema:Gukh + { 18867, 3 }, // #368 scx=Ol_Onal:Onao + { 18870, 8 }, // #369 scx=Sunuwar:Sunu + { 18878, 7 }, // #370 scx=Todhri:Todr + { 18885, 16 }, // #371 scx=Tulu_Tigalari:Tutg + { 18901, 7352 }, // #372 bp=RGI_Emoji + { 18901, 683 }, // #373 bp=Basic_Emoji + { 19584, 24 }, // #374 bp=Emoji_Keycap_Sequence + { 19608, 998 }, // #375 bp=RGI_Emoji_Modifier_Sequence + { 20606, 389 }, // #376 bp=RGI_Emoji_Flag_Sequence + { 20995, 12 }, // #377 bp=RGI_Emoji_Tag_Sequence + { 21007, 5246 } // #378 bp=RGI_Emoji_ZWJ_Sequence +}; + +template +const T3 unicode_property_data::rangetable[] = +{ + // #5 (0+762): gc=Other:C + // Cc:2 + Cf:21 + Cn:735 + Co:3 + Cs:1 + // #6 (0+2): gc=Control:Cc:cntrl + 0x0000, 0x001F, 0x007F, 0x009F, + // #7 (2+21): gc=Format:Cf + 0x00AD, 0x00AD, 0x0600, 0x0605, 0x061C, 0x061C, 0x06DD, 0x06DD, + 0x070F, 0x070F, 0x0890, 0x0891, 0x08E2, 0x08E2, 0x180E, 0x180E, + 0x200B, 0x200F, 0x202A, 0x202E, 0x2060, 0x2064, 0x2066, 0x206F, + 0xFEFF, 0xFEFF, 0xFFF9, 0xFFFB, 0x110BD, 0x110BD, 0x110CD, 0x110CD, + 0x13430, 0x1343F, 0x1BCA0, 0x1BCA3, 0x1D173, 0x1D17A, 0xE0001, 0xE0001, + 0xE0020, 0xE007F, + // #8 (23+735): gc=Unassigned:Cn + 0x0378, 0x0379, 0x0380, 0x0383, 0x038B, 0x038B, 0x038D, 0x038D, + 0x03A2, 0x03A2, 0x0530, 0x0530, 0x0557, 0x0558, 0x058B, 0x058C, + 0x0590, 0x0590, 0x05C8, 0x05CF, 0x05EB, 0x05EE, 0x05F5, 0x05FF, + 0x070E, 0x070E, 0x074B, 0x074C, 0x07B2, 0x07BF, 0x07FB, 0x07FC, + 0x082E, 0x082F, 0x083F, 0x083F, 0x085C, 0x085D, 0x085F, 0x085F, + 0x086B, 0x086F, 0x0892, 0x0896, 0x0984, 0x0984, 0x098D, 0x098E, + 0x0991, 0x0992, 0x09A9, 0x09A9, 0x09B1, 0x09B1, 0x09B3, 0x09B5, + 0x09BA, 0x09BB, 0x09C5, 0x09C6, 0x09C9, 0x09CA, 0x09CF, 0x09D6, + 0x09D8, 0x09DB, 0x09DE, 0x09DE, 0x09E4, 0x09E5, 0x09FF, 0x0A00, + 0x0A04, 0x0A04, 0x0A0B, 0x0A0E, 0x0A11, 0x0A12, 0x0A29, 0x0A29, + 0x0A31, 0x0A31, 0x0A34, 0x0A34, 0x0A37, 0x0A37, 0x0A3A, 0x0A3B, + 0x0A3D, 0x0A3D, 0x0A43, 0x0A46, 0x0A49, 0x0A4A, 0x0A4E, 0x0A50, + 0x0A52, 0x0A58, 0x0A5D, 0x0A5D, 0x0A5F, 0x0A65, 0x0A77, 0x0A80, + 0x0A84, 0x0A84, 0x0A8E, 0x0A8E, 0x0A92, 0x0A92, 0x0AA9, 0x0AA9, + 0x0AB1, 0x0AB1, 0x0AB4, 0x0AB4, 0x0ABA, 0x0ABB, 0x0AC6, 0x0AC6, + 0x0ACA, 0x0ACA, 0x0ACE, 0x0ACF, 0x0AD1, 0x0ADF, 0x0AE4, 0x0AE5, + 0x0AF2, 0x0AF8, 0x0B00, 0x0B00, 0x0B04, 0x0B04, 0x0B0D, 0x0B0E, + 0x0B11, 0x0B12, 0x0B29, 0x0B29, 0x0B31, 0x0B31, 0x0B34, 0x0B34, + 0x0B3A, 0x0B3B, 0x0B45, 0x0B46, 0x0B49, 0x0B4A, 0x0B4E, 0x0B54, + 0x0B58, 0x0B5B, 0x0B5E, 0x0B5E, 0x0B64, 0x0B65, 0x0B78, 0x0B81, + 0x0B84, 0x0B84, 0x0B8B, 0x0B8D, 0x0B91, 0x0B91, 0x0B96, 0x0B98, + 0x0B9B, 0x0B9B, 0x0B9D, 0x0B9D, 0x0BA0, 0x0BA2, 0x0BA5, 0x0BA7, + 0x0BAB, 0x0BAD, 0x0BBA, 0x0BBD, 0x0BC3, 0x0BC5, 0x0BC9, 0x0BC9, + 0x0BCE, 0x0BCF, 0x0BD1, 0x0BD6, 0x0BD8, 0x0BE5, 0x0BFB, 0x0BFF, + 0x0C0D, 0x0C0D, 0x0C11, 0x0C11, 0x0C29, 0x0C29, 0x0C3A, 0x0C3B, + 0x0C45, 0x0C45, 0x0C49, 0x0C49, 0x0C4E, 0x0C54, 0x0C57, 0x0C57, + 0x0C5B, 0x0C5B, 0x0C5E, 0x0C5F, 0x0C64, 0x0C65, 0x0C70, 0x0C76, + 0x0C8D, 0x0C8D, 0x0C91, 0x0C91, 0x0CA9, 0x0CA9, 0x0CB4, 0x0CB4, + 0x0CBA, 0x0CBB, 0x0CC5, 0x0CC5, 0x0CC9, 0x0CC9, 0x0CCE, 0x0CD4, + 0x0CD7, 0x0CDB, 0x0CDF, 0x0CDF, 0x0CE4, 0x0CE5, 0x0CF0, 0x0CF0, + 0x0CF4, 0x0CFF, 0x0D0D, 0x0D0D, 0x0D11, 0x0D11, 0x0D45, 0x0D45, + 0x0D49, 0x0D49, 0x0D50, 0x0D53, 0x0D64, 0x0D65, 0x0D80, 0x0D80, + 0x0D84, 0x0D84, 0x0D97, 0x0D99, 0x0DB2, 0x0DB2, 0x0DBC, 0x0DBC, + 0x0DBE, 0x0DBF, 0x0DC7, 0x0DC9, 0x0DCB, 0x0DCE, 0x0DD5, 0x0DD5, + 0x0DD7, 0x0DD7, 0x0DE0, 0x0DE5, 0x0DF0, 0x0DF1, 0x0DF5, 0x0E00, + 0x0E3B, 0x0E3E, 0x0E5C, 0x0E80, 0x0E83, 0x0E83, 0x0E85, 0x0E85, + 0x0E8B, 0x0E8B, 0x0EA4, 0x0EA4, 0x0EA6, 0x0EA6, 0x0EBE, 0x0EBF, + 0x0EC5, 0x0EC5, 0x0EC7, 0x0EC7, 0x0ECF, 0x0ECF, 0x0EDA, 0x0EDB, + 0x0EE0, 0x0EFF, 0x0F48, 0x0F48, 0x0F6D, 0x0F70, 0x0F98, 0x0F98, + 0x0FBD, 0x0FBD, 0x0FCD, 0x0FCD, 0x0FDB, 0x0FFF, 0x10C6, 0x10C6, + 0x10C8, 0x10CC, 0x10CE, 0x10CF, 0x1249, 0x1249, 0x124E, 0x124F, + 0x1257, 0x1257, 0x1259, 0x1259, 0x125E, 0x125F, 0x1289, 0x1289, + 0x128E, 0x128F, 0x12B1, 0x12B1, 0x12B6, 0x12B7, 0x12BF, 0x12BF, + 0x12C1, 0x12C1, 0x12C6, 0x12C7, 0x12D7, 0x12D7, 0x1311, 0x1311, + 0x1316, 0x1317, 0x135B, 0x135C, 0x137D, 0x137F, 0x139A, 0x139F, + 0x13F6, 0x13F7, 0x13FE, 0x13FF, 0x169D, 0x169F, 0x16F9, 0x16FF, + 0x1716, 0x171E, 0x1737, 0x173F, 0x1754, 0x175F, 0x176D, 0x176D, + 0x1771, 0x1771, 0x1774, 0x177F, 0x17DE, 0x17DF, 0x17EA, 0x17EF, + 0x17FA, 0x17FF, 0x181A, 0x181F, 0x1879, 0x187F, 0x18AB, 0x18AF, + 0x18F6, 0x18FF, 0x191F, 0x191F, 0x192C, 0x192F, 0x193C, 0x193F, + 0x1941, 0x1943, 0x196E, 0x196F, 0x1975, 0x197F, 0x19AC, 0x19AF, + 0x19CA, 0x19CF, 0x19DB, 0x19DD, 0x1A1C, 0x1A1D, 0x1A5F, 0x1A5F, + 0x1A7D, 0x1A7E, 0x1A8A, 0x1A8F, 0x1A9A, 0x1A9F, 0x1AAE, 0x1AAF, + 0x1ADE, 0x1ADF, 0x1AEC, 0x1AFF, 0x1B4D, 0x1B4D, 0x1BF4, 0x1BFB, + 0x1C38, 0x1C3A, 0x1C4A, 0x1C4C, 0x1C8B, 0x1C8F, 0x1CBB, 0x1CBC, + 0x1CC8, 0x1CCF, 0x1CFB, 0x1CFF, 0x1F16, 0x1F17, 0x1F1E, 0x1F1F, + 0x1F46, 0x1F47, 0x1F4E, 0x1F4F, 0x1F58, 0x1F58, 0x1F5A, 0x1F5A, + 0x1F5C, 0x1F5C, 0x1F5E, 0x1F5E, 0x1F7E, 0x1F7F, 0x1FB5, 0x1FB5, + 0x1FC5, 0x1FC5, 0x1FD4, 0x1FD5, 0x1FDC, 0x1FDC, 0x1FF0, 0x1FF1, + 0x1FF5, 0x1FF5, 0x1FFF, 0x1FFF, 0x2065, 0x2065, 0x2072, 0x2073, + 0x208F, 0x208F, 0x209D, 0x209F, 0x20C2, 0x20CF, 0x20F1, 0x20FF, + 0x218C, 0x218F, 0x242A, 0x243F, 0x244B, 0x245F, 0x2B74, 0x2B75, + 0x2CF4, 0x2CF8, 0x2D26, 0x2D26, 0x2D28, 0x2D2C, 0x2D2E, 0x2D2F, + 0x2D68, 0x2D6E, 0x2D71, 0x2D7E, 0x2D97, 0x2D9F, 0x2DA7, 0x2DA7, + 0x2DAF, 0x2DAF, 0x2DB7, 0x2DB7, 0x2DBF, 0x2DBF, 0x2DC7, 0x2DC7, + 0x2DCF, 0x2DCF, 0x2DD7, 0x2DD7, 0x2DDF, 0x2DDF, 0x2E5E, 0x2E7F, + 0x2E9A, 0x2E9A, 0x2EF4, 0x2EFF, 0x2FD6, 0x2FEF, 0x3040, 0x3040, + 0x3097, 0x3098, 0x3100, 0x3104, 0x3130, 0x3130, 0x318F, 0x318F, + 0x31E6, 0x31EE, 0x321F, 0x321F, 0xA48D, 0xA48F, 0xA4C7, 0xA4CF, + 0xA62C, 0xA63F, 0xA6F8, 0xA6FF, 0xA7DD, 0xA7F0, 0xA82D, 0xA82F, + 0xA83A, 0xA83F, 0xA878, 0xA87F, 0xA8C6, 0xA8CD, 0xA8DA, 0xA8DF, + 0xA954, 0xA95E, 0xA97D, 0xA97F, 0xA9CE, 0xA9CE, 0xA9DA, 0xA9DD, + 0xA9FF, 0xA9FF, 0xAA37, 0xAA3F, 0xAA4E, 0xAA4F, 0xAA5A, 0xAA5B, + 0xAAC3, 0xAADA, 0xAAF7, 0xAB00, 0xAB07, 0xAB08, 0xAB0F, 0xAB10, + 0xAB17, 0xAB1F, 0xAB27, 0xAB27, 0xAB2F, 0xAB2F, 0xAB6C, 0xAB6F, + 0xABEE, 0xABEF, 0xABFA, 0xABFF, 0xD7A4, 0xD7AF, 0xD7C7, 0xD7CA, + 0xD7FC, 0xD7FF, 0xFA6E, 0xFA6F, 0xFADA, 0xFAFF, 0xFB07, 0xFB12, + 0xFB18, 0xFB1C, 0xFB37, 0xFB37, 0xFB3D, 0xFB3D, 0xFB3F, 0xFB3F, + 0xFB42, 0xFB42, 0xFB45, 0xFB45, 0xFDD0, 0xFDEF, 0xFE1A, 0xFE1F, + 0xFE53, 0xFE53, 0xFE67, 0xFE67, 0xFE6C, 0xFE6F, 0xFE75, 0xFE75, + 0xFEFD, 0xFEFE, 0xFF00, 0xFF00, 0xFFBF, 0xFFC1, 0xFFC8, 0xFFC9, + 0xFFD0, 0xFFD1, 0xFFD8, 0xFFD9, 0xFFDD, 0xFFDF, 0xFFE7, 0xFFE7, + 0xFFEF, 0xFFF8, 0xFFFE, 0xFFFF, 0x1000C, 0x1000C, 0x10027, 0x10027, + 0x1003B, 0x1003B, 0x1003E, 0x1003E, 0x1004E, 0x1004F, 0x1005E, 0x1007F, + 0x100FB, 0x100FF, 0x10103, 0x10106, 0x10134, 0x10136, 0x1018F, 0x1018F, + 0x1019D, 0x1019F, 0x101A1, 0x101CF, 0x101FE, 0x1027F, 0x1029D, 0x1029F, + 0x102D1, 0x102DF, 0x102FC, 0x102FF, 0x10324, 0x1032C, 0x1034B, 0x1034F, + 0x1037B, 0x1037F, 0x1039E, 0x1039E, 0x103C4, 0x103C7, 0x103D6, 0x103FF, + 0x1049E, 0x1049F, 0x104AA, 0x104AF, 0x104D4, 0x104D7, 0x104FC, 0x104FF, + 0x10528, 0x1052F, 0x10564, 0x1056E, 0x1057B, 0x1057B, 0x1058B, 0x1058B, + 0x10593, 0x10593, 0x10596, 0x10596, 0x105A2, 0x105A2, 0x105B2, 0x105B2, + 0x105BA, 0x105BA, 0x105BD, 0x105BF, 0x105F4, 0x105FF, 0x10737, 0x1073F, + 0x10756, 0x1075F, 0x10768, 0x1077F, 0x10786, 0x10786, 0x107B1, 0x107B1, + 0x107BB, 0x107FF, 0x10806, 0x10807, 0x10809, 0x10809, 0x10836, 0x10836, + 0x10839, 0x1083B, 0x1083D, 0x1083E, 0x10856, 0x10856, 0x1089F, 0x108A6, + 0x108B0, 0x108DF, 0x108F3, 0x108F3, 0x108F6, 0x108FA, 0x1091C, 0x1091E, + 0x1093A, 0x1093E, 0x1095A, 0x1097F, 0x109B8, 0x109BB, 0x109D0, 0x109D1, + 0x10A04, 0x10A04, 0x10A07, 0x10A0B, 0x10A14, 0x10A14, 0x10A18, 0x10A18, + 0x10A36, 0x10A37, 0x10A3B, 0x10A3E, 0x10A49, 0x10A4F, 0x10A59, 0x10A5F, + 0x10AA0, 0x10ABF, 0x10AE7, 0x10AEA, 0x10AF7, 0x10AFF, 0x10B36, 0x10B38, + 0x10B56, 0x10B57, 0x10B73, 0x10B77, 0x10B92, 0x10B98, 0x10B9D, 0x10BA8, + 0x10BB0, 0x10BFF, 0x10C49, 0x10C7F, 0x10CB3, 0x10CBF, 0x10CF3, 0x10CF9, + 0x10D28, 0x10D2F, 0x10D3A, 0x10D3F, 0x10D66, 0x10D68, 0x10D86, 0x10D8D, + 0x10D90, 0x10E5F, 0x10E7F, 0x10E7F, 0x10EAA, 0x10EAA, 0x10EAE, 0x10EAF, + 0x10EB2, 0x10EC1, 0x10EC8, 0x10ECF, 0x10ED9, 0x10EF9, 0x10F28, 0x10F2F, + 0x10F5A, 0x10F6F, 0x10F8A, 0x10FAF, 0x10FCC, 0x10FDF, 0x10FF7, 0x10FFF, + 0x1104E, 0x11051, 0x11076, 0x1107E, 0x110C3, 0x110CC, 0x110CE, 0x110CF, + 0x110E9, 0x110EF, 0x110FA, 0x110FF, 0x11135, 0x11135, 0x11148, 0x1114F, + 0x11177, 0x1117F, 0x111E0, 0x111E0, 0x111F5, 0x111FF, 0x11212, 0x11212, + 0x11242, 0x1127F, 0x11287, 0x11287, 0x11289, 0x11289, 0x1128E, 0x1128E, + 0x1129E, 0x1129E, 0x112AA, 0x112AF, 0x112EB, 0x112EF, 0x112FA, 0x112FF, + 0x11304, 0x11304, 0x1130D, 0x1130E, 0x11311, 0x11312, 0x11329, 0x11329, + 0x11331, 0x11331, 0x11334, 0x11334, 0x1133A, 0x1133A, 0x11345, 0x11346, + 0x11349, 0x1134A, 0x1134E, 0x1134F, 0x11351, 0x11356, 0x11358, 0x1135C, + 0x11364, 0x11365, 0x1136D, 0x1136F, 0x11375, 0x1137F, 0x1138A, 0x1138A, + 0x1138C, 0x1138D, 0x1138F, 0x1138F, 0x113B6, 0x113B6, 0x113C1, 0x113C1, + 0x113C3, 0x113C4, 0x113C6, 0x113C6, 0x113CB, 0x113CB, 0x113D6, 0x113D6, + 0x113D9, 0x113E0, 0x113E3, 0x113FF, 0x1145C, 0x1145C, 0x11462, 0x1147F, + 0x114C8, 0x114CF, 0x114DA, 0x1157F, 0x115B6, 0x115B7, 0x115DE, 0x115FF, + 0x11645, 0x1164F, 0x1165A, 0x1165F, 0x1166D, 0x1167F, 0x116BA, 0x116BF, + 0x116CA, 0x116CF, 0x116E4, 0x116FF, 0x1171B, 0x1171C, 0x1172C, 0x1172F, + 0x11747, 0x117FF, 0x1183C, 0x1189F, 0x118F3, 0x118FE, 0x11907, 0x11908, + 0x1190A, 0x1190B, 0x11914, 0x11914, 0x11917, 0x11917, 0x11936, 0x11936, + 0x11939, 0x1193A, 0x11947, 0x1194F, 0x1195A, 0x1199F, 0x119A8, 0x119A9, + 0x119D8, 0x119D9, 0x119E5, 0x119FF, 0x11A48, 0x11A4F, 0x11AA3, 0x11AAF, + 0x11AF9, 0x11AFF, 0x11B0A, 0x11B5F, 0x11B68, 0x11BBF, 0x11BE2, 0x11BEF, + 0x11BFA, 0x11BFF, 0x11C09, 0x11C09, 0x11C37, 0x11C37, 0x11C46, 0x11C4F, + 0x11C6D, 0x11C6F, 0x11C90, 0x11C91, 0x11CA8, 0x11CA8, 0x11CB7, 0x11CFF, + 0x11D07, 0x11D07, 0x11D0A, 0x11D0A, 0x11D37, 0x11D39, 0x11D3B, 0x11D3B, + 0x11D3E, 0x11D3E, 0x11D48, 0x11D4F, 0x11D5A, 0x11D5F, 0x11D66, 0x11D66, + 0x11D69, 0x11D69, 0x11D8F, 0x11D8F, 0x11D92, 0x11D92, 0x11D99, 0x11D9F, + 0x11DAA, 0x11DAF, 0x11DDC, 0x11DDF, 0x11DEA, 0x11EDF, 0x11EF9, 0x11EFF, + 0x11F11, 0x11F11, 0x11F3B, 0x11F3D, 0x11F5B, 0x11FAF, 0x11FB1, 0x11FBF, + 0x11FF2, 0x11FFE, 0x1239A, 0x123FF, 0x1246F, 0x1246F, 0x12475, 0x1247F, + 0x12544, 0x12F8F, 0x12FF3, 0x12FFF, 0x13456, 0x1345F, 0x143FB, 0x143FF, + 0x14647, 0x160FF, 0x1613A, 0x167FF, 0x16A39, 0x16A3F, 0x16A5F, 0x16A5F, + 0x16A6A, 0x16A6D, 0x16ABF, 0x16ABF, 0x16ACA, 0x16ACF, 0x16AEE, 0x16AEF, + 0x16AF6, 0x16AFF, 0x16B46, 0x16B4F, 0x16B5A, 0x16B5A, 0x16B62, 0x16B62, + 0x16B78, 0x16B7C, 0x16B90, 0x16D3F, 0x16D7A, 0x16E3F, 0x16E9B, 0x16E9F, + 0x16EB9, 0x16EBA, 0x16ED4, 0x16EFF, 0x16F4B, 0x16F4E, 0x16F88, 0x16F8E, + 0x16FA0, 0x16FDF, 0x16FE5, 0x16FEF, 0x16FF7, 0x16FFF, 0x18CD6, 0x18CFE, + 0x18D1F, 0x18D7F, 0x18DF3, 0x1AFEF, 0x1AFF4, 0x1AFF4, 0x1AFFC, 0x1AFFC, + 0x1AFFF, 0x1AFFF, 0x1B123, 0x1B131, 0x1B133, 0x1B14F, 0x1B153, 0x1B154, + 0x1B156, 0x1B163, 0x1B168, 0x1B16F, 0x1B2FC, 0x1BBFF, 0x1BC6B, 0x1BC6F, + 0x1BC7D, 0x1BC7F, 0x1BC89, 0x1BC8F, 0x1BC9A, 0x1BC9B, 0x1BCA4, 0x1CBFF, + 0x1CCFD, 0x1CCFF, 0x1CEB4, 0x1CEB9, 0x1CED1, 0x1CEDF, 0x1CEF1, 0x1CEFF, + 0x1CF2E, 0x1CF2F, 0x1CF47, 0x1CF4F, 0x1CFC4, 0x1CFFF, 0x1D0F6, 0x1D0FF, + 0x1D127, 0x1D128, 0x1D1EB, 0x1D1FF, 0x1D246, 0x1D2BF, 0x1D2D4, 0x1D2DF, + 0x1D2F4, 0x1D2FF, 0x1D357, 0x1D35F, 0x1D379, 0x1D3FF, 0x1D455, 0x1D455, + 0x1D49D, 0x1D49D, 0x1D4A0, 0x1D4A1, 0x1D4A3, 0x1D4A4, 0x1D4A7, 0x1D4A8, + 0x1D4AD, 0x1D4AD, 0x1D4BA, 0x1D4BA, 0x1D4BC, 0x1D4BC, 0x1D4C4, 0x1D4C4, + 0x1D506, 0x1D506, 0x1D50B, 0x1D50C, 0x1D515, 0x1D515, 0x1D51D, 0x1D51D, + 0x1D53A, 0x1D53A, 0x1D53F, 0x1D53F, 0x1D545, 0x1D545, 0x1D547, 0x1D549, + 0x1D551, 0x1D551, 0x1D6A6, 0x1D6A7, 0x1D7CC, 0x1D7CD, 0x1DA8C, 0x1DA9A, + 0x1DAA0, 0x1DAA0, 0x1DAB0, 0x1DEFF, 0x1DF1F, 0x1DF24, 0x1DF2B, 0x1DFFF, + 0x1E007, 0x1E007, 0x1E019, 0x1E01A, 0x1E022, 0x1E022, 0x1E025, 0x1E025, + 0x1E02B, 0x1E02F, 0x1E06E, 0x1E08E, 0x1E090, 0x1E0FF, 0x1E12D, 0x1E12F, + 0x1E13E, 0x1E13F, 0x1E14A, 0x1E14D, 0x1E150, 0x1E28F, 0x1E2AF, 0x1E2BF, + 0x1E2FA, 0x1E2FE, 0x1E300, 0x1E4CF, 0x1E4FA, 0x1E5CF, 0x1E5FB, 0x1E5FE, + 0x1E600, 0x1E6BF, 0x1E6DF, 0x1E6DF, 0x1E6F6, 0x1E6FD, 0x1E700, 0x1E7DF, + 0x1E7E7, 0x1E7E7, 0x1E7EC, 0x1E7EC, 0x1E7EF, 0x1E7EF, 0x1E7FF, 0x1E7FF, + 0x1E8C5, 0x1E8C6, 0x1E8D7, 0x1E8FF, 0x1E94C, 0x1E94F, 0x1E95A, 0x1E95D, + 0x1E960, 0x1EC70, 0x1ECB5, 0x1ED00, 0x1ED3E, 0x1EDFF, 0x1EE04, 0x1EE04, + 0x1EE20, 0x1EE20, 0x1EE23, 0x1EE23, 0x1EE25, 0x1EE26, 0x1EE28, 0x1EE28, + 0x1EE33, 0x1EE33, 0x1EE38, 0x1EE38, 0x1EE3A, 0x1EE3A, 0x1EE3C, 0x1EE41, + 0x1EE43, 0x1EE46, 0x1EE48, 0x1EE48, 0x1EE4A, 0x1EE4A, 0x1EE4C, 0x1EE4C, + 0x1EE50, 0x1EE50, 0x1EE53, 0x1EE53, 0x1EE55, 0x1EE56, 0x1EE58, 0x1EE58, + 0x1EE5A, 0x1EE5A, 0x1EE5C, 0x1EE5C, 0x1EE5E, 0x1EE5E, 0x1EE60, 0x1EE60, + 0x1EE63, 0x1EE63, 0x1EE65, 0x1EE66, 0x1EE6B, 0x1EE6B, 0x1EE73, 0x1EE73, + 0x1EE78, 0x1EE78, 0x1EE7D, 0x1EE7D, 0x1EE7F, 0x1EE7F, 0x1EE8A, 0x1EE8A, + 0x1EE9C, 0x1EEA0, 0x1EEA4, 0x1EEA4, 0x1EEAA, 0x1EEAA, 0x1EEBC, 0x1EEEF, + 0x1EEF2, 0x1EFFF, 0x1F02C, 0x1F02F, 0x1F094, 0x1F09F, 0x1F0AF, 0x1F0B0, + 0x1F0C0, 0x1F0C0, 0x1F0D0, 0x1F0D0, 0x1F0F6, 0x1F0FF, 0x1F1AE, 0x1F1E5, + 0x1F203, 0x1F20F, 0x1F23C, 0x1F23F, 0x1F249, 0x1F24F, 0x1F252, 0x1F25F, + 0x1F266, 0x1F2FF, 0x1F6D9, 0x1F6DB, 0x1F6ED, 0x1F6EF, 0x1F6FD, 0x1F6FF, + 0x1F7DA, 0x1F7DF, 0x1F7EC, 0x1F7EF, 0x1F7F1, 0x1F7FF, 0x1F80C, 0x1F80F, + 0x1F848, 0x1F84F, 0x1F85A, 0x1F85F, 0x1F888, 0x1F88F, 0x1F8AE, 0x1F8AF, + 0x1F8BC, 0x1F8BF, 0x1F8C2, 0x1F8CF, 0x1F8D9, 0x1F8FF, 0x1FA58, 0x1FA5F, + 0x1FA6E, 0x1FA6F, 0x1FA7D, 0x1FA7F, 0x1FA8B, 0x1FA8D, 0x1FAC7, 0x1FAC7, + 0x1FAC9, 0x1FACC, 0x1FADD, 0x1FADE, 0x1FAEB, 0x1FAEE, 0x1FAF9, 0x1FAFF, + 0x1FB93, 0x1FB93, 0x1FBFB, 0x1FFFF, 0x2A6E0, 0x2A6FF, 0x2B81E, 0x2B81F, + 0x2CEAE, 0x2CEAF, 0x2EBE1, 0x2EBEF, 0x2EE5E, 0x2F7FF, 0x2FA1E, 0x2FFFF, + 0x3134B, 0x3134F, 0x3347A, 0xE0000, 0xE0002, 0xE001F, 0xE0080, 0xE00FF, + 0xE01F0, 0xEFFFF, 0xFFFFE, 0xFFFFF, 0x10FFFE, 0x10FFFF, + // #9 (758+3): gc=Private_Use:Co + 0xE000, 0xF8FF, 0xF0000, 0xFFFFD, 0x100000, 0x10FFFD, + // #10 (761+1): gc=Surrogate:Cs + 0xD800, 0xDFFF, + // #11 (762+1945): gc=Letter:L + // Ll:664 + Lt:10 + Lu:655 + Lm:79 + Lo:537 + // #12 (762+1329): gc=Cased_Letter:LC + // Ll:664 + Lt:10 + Lu:655 + // #13 (762+664): gc=Lowercase_Letter:Ll + 0x0061, 0x007A, 0x00B5, 0x00B5, 0x00DF, 0x00F6, 0x00F8, 0x00FF, + 0x0101, 0x0101, 0x0103, 0x0103, 0x0105, 0x0105, 0x0107, 0x0107, + 0x0109, 0x0109, 0x010B, 0x010B, 0x010D, 0x010D, 0x010F, 0x010F, + 0x0111, 0x0111, 0x0113, 0x0113, 0x0115, 0x0115, 0x0117, 0x0117, + 0x0119, 0x0119, 0x011B, 0x011B, 0x011D, 0x011D, 0x011F, 0x011F, + 0x0121, 0x0121, 0x0123, 0x0123, 0x0125, 0x0125, 0x0127, 0x0127, + 0x0129, 0x0129, 0x012B, 0x012B, 0x012D, 0x012D, 0x012F, 0x012F, + 0x0131, 0x0131, 0x0133, 0x0133, 0x0135, 0x0135, 0x0137, 0x0138, + 0x013A, 0x013A, 0x013C, 0x013C, 0x013E, 0x013E, 0x0140, 0x0140, + 0x0142, 0x0142, 0x0144, 0x0144, 0x0146, 0x0146, 0x0148, 0x0149, + 0x014B, 0x014B, 0x014D, 0x014D, 0x014F, 0x014F, 0x0151, 0x0151, + 0x0153, 0x0153, 0x0155, 0x0155, 0x0157, 0x0157, 0x0159, 0x0159, + 0x015B, 0x015B, 0x015D, 0x015D, 0x015F, 0x015F, 0x0161, 0x0161, + 0x0163, 0x0163, 0x0165, 0x0165, 0x0167, 0x0167, 0x0169, 0x0169, + 0x016B, 0x016B, 0x016D, 0x016D, 0x016F, 0x016F, 0x0171, 0x0171, + 0x0173, 0x0173, 0x0175, 0x0175, 0x0177, 0x0177, 0x017A, 0x017A, + 0x017C, 0x017C, 0x017E, 0x0180, 0x0183, 0x0183, 0x0185, 0x0185, + 0x0188, 0x0188, 0x018C, 0x018D, 0x0192, 0x0192, 0x0195, 0x0195, + 0x0199, 0x019B, 0x019E, 0x019E, 0x01A1, 0x01A1, 0x01A3, 0x01A3, + 0x01A5, 0x01A5, 0x01A8, 0x01A8, 0x01AA, 0x01AB, 0x01AD, 0x01AD, + 0x01B0, 0x01B0, 0x01B4, 0x01B4, 0x01B6, 0x01B6, 0x01B9, 0x01BA, + 0x01BD, 0x01BF, 0x01C6, 0x01C6, 0x01C9, 0x01C9, 0x01CC, 0x01CC, + 0x01CE, 0x01CE, 0x01D0, 0x01D0, 0x01D2, 0x01D2, 0x01D4, 0x01D4, + 0x01D6, 0x01D6, 0x01D8, 0x01D8, 0x01DA, 0x01DA, 0x01DC, 0x01DD, + 0x01DF, 0x01DF, 0x01E1, 0x01E1, 0x01E3, 0x01E3, 0x01E5, 0x01E5, + 0x01E7, 0x01E7, 0x01E9, 0x01E9, 0x01EB, 0x01EB, 0x01ED, 0x01ED, + 0x01EF, 0x01F0, 0x01F3, 0x01F3, 0x01F5, 0x01F5, 0x01F9, 0x01F9, + 0x01FB, 0x01FB, 0x01FD, 0x01FD, 0x01FF, 0x01FF, 0x0201, 0x0201, + 0x0203, 0x0203, 0x0205, 0x0205, 0x0207, 0x0207, 0x0209, 0x0209, + 0x020B, 0x020B, 0x020D, 0x020D, 0x020F, 0x020F, 0x0211, 0x0211, + 0x0213, 0x0213, 0x0215, 0x0215, 0x0217, 0x0217, 0x0219, 0x0219, + 0x021B, 0x021B, 0x021D, 0x021D, 0x021F, 0x021F, 0x0221, 0x0221, + 0x0223, 0x0223, 0x0225, 0x0225, 0x0227, 0x0227, 0x0229, 0x0229, + 0x022B, 0x022B, 0x022D, 0x022D, 0x022F, 0x022F, 0x0231, 0x0231, + 0x0233, 0x0239, 0x023C, 0x023C, 0x023F, 0x0240, 0x0242, 0x0242, + 0x0247, 0x0247, 0x0249, 0x0249, 0x024B, 0x024B, 0x024D, 0x024D, + 0x024F, 0x0293, 0x0296, 0x02AF, 0x0371, 0x0371, 0x0373, 0x0373, + 0x0377, 0x0377, 0x037B, 0x037D, 0x0390, 0x0390, 0x03AC, 0x03CE, + 0x03D0, 0x03D1, 0x03D5, 0x03D7, 0x03D9, 0x03D9, 0x03DB, 0x03DB, + 0x03DD, 0x03DD, 0x03DF, 0x03DF, 0x03E1, 0x03E1, 0x03E3, 0x03E3, + 0x03E5, 0x03E5, 0x03E7, 0x03E7, 0x03E9, 0x03E9, 0x03EB, 0x03EB, + 0x03ED, 0x03ED, 0x03EF, 0x03F3, 0x03F5, 0x03F5, 0x03F8, 0x03F8, + 0x03FB, 0x03FC, 0x0430, 0x045F, 0x0461, 0x0461, 0x0463, 0x0463, + 0x0465, 0x0465, 0x0467, 0x0467, 0x0469, 0x0469, 0x046B, 0x046B, + 0x046D, 0x046D, 0x046F, 0x046F, 0x0471, 0x0471, 0x0473, 0x0473, + 0x0475, 0x0475, 0x0477, 0x0477, 0x0479, 0x0479, 0x047B, 0x047B, + 0x047D, 0x047D, 0x047F, 0x047F, 0x0481, 0x0481, 0x048B, 0x048B, + 0x048D, 0x048D, 0x048F, 0x048F, 0x0491, 0x0491, 0x0493, 0x0493, + 0x0495, 0x0495, 0x0497, 0x0497, 0x0499, 0x0499, 0x049B, 0x049B, + 0x049D, 0x049D, 0x049F, 0x049F, 0x04A1, 0x04A1, 0x04A3, 0x04A3, + 0x04A5, 0x04A5, 0x04A7, 0x04A7, 0x04A9, 0x04A9, 0x04AB, 0x04AB, + 0x04AD, 0x04AD, 0x04AF, 0x04AF, 0x04B1, 0x04B1, 0x04B3, 0x04B3, + 0x04B5, 0x04B5, 0x04B7, 0x04B7, 0x04B9, 0x04B9, 0x04BB, 0x04BB, + 0x04BD, 0x04BD, 0x04BF, 0x04BF, 0x04C2, 0x04C2, 0x04C4, 0x04C4, + 0x04C6, 0x04C6, 0x04C8, 0x04C8, 0x04CA, 0x04CA, 0x04CC, 0x04CC, + 0x04CE, 0x04CF, 0x04D1, 0x04D1, 0x04D3, 0x04D3, 0x04D5, 0x04D5, + 0x04D7, 0x04D7, 0x04D9, 0x04D9, 0x04DB, 0x04DB, 0x04DD, 0x04DD, + 0x04DF, 0x04DF, 0x04E1, 0x04E1, 0x04E3, 0x04E3, 0x04E5, 0x04E5, + 0x04E7, 0x04E7, 0x04E9, 0x04E9, 0x04EB, 0x04EB, 0x04ED, 0x04ED, + 0x04EF, 0x04EF, 0x04F1, 0x04F1, 0x04F3, 0x04F3, 0x04F5, 0x04F5, + 0x04F7, 0x04F7, 0x04F9, 0x04F9, 0x04FB, 0x04FB, 0x04FD, 0x04FD, + 0x04FF, 0x04FF, 0x0501, 0x0501, 0x0503, 0x0503, 0x0505, 0x0505, + 0x0507, 0x0507, 0x0509, 0x0509, 0x050B, 0x050B, 0x050D, 0x050D, + 0x050F, 0x050F, 0x0511, 0x0511, 0x0513, 0x0513, 0x0515, 0x0515, + 0x0517, 0x0517, 0x0519, 0x0519, 0x051B, 0x051B, 0x051D, 0x051D, + 0x051F, 0x051F, 0x0521, 0x0521, 0x0523, 0x0523, 0x0525, 0x0525, + 0x0527, 0x0527, 0x0529, 0x0529, 0x052B, 0x052B, 0x052D, 0x052D, + 0x052F, 0x052F, 0x0560, 0x0588, 0x10D0, 0x10FA, 0x10FD, 0x10FF, + 0x13F8, 0x13FD, 0x1C80, 0x1C88, 0x1C8A, 0x1C8A, 0x1D00, 0x1D2B, + 0x1D6B, 0x1D77, 0x1D79, 0x1D9A, 0x1E01, 0x1E01, 0x1E03, 0x1E03, + 0x1E05, 0x1E05, 0x1E07, 0x1E07, 0x1E09, 0x1E09, 0x1E0B, 0x1E0B, + 0x1E0D, 0x1E0D, 0x1E0F, 0x1E0F, 0x1E11, 0x1E11, 0x1E13, 0x1E13, + 0x1E15, 0x1E15, 0x1E17, 0x1E17, 0x1E19, 0x1E19, 0x1E1B, 0x1E1B, + 0x1E1D, 0x1E1D, 0x1E1F, 0x1E1F, 0x1E21, 0x1E21, 0x1E23, 0x1E23, + 0x1E25, 0x1E25, 0x1E27, 0x1E27, 0x1E29, 0x1E29, 0x1E2B, 0x1E2B, + 0x1E2D, 0x1E2D, 0x1E2F, 0x1E2F, 0x1E31, 0x1E31, 0x1E33, 0x1E33, + 0x1E35, 0x1E35, 0x1E37, 0x1E37, 0x1E39, 0x1E39, 0x1E3B, 0x1E3B, + 0x1E3D, 0x1E3D, 0x1E3F, 0x1E3F, 0x1E41, 0x1E41, 0x1E43, 0x1E43, + 0x1E45, 0x1E45, 0x1E47, 0x1E47, 0x1E49, 0x1E49, 0x1E4B, 0x1E4B, + 0x1E4D, 0x1E4D, 0x1E4F, 0x1E4F, 0x1E51, 0x1E51, 0x1E53, 0x1E53, + 0x1E55, 0x1E55, 0x1E57, 0x1E57, 0x1E59, 0x1E59, 0x1E5B, 0x1E5B, + 0x1E5D, 0x1E5D, 0x1E5F, 0x1E5F, 0x1E61, 0x1E61, 0x1E63, 0x1E63, + 0x1E65, 0x1E65, 0x1E67, 0x1E67, 0x1E69, 0x1E69, 0x1E6B, 0x1E6B, + 0x1E6D, 0x1E6D, 0x1E6F, 0x1E6F, 0x1E71, 0x1E71, 0x1E73, 0x1E73, + 0x1E75, 0x1E75, 0x1E77, 0x1E77, 0x1E79, 0x1E79, 0x1E7B, 0x1E7B, + 0x1E7D, 0x1E7D, 0x1E7F, 0x1E7F, 0x1E81, 0x1E81, 0x1E83, 0x1E83, + 0x1E85, 0x1E85, 0x1E87, 0x1E87, 0x1E89, 0x1E89, 0x1E8B, 0x1E8B, + 0x1E8D, 0x1E8D, 0x1E8F, 0x1E8F, 0x1E91, 0x1E91, 0x1E93, 0x1E93, + 0x1E95, 0x1E9D, 0x1E9F, 0x1E9F, 0x1EA1, 0x1EA1, 0x1EA3, 0x1EA3, + 0x1EA5, 0x1EA5, 0x1EA7, 0x1EA7, 0x1EA9, 0x1EA9, 0x1EAB, 0x1EAB, + 0x1EAD, 0x1EAD, 0x1EAF, 0x1EAF, 0x1EB1, 0x1EB1, 0x1EB3, 0x1EB3, + 0x1EB5, 0x1EB5, 0x1EB7, 0x1EB7, 0x1EB9, 0x1EB9, 0x1EBB, 0x1EBB, + 0x1EBD, 0x1EBD, 0x1EBF, 0x1EBF, 0x1EC1, 0x1EC1, 0x1EC3, 0x1EC3, + 0x1EC5, 0x1EC5, 0x1EC7, 0x1EC7, 0x1EC9, 0x1EC9, 0x1ECB, 0x1ECB, + 0x1ECD, 0x1ECD, 0x1ECF, 0x1ECF, 0x1ED1, 0x1ED1, 0x1ED3, 0x1ED3, + 0x1ED5, 0x1ED5, 0x1ED7, 0x1ED7, 0x1ED9, 0x1ED9, 0x1EDB, 0x1EDB, + 0x1EDD, 0x1EDD, 0x1EDF, 0x1EDF, 0x1EE1, 0x1EE1, 0x1EE3, 0x1EE3, + 0x1EE5, 0x1EE5, 0x1EE7, 0x1EE7, 0x1EE9, 0x1EE9, 0x1EEB, 0x1EEB, + 0x1EED, 0x1EED, 0x1EEF, 0x1EEF, 0x1EF1, 0x1EF1, 0x1EF3, 0x1EF3, + 0x1EF5, 0x1EF5, 0x1EF7, 0x1EF7, 0x1EF9, 0x1EF9, 0x1EFB, 0x1EFB, + 0x1EFD, 0x1EFD, 0x1EFF, 0x1F07, 0x1F10, 0x1F15, 0x1F20, 0x1F27, + 0x1F30, 0x1F37, 0x1F40, 0x1F45, 0x1F50, 0x1F57, 0x1F60, 0x1F67, + 0x1F70, 0x1F7D, 0x1F80, 0x1F87, 0x1F90, 0x1F97, 0x1FA0, 0x1FA7, + 0x1FB0, 0x1FB4, 0x1FB6, 0x1FB7, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, + 0x1FC6, 0x1FC7, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FD7, 0x1FE0, 0x1FE7, + 0x1FF2, 0x1FF4, 0x1FF6, 0x1FF7, 0x210A, 0x210A, 0x210E, 0x210F, + 0x2113, 0x2113, 0x212F, 0x212F, 0x2134, 0x2134, 0x2139, 0x2139, + 0x213C, 0x213D, 0x2146, 0x2149, 0x214E, 0x214E, 0x2184, 0x2184, + 0x2C30, 0x2C5F, 0x2C61, 0x2C61, 0x2C65, 0x2C66, 0x2C68, 0x2C68, + 0x2C6A, 0x2C6A, 0x2C6C, 0x2C6C, 0x2C71, 0x2C71, 0x2C73, 0x2C74, + 0x2C76, 0x2C7B, 0x2C81, 0x2C81, 0x2C83, 0x2C83, 0x2C85, 0x2C85, + 0x2C87, 0x2C87, 0x2C89, 0x2C89, 0x2C8B, 0x2C8B, 0x2C8D, 0x2C8D, + 0x2C8F, 0x2C8F, 0x2C91, 0x2C91, 0x2C93, 0x2C93, 0x2C95, 0x2C95, + 0x2C97, 0x2C97, 0x2C99, 0x2C99, 0x2C9B, 0x2C9B, 0x2C9D, 0x2C9D, + 0x2C9F, 0x2C9F, 0x2CA1, 0x2CA1, 0x2CA3, 0x2CA3, 0x2CA5, 0x2CA5, + 0x2CA7, 0x2CA7, 0x2CA9, 0x2CA9, 0x2CAB, 0x2CAB, 0x2CAD, 0x2CAD, + 0x2CAF, 0x2CAF, 0x2CB1, 0x2CB1, 0x2CB3, 0x2CB3, 0x2CB5, 0x2CB5, + 0x2CB7, 0x2CB7, 0x2CB9, 0x2CB9, 0x2CBB, 0x2CBB, 0x2CBD, 0x2CBD, + 0x2CBF, 0x2CBF, 0x2CC1, 0x2CC1, 0x2CC3, 0x2CC3, 0x2CC5, 0x2CC5, + 0x2CC7, 0x2CC7, 0x2CC9, 0x2CC9, 0x2CCB, 0x2CCB, 0x2CCD, 0x2CCD, + 0x2CCF, 0x2CCF, 0x2CD1, 0x2CD1, 0x2CD3, 0x2CD3, 0x2CD5, 0x2CD5, + 0x2CD7, 0x2CD7, 0x2CD9, 0x2CD9, 0x2CDB, 0x2CDB, 0x2CDD, 0x2CDD, + 0x2CDF, 0x2CDF, 0x2CE1, 0x2CE1, 0x2CE3, 0x2CE4, 0x2CEC, 0x2CEC, + 0x2CEE, 0x2CEE, 0x2CF3, 0x2CF3, 0x2D00, 0x2D25, 0x2D27, 0x2D27, + 0x2D2D, 0x2D2D, 0xA641, 0xA641, 0xA643, 0xA643, 0xA645, 0xA645, + 0xA647, 0xA647, 0xA649, 0xA649, 0xA64B, 0xA64B, 0xA64D, 0xA64D, + 0xA64F, 0xA64F, 0xA651, 0xA651, 0xA653, 0xA653, 0xA655, 0xA655, + 0xA657, 0xA657, 0xA659, 0xA659, 0xA65B, 0xA65B, 0xA65D, 0xA65D, + 0xA65F, 0xA65F, 0xA661, 0xA661, 0xA663, 0xA663, 0xA665, 0xA665, + 0xA667, 0xA667, 0xA669, 0xA669, 0xA66B, 0xA66B, 0xA66D, 0xA66D, + 0xA681, 0xA681, 0xA683, 0xA683, 0xA685, 0xA685, 0xA687, 0xA687, + 0xA689, 0xA689, 0xA68B, 0xA68B, 0xA68D, 0xA68D, 0xA68F, 0xA68F, + 0xA691, 0xA691, 0xA693, 0xA693, 0xA695, 0xA695, 0xA697, 0xA697, + 0xA699, 0xA699, 0xA69B, 0xA69B, 0xA723, 0xA723, 0xA725, 0xA725, + 0xA727, 0xA727, 0xA729, 0xA729, 0xA72B, 0xA72B, 0xA72D, 0xA72D, + 0xA72F, 0xA731, 0xA733, 0xA733, 0xA735, 0xA735, 0xA737, 0xA737, + 0xA739, 0xA739, 0xA73B, 0xA73B, 0xA73D, 0xA73D, 0xA73F, 0xA73F, + 0xA741, 0xA741, 0xA743, 0xA743, 0xA745, 0xA745, 0xA747, 0xA747, + 0xA749, 0xA749, 0xA74B, 0xA74B, 0xA74D, 0xA74D, 0xA74F, 0xA74F, + 0xA751, 0xA751, 0xA753, 0xA753, 0xA755, 0xA755, 0xA757, 0xA757, + 0xA759, 0xA759, 0xA75B, 0xA75B, 0xA75D, 0xA75D, 0xA75F, 0xA75F, + 0xA761, 0xA761, 0xA763, 0xA763, 0xA765, 0xA765, 0xA767, 0xA767, + 0xA769, 0xA769, 0xA76B, 0xA76B, 0xA76D, 0xA76D, 0xA76F, 0xA76F, + 0xA771, 0xA778, 0xA77A, 0xA77A, 0xA77C, 0xA77C, 0xA77F, 0xA77F, + 0xA781, 0xA781, 0xA783, 0xA783, 0xA785, 0xA785, 0xA787, 0xA787, + 0xA78C, 0xA78C, 0xA78E, 0xA78E, 0xA791, 0xA791, 0xA793, 0xA795, + 0xA797, 0xA797, 0xA799, 0xA799, 0xA79B, 0xA79B, 0xA79D, 0xA79D, + 0xA79F, 0xA79F, 0xA7A1, 0xA7A1, 0xA7A3, 0xA7A3, 0xA7A5, 0xA7A5, + 0xA7A7, 0xA7A7, 0xA7A9, 0xA7A9, 0xA7AF, 0xA7AF, 0xA7B5, 0xA7B5, + 0xA7B7, 0xA7B7, 0xA7B9, 0xA7B9, 0xA7BB, 0xA7BB, 0xA7BD, 0xA7BD, + 0xA7BF, 0xA7BF, 0xA7C1, 0xA7C1, 0xA7C3, 0xA7C3, 0xA7C8, 0xA7C8, + 0xA7CA, 0xA7CA, 0xA7CD, 0xA7CD, 0xA7CF, 0xA7CF, 0xA7D1, 0xA7D1, + 0xA7D3, 0xA7D3, 0xA7D5, 0xA7D5, 0xA7D7, 0xA7D7, 0xA7D9, 0xA7D9, + 0xA7DB, 0xA7DB, 0xA7F6, 0xA7F6, 0xA7FA, 0xA7FA, 0xAB30, 0xAB5A, + 0xAB60, 0xAB68, 0xAB70, 0xABBF, 0xFB00, 0xFB06, 0xFB13, 0xFB17, + 0xFF41, 0xFF5A, 0x10428, 0x1044F, 0x104D8, 0x104FB, 0x10597, 0x105A1, + 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x10CC0, 0x10CF2, + 0x10D70, 0x10D85, 0x118C0, 0x118DF, 0x16E60, 0x16E7F, 0x16EBB, 0x16ED3, + 0x1D41A, 0x1D433, 0x1D44E, 0x1D454, 0x1D456, 0x1D467, 0x1D482, 0x1D49B, + 0x1D4B6, 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D4CF, + 0x1D4EA, 0x1D503, 0x1D51E, 0x1D537, 0x1D552, 0x1D56B, 0x1D586, 0x1D59F, + 0x1D5BA, 0x1D5D3, 0x1D5EE, 0x1D607, 0x1D622, 0x1D63B, 0x1D656, 0x1D66F, + 0x1D68A, 0x1D6A5, 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6E1, 0x1D6FC, 0x1D714, + 0x1D716, 0x1D71B, 0x1D736, 0x1D74E, 0x1D750, 0x1D755, 0x1D770, 0x1D788, + 0x1D78A, 0x1D78F, 0x1D7AA, 0x1D7C2, 0x1D7C4, 0x1D7C9, 0x1D7CB, 0x1D7CB, + 0x1DF00, 0x1DF09, 0x1DF0B, 0x1DF1E, 0x1DF25, 0x1DF2A, 0x1E922, 0x1E943, + // #14 (1426+10): gc=Titlecase_Letter:Lt + 0x01C5, 0x01C5, 0x01C8, 0x01C8, 0x01CB, 0x01CB, 0x01F2, 0x01F2, + 0x1F88, 0x1F8F, 0x1F98, 0x1F9F, 0x1FA8, 0x1FAF, 0x1FBC, 0x1FBC, + 0x1FCC, 0x1FCC, 0x1FFC, 0x1FFC, + // #15 (1436+655): gc=Uppercase_Letter:Lu + 0x0041, 0x005A, 0x00C0, 0x00D6, 0x00D8, 0x00DE, 0x0100, 0x0100, + 0x0102, 0x0102, 0x0104, 0x0104, 0x0106, 0x0106, 0x0108, 0x0108, + 0x010A, 0x010A, 0x010C, 0x010C, 0x010E, 0x010E, 0x0110, 0x0110, + 0x0112, 0x0112, 0x0114, 0x0114, 0x0116, 0x0116, 0x0118, 0x0118, + 0x011A, 0x011A, 0x011C, 0x011C, 0x011E, 0x011E, 0x0120, 0x0120, + 0x0122, 0x0122, 0x0124, 0x0124, 0x0126, 0x0126, 0x0128, 0x0128, + 0x012A, 0x012A, 0x012C, 0x012C, 0x012E, 0x012E, 0x0130, 0x0130, + 0x0132, 0x0132, 0x0134, 0x0134, 0x0136, 0x0136, 0x0139, 0x0139, + 0x013B, 0x013B, 0x013D, 0x013D, 0x013F, 0x013F, 0x0141, 0x0141, + 0x0143, 0x0143, 0x0145, 0x0145, 0x0147, 0x0147, 0x014A, 0x014A, + 0x014C, 0x014C, 0x014E, 0x014E, 0x0150, 0x0150, 0x0152, 0x0152, + 0x0154, 0x0154, 0x0156, 0x0156, 0x0158, 0x0158, 0x015A, 0x015A, + 0x015C, 0x015C, 0x015E, 0x015E, 0x0160, 0x0160, 0x0162, 0x0162, + 0x0164, 0x0164, 0x0166, 0x0166, 0x0168, 0x0168, 0x016A, 0x016A, + 0x016C, 0x016C, 0x016E, 0x016E, 0x0170, 0x0170, 0x0172, 0x0172, + 0x0174, 0x0174, 0x0176, 0x0176, 0x0178, 0x0179, 0x017B, 0x017B, + 0x017D, 0x017D, 0x0181, 0x0182, 0x0184, 0x0184, 0x0186, 0x0187, + 0x0189, 0x018B, 0x018E, 0x0191, 0x0193, 0x0194, 0x0196, 0x0198, + 0x019C, 0x019D, 0x019F, 0x01A0, 0x01A2, 0x01A2, 0x01A4, 0x01A4, + 0x01A6, 0x01A7, 0x01A9, 0x01A9, 0x01AC, 0x01AC, 0x01AE, 0x01AF, + 0x01B1, 0x01B3, 0x01B5, 0x01B5, 0x01B7, 0x01B8, 0x01BC, 0x01BC, + 0x01C4, 0x01C4, 0x01C7, 0x01C7, 0x01CA, 0x01CA, 0x01CD, 0x01CD, + 0x01CF, 0x01CF, 0x01D1, 0x01D1, 0x01D3, 0x01D3, 0x01D5, 0x01D5, + 0x01D7, 0x01D7, 0x01D9, 0x01D9, 0x01DB, 0x01DB, 0x01DE, 0x01DE, + 0x01E0, 0x01E0, 0x01E2, 0x01E2, 0x01E4, 0x01E4, 0x01E6, 0x01E6, + 0x01E8, 0x01E8, 0x01EA, 0x01EA, 0x01EC, 0x01EC, 0x01EE, 0x01EE, + 0x01F1, 0x01F1, 0x01F4, 0x01F4, 0x01F6, 0x01F8, 0x01FA, 0x01FA, + 0x01FC, 0x01FC, 0x01FE, 0x01FE, 0x0200, 0x0200, 0x0202, 0x0202, + 0x0204, 0x0204, 0x0206, 0x0206, 0x0208, 0x0208, 0x020A, 0x020A, + 0x020C, 0x020C, 0x020E, 0x020E, 0x0210, 0x0210, 0x0212, 0x0212, + 0x0214, 0x0214, 0x0216, 0x0216, 0x0218, 0x0218, 0x021A, 0x021A, + 0x021C, 0x021C, 0x021E, 0x021E, 0x0220, 0x0220, 0x0222, 0x0222, + 0x0224, 0x0224, 0x0226, 0x0226, 0x0228, 0x0228, 0x022A, 0x022A, + 0x022C, 0x022C, 0x022E, 0x022E, 0x0230, 0x0230, 0x0232, 0x0232, + 0x023A, 0x023B, 0x023D, 0x023E, 0x0241, 0x0241, 0x0243, 0x0246, + 0x0248, 0x0248, 0x024A, 0x024A, 0x024C, 0x024C, 0x024E, 0x024E, + 0x0370, 0x0370, 0x0372, 0x0372, 0x0376, 0x0376, 0x037F, 0x037F, + 0x0386, 0x0386, 0x0388, 0x038A, 0x038C, 0x038C, 0x038E, 0x038F, + 0x0391, 0x03A1, 0x03A3, 0x03AB, 0x03CF, 0x03CF, 0x03D2, 0x03D4, + 0x03D8, 0x03D8, 0x03DA, 0x03DA, 0x03DC, 0x03DC, 0x03DE, 0x03DE, + 0x03E0, 0x03E0, 0x03E2, 0x03E2, 0x03E4, 0x03E4, 0x03E6, 0x03E6, + 0x03E8, 0x03E8, 0x03EA, 0x03EA, 0x03EC, 0x03EC, 0x03EE, 0x03EE, + 0x03F4, 0x03F4, 0x03F7, 0x03F7, 0x03F9, 0x03FA, 0x03FD, 0x042F, + 0x0460, 0x0460, 0x0462, 0x0462, 0x0464, 0x0464, 0x0466, 0x0466, + 0x0468, 0x0468, 0x046A, 0x046A, 0x046C, 0x046C, 0x046E, 0x046E, + 0x0470, 0x0470, 0x0472, 0x0472, 0x0474, 0x0474, 0x0476, 0x0476, + 0x0478, 0x0478, 0x047A, 0x047A, 0x047C, 0x047C, 0x047E, 0x047E, + 0x0480, 0x0480, 0x048A, 0x048A, 0x048C, 0x048C, 0x048E, 0x048E, + 0x0490, 0x0490, 0x0492, 0x0492, 0x0494, 0x0494, 0x0496, 0x0496, + 0x0498, 0x0498, 0x049A, 0x049A, 0x049C, 0x049C, 0x049E, 0x049E, + 0x04A0, 0x04A0, 0x04A2, 0x04A2, 0x04A4, 0x04A4, 0x04A6, 0x04A6, + 0x04A8, 0x04A8, 0x04AA, 0x04AA, 0x04AC, 0x04AC, 0x04AE, 0x04AE, + 0x04B0, 0x04B0, 0x04B2, 0x04B2, 0x04B4, 0x04B4, 0x04B6, 0x04B6, + 0x04B8, 0x04B8, 0x04BA, 0x04BA, 0x04BC, 0x04BC, 0x04BE, 0x04BE, + 0x04C0, 0x04C1, 0x04C3, 0x04C3, 0x04C5, 0x04C5, 0x04C7, 0x04C7, + 0x04C9, 0x04C9, 0x04CB, 0x04CB, 0x04CD, 0x04CD, 0x04D0, 0x04D0, + 0x04D2, 0x04D2, 0x04D4, 0x04D4, 0x04D6, 0x04D6, 0x04D8, 0x04D8, + 0x04DA, 0x04DA, 0x04DC, 0x04DC, 0x04DE, 0x04DE, 0x04E0, 0x04E0, + 0x04E2, 0x04E2, 0x04E4, 0x04E4, 0x04E6, 0x04E6, 0x04E8, 0x04E8, + 0x04EA, 0x04EA, 0x04EC, 0x04EC, 0x04EE, 0x04EE, 0x04F0, 0x04F0, + 0x04F2, 0x04F2, 0x04F4, 0x04F4, 0x04F6, 0x04F6, 0x04F8, 0x04F8, + 0x04FA, 0x04FA, 0x04FC, 0x04FC, 0x04FE, 0x04FE, 0x0500, 0x0500, + 0x0502, 0x0502, 0x0504, 0x0504, 0x0506, 0x0506, 0x0508, 0x0508, + 0x050A, 0x050A, 0x050C, 0x050C, 0x050E, 0x050E, 0x0510, 0x0510, + 0x0512, 0x0512, 0x0514, 0x0514, 0x0516, 0x0516, 0x0518, 0x0518, + 0x051A, 0x051A, 0x051C, 0x051C, 0x051E, 0x051E, 0x0520, 0x0520, + 0x0522, 0x0522, 0x0524, 0x0524, 0x0526, 0x0526, 0x0528, 0x0528, + 0x052A, 0x052A, 0x052C, 0x052C, 0x052E, 0x052E, 0x0531, 0x0556, + 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x13A0, 0x13F5, + 0x1C89, 0x1C89, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1E00, 0x1E00, + 0x1E02, 0x1E02, 0x1E04, 0x1E04, 0x1E06, 0x1E06, 0x1E08, 0x1E08, + 0x1E0A, 0x1E0A, 0x1E0C, 0x1E0C, 0x1E0E, 0x1E0E, 0x1E10, 0x1E10, + 0x1E12, 0x1E12, 0x1E14, 0x1E14, 0x1E16, 0x1E16, 0x1E18, 0x1E18, + 0x1E1A, 0x1E1A, 0x1E1C, 0x1E1C, 0x1E1E, 0x1E1E, 0x1E20, 0x1E20, + 0x1E22, 0x1E22, 0x1E24, 0x1E24, 0x1E26, 0x1E26, 0x1E28, 0x1E28, + 0x1E2A, 0x1E2A, 0x1E2C, 0x1E2C, 0x1E2E, 0x1E2E, 0x1E30, 0x1E30, + 0x1E32, 0x1E32, 0x1E34, 0x1E34, 0x1E36, 0x1E36, 0x1E38, 0x1E38, + 0x1E3A, 0x1E3A, 0x1E3C, 0x1E3C, 0x1E3E, 0x1E3E, 0x1E40, 0x1E40, + 0x1E42, 0x1E42, 0x1E44, 0x1E44, 0x1E46, 0x1E46, 0x1E48, 0x1E48, + 0x1E4A, 0x1E4A, 0x1E4C, 0x1E4C, 0x1E4E, 0x1E4E, 0x1E50, 0x1E50, + 0x1E52, 0x1E52, 0x1E54, 0x1E54, 0x1E56, 0x1E56, 0x1E58, 0x1E58, + 0x1E5A, 0x1E5A, 0x1E5C, 0x1E5C, 0x1E5E, 0x1E5E, 0x1E60, 0x1E60, + 0x1E62, 0x1E62, 0x1E64, 0x1E64, 0x1E66, 0x1E66, 0x1E68, 0x1E68, + 0x1E6A, 0x1E6A, 0x1E6C, 0x1E6C, 0x1E6E, 0x1E6E, 0x1E70, 0x1E70, + 0x1E72, 0x1E72, 0x1E74, 0x1E74, 0x1E76, 0x1E76, 0x1E78, 0x1E78, + 0x1E7A, 0x1E7A, 0x1E7C, 0x1E7C, 0x1E7E, 0x1E7E, 0x1E80, 0x1E80, + 0x1E82, 0x1E82, 0x1E84, 0x1E84, 0x1E86, 0x1E86, 0x1E88, 0x1E88, + 0x1E8A, 0x1E8A, 0x1E8C, 0x1E8C, 0x1E8E, 0x1E8E, 0x1E90, 0x1E90, + 0x1E92, 0x1E92, 0x1E94, 0x1E94, 0x1E9E, 0x1E9E, 0x1EA0, 0x1EA0, + 0x1EA2, 0x1EA2, 0x1EA4, 0x1EA4, 0x1EA6, 0x1EA6, 0x1EA8, 0x1EA8, + 0x1EAA, 0x1EAA, 0x1EAC, 0x1EAC, 0x1EAE, 0x1EAE, 0x1EB0, 0x1EB0, + 0x1EB2, 0x1EB2, 0x1EB4, 0x1EB4, 0x1EB6, 0x1EB6, 0x1EB8, 0x1EB8, + 0x1EBA, 0x1EBA, 0x1EBC, 0x1EBC, 0x1EBE, 0x1EBE, 0x1EC0, 0x1EC0, + 0x1EC2, 0x1EC2, 0x1EC4, 0x1EC4, 0x1EC6, 0x1EC6, 0x1EC8, 0x1EC8, + 0x1ECA, 0x1ECA, 0x1ECC, 0x1ECC, 0x1ECE, 0x1ECE, 0x1ED0, 0x1ED0, + 0x1ED2, 0x1ED2, 0x1ED4, 0x1ED4, 0x1ED6, 0x1ED6, 0x1ED8, 0x1ED8, + 0x1EDA, 0x1EDA, 0x1EDC, 0x1EDC, 0x1EDE, 0x1EDE, 0x1EE0, 0x1EE0, + 0x1EE2, 0x1EE2, 0x1EE4, 0x1EE4, 0x1EE6, 0x1EE6, 0x1EE8, 0x1EE8, + 0x1EEA, 0x1EEA, 0x1EEC, 0x1EEC, 0x1EEE, 0x1EEE, 0x1EF0, 0x1EF0, + 0x1EF2, 0x1EF2, 0x1EF4, 0x1EF4, 0x1EF6, 0x1EF6, 0x1EF8, 0x1EF8, + 0x1EFA, 0x1EFA, 0x1EFC, 0x1EFC, 0x1EFE, 0x1EFE, 0x1F08, 0x1F0F, + 0x1F18, 0x1F1D, 0x1F28, 0x1F2F, 0x1F38, 0x1F3F, 0x1F48, 0x1F4D, + 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F5F, + 0x1F68, 0x1F6F, 0x1FB8, 0x1FBB, 0x1FC8, 0x1FCB, 0x1FD8, 0x1FDB, + 0x1FE8, 0x1FEC, 0x1FF8, 0x1FFB, 0x2102, 0x2102, 0x2107, 0x2107, + 0x210B, 0x210D, 0x2110, 0x2112, 0x2115, 0x2115, 0x2119, 0x211D, + 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x212D, + 0x2130, 0x2133, 0x213E, 0x213F, 0x2145, 0x2145, 0x2183, 0x2183, + 0x2C00, 0x2C2F, 0x2C60, 0x2C60, 0x2C62, 0x2C64, 0x2C67, 0x2C67, + 0x2C69, 0x2C69, 0x2C6B, 0x2C6B, 0x2C6D, 0x2C70, 0x2C72, 0x2C72, + 0x2C75, 0x2C75, 0x2C7E, 0x2C80, 0x2C82, 0x2C82, 0x2C84, 0x2C84, + 0x2C86, 0x2C86, 0x2C88, 0x2C88, 0x2C8A, 0x2C8A, 0x2C8C, 0x2C8C, + 0x2C8E, 0x2C8E, 0x2C90, 0x2C90, 0x2C92, 0x2C92, 0x2C94, 0x2C94, + 0x2C96, 0x2C96, 0x2C98, 0x2C98, 0x2C9A, 0x2C9A, 0x2C9C, 0x2C9C, + 0x2C9E, 0x2C9E, 0x2CA0, 0x2CA0, 0x2CA2, 0x2CA2, 0x2CA4, 0x2CA4, + 0x2CA6, 0x2CA6, 0x2CA8, 0x2CA8, 0x2CAA, 0x2CAA, 0x2CAC, 0x2CAC, + 0x2CAE, 0x2CAE, 0x2CB0, 0x2CB0, 0x2CB2, 0x2CB2, 0x2CB4, 0x2CB4, + 0x2CB6, 0x2CB6, 0x2CB8, 0x2CB8, 0x2CBA, 0x2CBA, 0x2CBC, 0x2CBC, + 0x2CBE, 0x2CBE, 0x2CC0, 0x2CC0, 0x2CC2, 0x2CC2, 0x2CC4, 0x2CC4, + 0x2CC6, 0x2CC6, 0x2CC8, 0x2CC8, 0x2CCA, 0x2CCA, 0x2CCC, 0x2CCC, + 0x2CCE, 0x2CCE, 0x2CD0, 0x2CD0, 0x2CD2, 0x2CD2, 0x2CD4, 0x2CD4, + 0x2CD6, 0x2CD6, 0x2CD8, 0x2CD8, 0x2CDA, 0x2CDA, 0x2CDC, 0x2CDC, + 0x2CDE, 0x2CDE, 0x2CE0, 0x2CE0, 0x2CE2, 0x2CE2, 0x2CEB, 0x2CEB, + 0x2CED, 0x2CED, 0x2CF2, 0x2CF2, 0xA640, 0xA640, 0xA642, 0xA642, + 0xA644, 0xA644, 0xA646, 0xA646, 0xA648, 0xA648, 0xA64A, 0xA64A, + 0xA64C, 0xA64C, 0xA64E, 0xA64E, 0xA650, 0xA650, 0xA652, 0xA652, + 0xA654, 0xA654, 0xA656, 0xA656, 0xA658, 0xA658, 0xA65A, 0xA65A, + 0xA65C, 0xA65C, 0xA65E, 0xA65E, 0xA660, 0xA660, 0xA662, 0xA662, + 0xA664, 0xA664, 0xA666, 0xA666, 0xA668, 0xA668, 0xA66A, 0xA66A, + 0xA66C, 0xA66C, 0xA680, 0xA680, 0xA682, 0xA682, 0xA684, 0xA684, + 0xA686, 0xA686, 0xA688, 0xA688, 0xA68A, 0xA68A, 0xA68C, 0xA68C, + 0xA68E, 0xA68E, 0xA690, 0xA690, 0xA692, 0xA692, 0xA694, 0xA694, + 0xA696, 0xA696, 0xA698, 0xA698, 0xA69A, 0xA69A, 0xA722, 0xA722, + 0xA724, 0xA724, 0xA726, 0xA726, 0xA728, 0xA728, 0xA72A, 0xA72A, + 0xA72C, 0xA72C, 0xA72E, 0xA72E, 0xA732, 0xA732, 0xA734, 0xA734, + 0xA736, 0xA736, 0xA738, 0xA738, 0xA73A, 0xA73A, 0xA73C, 0xA73C, + 0xA73E, 0xA73E, 0xA740, 0xA740, 0xA742, 0xA742, 0xA744, 0xA744, + 0xA746, 0xA746, 0xA748, 0xA748, 0xA74A, 0xA74A, 0xA74C, 0xA74C, + 0xA74E, 0xA74E, 0xA750, 0xA750, 0xA752, 0xA752, 0xA754, 0xA754, + 0xA756, 0xA756, 0xA758, 0xA758, 0xA75A, 0xA75A, 0xA75C, 0xA75C, + 0xA75E, 0xA75E, 0xA760, 0xA760, 0xA762, 0xA762, 0xA764, 0xA764, + 0xA766, 0xA766, 0xA768, 0xA768, 0xA76A, 0xA76A, 0xA76C, 0xA76C, + 0xA76E, 0xA76E, 0xA779, 0xA779, 0xA77B, 0xA77B, 0xA77D, 0xA77E, + 0xA780, 0xA780, 0xA782, 0xA782, 0xA784, 0xA784, 0xA786, 0xA786, + 0xA78B, 0xA78B, 0xA78D, 0xA78D, 0xA790, 0xA790, 0xA792, 0xA792, + 0xA796, 0xA796, 0xA798, 0xA798, 0xA79A, 0xA79A, 0xA79C, 0xA79C, + 0xA79E, 0xA79E, 0xA7A0, 0xA7A0, 0xA7A2, 0xA7A2, 0xA7A4, 0xA7A4, + 0xA7A6, 0xA7A6, 0xA7A8, 0xA7A8, 0xA7AA, 0xA7AE, 0xA7B0, 0xA7B4, + 0xA7B6, 0xA7B6, 0xA7B8, 0xA7B8, 0xA7BA, 0xA7BA, 0xA7BC, 0xA7BC, + 0xA7BE, 0xA7BE, 0xA7C0, 0xA7C0, 0xA7C2, 0xA7C2, 0xA7C4, 0xA7C7, + 0xA7C9, 0xA7C9, 0xA7CB, 0xA7CC, 0xA7CE, 0xA7CE, 0xA7D0, 0xA7D0, + 0xA7D2, 0xA7D2, 0xA7D4, 0xA7D4, 0xA7D6, 0xA7D6, 0xA7D8, 0xA7D8, + 0xA7DA, 0xA7DA, 0xA7DC, 0xA7DC, 0xA7F5, 0xA7F5, 0xFF21, 0xFF3A, + 0x10400, 0x10427, 0x104B0, 0x104D3, 0x10570, 0x1057A, 0x1057C, 0x1058A, + 0x1058C, 0x10592, 0x10594, 0x10595, 0x10C80, 0x10CB2, 0x10D50, 0x10D65, + 0x118A0, 0x118BF, 0x16E40, 0x16E5F, 0x16EA0, 0x16EB8, 0x1D400, 0x1D419, + 0x1D434, 0x1D44D, 0x1D468, 0x1D481, 0x1D49C, 0x1D49C, 0x1D49E, 0x1D49F, + 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B5, + 0x1D4D0, 0x1D4E9, 0x1D504, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, + 0x1D516, 0x1D51C, 0x1D538, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, + 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D56C, 0x1D585, 0x1D5A0, 0x1D5B9, + 0x1D5D4, 0x1D5ED, 0x1D608, 0x1D621, 0x1D63C, 0x1D655, 0x1D670, 0x1D689, + 0x1D6A8, 0x1D6C0, 0x1D6E2, 0x1D6FA, 0x1D71C, 0x1D734, 0x1D756, 0x1D76E, + 0x1D790, 0x1D7A8, 0x1D7CA, 0x1D7CA, 0x1E900, 0x1E921, + // #16 (2091+79): gc=Modifier_Letter:Lm + 0x02B0, 0x02C1, 0x02C6, 0x02D1, 0x02E0, 0x02E4, 0x02EC, 0x02EC, + 0x02EE, 0x02EE, 0x0374, 0x0374, 0x037A, 0x037A, 0x0559, 0x0559, + 0x0640, 0x0640, 0x06E5, 0x06E6, 0x07F4, 0x07F5, 0x07FA, 0x07FA, + 0x081A, 0x081A, 0x0824, 0x0824, 0x0828, 0x0828, 0x08C9, 0x08C9, + 0x0971, 0x0971, 0x0E46, 0x0E46, 0x0EC6, 0x0EC6, 0x10FC, 0x10FC, + 0x17D7, 0x17D7, 0x1843, 0x1843, 0x1AA7, 0x1AA7, 0x1C78, 0x1C7D, + 0x1D2C, 0x1D6A, 0x1D78, 0x1D78, 0x1D9B, 0x1DBF, 0x2071, 0x2071, + 0x207F, 0x207F, 0x2090, 0x209C, 0x2C7C, 0x2C7D, 0x2D6F, 0x2D6F, + 0x2E2F, 0x2E2F, 0x3005, 0x3005, 0x3031, 0x3035, 0x303B, 0x303B, + 0x309D, 0x309E, 0x30FC, 0x30FE, 0xA015, 0xA015, 0xA4F8, 0xA4FD, + 0xA60C, 0xA60C, 0xA67F, 0xA67F, 0xA69C, 0xA69D, 0xA717, 0xA71F, + 0xA770, 0xA770, 0xA788, 0xA788, 0xA7F1, 0xA7F4, 0xA7F8, 0xA7F9, + 0xA9CF, 0xA9CF, 0xA9E6, 0xA9E6, 0xAA70, 0xAA70, 0xAADD, 0xAADD, + 0xAAF3, 0xAAF4, 0xAB5C, 0xAB5F, 0xAB69, 0xAB69, 0xFF70, 0xFF70, + 0xFF9E, 0xFF9F, 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, + 0x10D4E, 0x10D4E, 0x10D6F, 0x10D6F, 0x10EC5, 0x10EC5, 0x11DD9, 0x11DD9, + 0x16B40, 0x16B43, 0x16D40, 0x16D42, 0x16D6B, 0x16D6C, 0x16F93, 0x16F9F, + 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE3, 0x16FF2, 0x16FF3, 0x1AFF0, 0x1AFF3, + 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1E030, 0x1E06D, 0x1E137, 0x1E13D, + 0x1E4EB, 0x1E4EB, 0x1E6FF, 0x1E6FF, 0x1E94B, 0x1E94B, + // #17 (2170+537): gc=Other_Letter:Lo + 0x00AA, 0x00AA, 0x00BA, 0x00BA, 0x01BB, 0x01BB, 0x01C0, 0x01C3, + 0x0294, 0x0295, 0x05D0, 0x05EA, 0x05EF, 0x05F2, 0x0620, 0x063F, + 0x0641, 0x064A, 0x066E, 0x066F, 0x0671, 0x06D3, 0x06D5, 0x06D5, + 0x06EE, 0x06EF, 0x06FA, 0x06FC, 0x06FF, 0x06FF, 0x0710, 0x0710, + 0x0712, 0x072F, 0x074D, 0x07A5, 0x07B1, 0x07B1, 0x07CA, 0x07EA, + 0x0800, 0x0815, 0x0840, 0x0858, 0x0860, 0x086A, 0x0870, 0x0887, + 0x0889, 0x088F, 0x08A0, 0x08C8, 0x0904, 0x0939, 0x093D, 0x093D, + 0x0950, 0x0950, 0x0958, 0x0961, 0x0972, 0x0980, 0x0985, 0x098C, + 0x098F, 0x0990, 0x0993, 0x09A8, 0x09AA, 0x09B0, 0x09B2, 0x09B2, + 0x09B6, 0x09B9, 0x09BD, 0x09BD, 0x09CE, 0x09CE, 0x09DC, 0x09DD, + 0x09DF, 0x09E1, 0x09F0, 0x09F1, 0x09FC, 0x09FC, 0x0A05, 0x0A0A, + 0x0A0F, 0x0A10, 0x0A13, 0x0A28, 0x0A2A, 0x0A30, 0x0A32, 0x0A33, + 0x0A35, 0x0A36, 0x0A38, 0x0A39, 0x0A59, 0x0A5C, 0x0A5E, 0x0A5E, + 0x0A72, 0x0A74, 0x0A85, 0x0A8D, 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, + 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, 0x0AB5, 0x0AB9, 0x0ABD, 0x0ABD, + 0x0AD0, 0x0AD0, 0x0AE0, 0x0AE1, 0x0AF9, 0x0AF9, 0x0B05, 0x0B0C, + 0x0B0F, 0x0B10, 0x0B13, 0x0B28, 0x0B2A, 0x0B30, 0x0B32, 0x0B33, + 0x0B35, 0x0B39, 0x0B3D, 0x0B3D, 0x0B5C, 0x0B5D, 0x0B5F, 0x0B61, + 0x0B71, 0x0B71, 0x0B83, 0x0B83, 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, + 0x0B92, 0x0B95, 0x0B99, 0x0B9A, 0x0B9C, 0x0B9C, 0x0B9E, 0x0B9F, + 0x0BA3, 0x0BA4, 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB9, 0x0BD0, 0x0BD0, + 0x0C05, 0x0C0C, 0x0C0E, 0x0C10, 0x0C12, 0x0C28, 0x0C2A, 0x0C39, + 0x0C3D, 0x0C3D, 0x0C58, 0x0C5A, 0x0C5C, 0x0C5D, 0x0C60, 0x0C61, + 0x0C80, 0x0C80, 0x0C85, 0x0C8C, 0x0C8E, 0x0C90, 0x0C92, 0x0CA8, + 0x0CAA, 0x0CB3, 0x0CB5, 0x0CB9, 0x0CBD, 0x0CBD, 0x0CDC, 0x0CDE, + 0x0CE0, 0x0CE1, 0x0CF1, 0x0CF2, 0x0D04, 0x0D0C, 0x0D0E, 0x0D10, + 0x0D12, 0x0D3A, 0x0D3D, 0x0D3D, 0x0D4E, 0x0D4E, 0x0D54, 0x0D56, + 0x0D5F, 0x0D61, 0x0D7A, 0x0D7F, 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, + 0x0DB3, 0x0DBB, 0x0DBD, 0x0DBD, 0x0DC0, 0x0DC6, 0x0E01, 0x0E30, + 0x0E32, 0x0E33, 0x0E40, 0x0E45, 0x0E81, 0x0E82, 0x0E84, 0x0E84, + 0x0E86, 0x0E8A, 0x0E8C, 0x0EA3, 0x0EA5, 0x0EA5, 0x0EA7, 0x0EB0, + 0x0EB2, 0x0EB3, 0x0EBD, 0x0EBD, 0x0EC0, 0x0EC4, 0x0EDC, 0x0EDF, + 0x0F00, 0x0F00, 0x0F40, 0x0F47, 0x0F49, 0x0F6C, 0x0F88, 0x0F8C, + 0x1000, 0x102A, 0x103F, 0x103F, 0x1050, 0x1055, 0x105A, 0x105D, + 0x1061, 0x1061, 0x1065, 0x1066, 0x106E, 0x1070, 0x1075, 0x1081, + 0x108E, 0x108E, 0x1100, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, + 0x1258, 0x1258, 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, + 0x1290, 0x12B0, 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, + 0x12C2, 0x12C5, 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, + 0x1318, 0x135A, 0x1380, 0x138F, 0x1401, 0x166C, 0x166F, 0x167F, + 0x1681, 0x169A, 0x16A0, 0x16EA, 0x16F1, 0x16F8, 0x1700, 0x1711, + 0x171F, 0x1731, 0x1740, 0x1751, 0x1760, 0x176C, 0x176E, 0x1770, + 0x1780, 0x17B3, 0x17DC, 0x17DC, 0x1820, 0x1842, 0x1844, 0x1878, + 0x1880, 0x1884, 0x1887, 0x18A8, 0x18AA, 0x18AA, 0x18B0, 0x18F5, + 0x1900, 0x191E, 0x1950, 0x196D, 0x1970, 0x1974, 0x1980, 0x19AB, + 0x19B0, 0x19C9, 0x1A00, 0x1A16, 0x1A20, 0x1A54, 0x1B05, 0x1B33, + 0x1B45, 0x1B4C, 0x1B83, 0x1BA0, 0x1BAE, 0x1BAF, 0x1BBA, 0x1BE5, + 0x1C00, 0x1C23, 0x1C4D, 0x1C4F, 0x1C5A, 0x1C77, 0x1CE9, 0x1CEC, + 0x1CEE, 0x1CF3, 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x2135, 0x2138, + 0x2D30, 0x2D67, 0x2D80, 0x2D96, 0x2DA0, 0x2DA6, 0x2DA8, 0x2DAE, + 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, 0x2DC8, 0x2DCE, + 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, 0x3006, 0x3006, 0x303C, 0x303C, + 0x3041, 0x3096, 0x309F, 0x309F, 0x30A1, 0x30FA, 0x30FF, 0x30FF, + 0x3105, 0x312F, 0x3131, 0x318E, 0x31A0, 0x31BF, 0x31F0, 0x31FF, + 0x3400, 0x4DBF, 0x4E00, 0xA014, 0xA016, 0xA48C, 0xA4D0, 0xA4F7, + 0xA500, 0xA60B, 0xA610, 0xA61F, 0xA62A, 0xA62B, 0xA66E, 0xA66E, + 0xA6A0, 0xA6E5, 0xA78F, 0xA78F, 0xA7F7, 0xA7F7, 0xA7FB, 0xA801, + 0xA803, 0xA805, 0xA807, 0xA80A, 0xA80C, 0xA822, 0xA840, 0xA873, + 0xA882, 0xA8B3, 0xA8F2, 0xA8F7, 0xA8FB, 0xA8FB, 0xA8FD, 0xA8FE, + 0xA90A, 0xA925, 0xA930, 0xA946, 0xA960, 0xA97C, 0xA984, 0xA9B2, + 0xA9E0, 0xA9E4, 0xA9E7, 0xA9EF, 0xA9FA, 0xA9FE, 0xAA00, 0xAA28, + 0xAA40, 0xAA42, 0xAA44, 0xAA4B, 0xAA60, 0xAA6F, 0xAA71, 0xAA76, + 0xAA7A, 0xAA7A, 0xAA7E, 0xAAAF, 0xAAB1, 0xAAB1, 0xAAB5, 0xAAB6, + 0xAAB9, 0xAABD, 0xAAC0, 0xAAC0, 0xAAC2, 0xAAC2, 0xAADB, 0xAADC, + 0xAAE0, 0xAAEA, 0xAAF2, 0xAAF2, 0xAB01, 0xAB06, 0xAB09, 0xAB0E, + 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, 0xABC0, 0xABE2, + 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, 0xD7CB, 0xD7FB, 0xF900, 0xFA6D, + 0xFA70, 0xFAD9, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB28, 0xFB2A, 0xFB36, + 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, + 0xFB46, 0xFBB1, 0xFBD3, 0xFD3D, 0xFD50, 0xFD8F, 0xFD92, 0xFDC7, + 0xFDF0, 0xFDFB, 0xFE70, 0xFE74, 0xFE76, 0xFEFC, 0xFF66, 0xFF6F, + 0xFF71, 0xFF9D, 0xFFA0, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, + 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0x10000, 0x1000B, 0x1000D, 0x10026, + 0x10028, 0x1003A, 0x1003C, 0x1003D, 0x1003F, 0x1004D, 0x10050, 0x1005D, + 0x10080, 0x100FA, 0x10280, 0x1029C, 0x102A0, 0x102D0, 0x10300, 0x1031F, + 0x1032D, 0x10340, 0x10342, 0x10349, 0x10350, 0x10375, 0x10380, 0x1039D, + 0x103A0, 0x103C3, 0x103C8, 0x103CF, 0x10450, 0x1049D, 0x10500, 0x10527, + 0x10530, 0x10563, 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, + 0x10760, 0x10767, 0x10800, 0x10805, 0x10808, 0x10808, 0x1080A, 0x10835, + 0x10837, 0x10838, 0x1083C, 0x1083C, 0x1083F, 0x10855, 0x10860, 0x10876, + 0x10880, 0x1089E, 0x108E0, 0x108F2, 0x108F4, 0x108F5, 0x10900, 0x10915, + 0x10920, 0x10939, 0x10940, 0x10959, 0x10980, 0x109B7, 0x109BE, 0x109BF, + 0x10A00, 0x10A00, 0x10A10, 0x10A13, 0x10A15, 0x10A17, 0x10A19, 0x10A35, + 0x10A60, 0x10A7C, 0x10A80, 0x10A9C, 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE4, + 0x10B00, 0x10B35, 0x10B40, 0x10B55, 0x10B60, 0x10B72, 0x10B80, 0x10B91, + 0x10C00, 0x10C48, 0x10D00, 0x10D23, 0x10D4A, 0x10D4D, 0x10D4F, 0x10D4F, + 0x10E80, 0x10EA9, 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC4, 0x10EC6, 0x10EC7, + 0x10F00, 0x10F1C, 0x10F27, 0x10F27, 0x10F30, 0x10F45, 0x10F70, 0x10F81, + 0x10FB0, 0x10FC4, 0x10FE0, 0x10FF6, 0x11003, 0x11037, 0x11071, 0x11072, + 0x11075, 0x11075, 0x11083, 0x110AF, 0x110D0, 0x110E8, 0x11103, 0x11126, + 0x11144, 0x11144, 0x11147, 0x11147, 0x11150, 0x11172, 0x11176, 0x11176, + 0x11183, 0x111B2, 0x111C1, 0x111C4, 0x111DA, 0x111DA, 0x111DC, 0x111DC, + 0x11200, 0x11211, 0x11213, 0x1122B, 0x1123F, 0x11240, 0x11280, 0x11286, + 0x11288, 0x11288, 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A8, + 0x112B0, 0x112DE, 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, + 0x1132A, 0x11330, 0x11332, 0x11333, 0x11335, 0x11339, 0x1133D, 0x1133D, + 0x11350, 0x11350, 0x1135D, 0x11361, 0x11380, 0x11389, 0x1138B, 0x1138B, + 0x1138E, 0x1138E, 0x11390, 0x113B5, 0x113B7, 0x113B7, 0x113D1, 0x113D1, + 0x113D3, 0x113D3, 0x11400, 0x11434, 0x11447, 0x1144A, 0x1145F, 0x11461, + 0x11480, 0x114AF, 0x114C4, 0x114C5, 0x114C7, 0x114C7, 0x11580, 0x115AE, + 0x115D8, 0x115DB, 0x11600, 0x1162F, 0x11644, 0x11644, 0x11680, 0x116AA, + 0x116B8, 0x116B8, 0x11700, 0x1171A, 0x11740, 0x11746, 0x11800, 0x1182B, + 0x118FF, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, 0x11916, + 0x11918, 0x1192F, 0x1193F, 0x1193F, 0x11941, 0x11941, 0x119A0, 0x119A7, + 0x119AA, 0x119D0, 0x119E1, 0x119E1, 0x119E3, 0x119E3, 0x11A00, 0x11A00, + 0x11A0B, 0x11A32, 0x11A3A, 0x11A3A, 0x11A50, 0x11A50, 0x11A5C, 0x11A89, + 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, 0x11BC0, 0x11BE0, 0x11C00, 0x11C08, + 0x11C0A, 0x11C2E, 0x11C40, 0x11C40, 0x11C72, 0x11C8F, 0x11D00, 0x11D06, + 0x11D08, 0x11D09, 0x11D0B, 0x11D30, 0x11D46, 0x11D46, 0x11D60, 0x11D65, + 0x11D67, 0x11D68, 0x11D6A, 0x11D89, 0x11D98, 0x11D98, 0x11DB0, 0x11DD8, + 0x11DDA, 0x11DDB, 0x11EE0, 0x11EF2, 0x11F02, 0x11F02, 0x11F04, 0x11F10, + 0x11F12, 0x11F33, 0x11FB0, 0x11FB0, 0x12000, 0x12399, 0x12480, 0x12543, + 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13441, 0x13446, 0x13460, 0x143FA, + 0x14400, 0x14646, 0x16100, 0x1611D, 0x16800, 0x16A38, 0x16A40, 0x16A5E, + 0x16A70, 0x16ABE, 0x16AD0, 0x16AED, 0x16B00, 0x16B2F, 0x16B63, 0x16B77, + 0x16B7D, 0x16B8F, 0x16D43, 0x16D6A, 0x16F00, 0x16F4A, 0x16F50, 0x16F50, + 0x17000, 0x18CD5, 0x18CFF, 0x18D1E, 0x18D80, 0x18DF2, 0x1B000, 0x1B122, + 0x1B132, 0x1B132, 0x1B150, 0x1B152, 0x1B155, 0x1B155, 0x1B164, 0x1B167, + 0x1B170, 0x1B2FB, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, 0x1BC88, + 0x1BC90, 0x1BC99, 0x1DF0A, 0x1DF0A, 0x1E100, 0x1E12C, 0x1E14E, 0x1E14E, + 0x1E290, 0x1E2AD, 0x1E2C0, 0x1E2EB, 0x1E4D0, 0x1E4EA, 0x1E5D0, 0x1E5ED, + 0x1E5F0, 0x1E5F0, 0x1E6C0, 0x1E6DE, 0x1E6E0, 0x1E6E2, 0x1E6E4, 0x1E6E5, + 0x1E6E7, 0x1E6ED, 0x1E6F0, 0x1E6F4, 0x1E6FE, 0x1E6FE, 0x1E7E0, 0x1E7E6, + 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, 0x1E7FE, 0x1E800, 0x1E8C4, + 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, + 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, + 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, + 0x1EE4B, 0x1EE4B, 0x1EE4D, 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, + 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, + 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, + 0x1EE6C, 0x1EE72, 0x1EE74, 0x1EE77, 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, + 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, + 0x1EEAB, 0x1EEBB, 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, + 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, + 0x31350, 0x33479, + // #18 (2707+563): gc=Mark:M:Combining_Mark + // Mc:193 + Me:5 + Mn:365 + // #19 (2707+193): gc=Spacing_Mark:Mc + 0x0903, 0x0903, 0x093B, 0x093B, 0x093E, 0x0940, 0x0949, 0x094C, + 0x094E, 0x094F, 0x0982, 0x0983, 0x09BE, 0x09C0, 0x09C7, 0x09C8, + 0x09CB, 0x09CC, 0x09D7, 0x09D7, 0x0A03, 0x0A03, 0x0A3E, 0x0A40, + 0x0A83, 0x0A83, 0x0ABE, 0x0AC0, 0x0AC9, 0x0AC9, 0x0ACB, 0x0ACC, + 0x0B02, 0x0B03, 0x0B3E, 0x0B3E, 0x0B40, 0x0B40, 0x0B47, 0x0B48, + 0x0B4B, 0x0B4C, 0x0B57, 0x0B57, 0x0BBE, 0x0BBF, 0x0BC1, 0x0BC2, + 0x0BC6, 0x0BC8, 0x0BCA, 0x0BCC, 0x0BD7, 0x0BD7, 0x0C01, 0x0C03, + 0x0C41, 0x0C44, 0x0C82, 0x0C83, 0x0CBE, 0x0CBE, 0x0CC0, 0x0CC4, + 0x0CC7, 0x0CC8, 0x0CCA, 0x0CCB, 0x0CD5, 0x0CD6, 0x0CF3, 0x0CF3, + 0x0D02, 0x0D03, 0x0D3E, 0x0D40, 0x0D46, 0x0D48, 0x0D4A, 0x0D4C, + 0x0D57, 0x0D57, 0x0D82, 0x0D83, 0x0DCF, 0x0DD1, 0x0DD8, 0x0DDF, + 0x0DF2, 0x0DF3, 0x0F3E, 0x0F3F, 0x0F7F, 0x0F7F, 0x102B, 0x102C, + 0x1031, 0x1031, 0x1038, 0x1038, 0x103B, 0x103C, 0x1056, 0x1057, + 0x1062, 0x1064, 0x1067, 0x106D, 0x1083, 0x1084, 0x1087, 0x108C, + 0x108F, 0x108F, 0x109A, 0x109C, 0x1715, 0x1715, 0x1734, 0x1734, + 0x17B6, 0x17B6, 0x17BE, 0x17C5, 0x17C7, 0x17C8, 0x1923, 0x1926, + 0x1929, 0x192B, 0x1930, 0x1931, 0x1933, 0x1938, 0x1A19, 0x1A1A, + 0x1A55, 0x1A55, 0x1A57, 0x1A57, 0x1A61, 0x1A61, 0x1A63, 0x1A64, + 0x1A6D, 0x1A72, 0x1B04, 0x1B04, 0x1B35, 0x1B35, 0x1B3B, 0x1B3B, + 0x1B3D, 0x1B41, 0x1B43, 0x1B44, 0x1B82, 0x1B82, 0x1BA1, 0x1BA1, + 0x1BA6, 0x1BA7, 0x1BAA, 0x1BAA, 0x1BE7, 0x1BE7, 0x1BEA, 0x1BEC, + 0x1BEE, 0x1BEE, 0x1BF2, 0x1BF3, 0x1C24, 0x1C2B, 0x1C34, 0x1C35, + 0x1CE1, 0x1CE1, 0x1CF7, 0x1CF7, 0x302E, 0x302F, 0xA823, 0xA824, + 0xA827, 0xA827, 0xA880, 0xA881, 0xA8B4, 0xA8C3, 0xA952, 0xA953, + 0xA983, 0xA983, 0xA9B4, 0xA9B5, 0xA9BA, 0xA9BB, 0xA9BE, 0xA9C0, + 0xAA2F, 0xAA30, 0xAA33, 0xAA34, 0xAA4D, 0xAA4D, 0xAA7B, 0xAA7B, + 0xAA7D, 0xAA7D, 0xAAEB, 0xAAEB, 0xAAEE, 0xAAEF, 0xAAF5, 0xAAF5, + 0xABE3, 0xABE4, 0xABE6, 0xABE7, 0xABE9, 0xABEA, 0xABEC, 0xABEC, + 0x11000, 0x11000, 0x11002, 0x11002, 0x11082, 0x11082, 0x110B0, 0x110B2, + 0x110B7, 0x110B8, 0x1112C, 0x1112C, 0x11145, 0x11146, 0x11182, 0x11182, + 0x111B3, 0x111B5, 0x111BF, 0x111C0, 0x111CE, 0x111CE, 0x1122C, 0x1122E, + 0x11232, 0x11233, 0x11235, 0x11235, 0x112E0, 0x112E2, 0x11302, 0x11303, + 0x1133E, 0x1133F, 0x11341, 0x11344, 0x11347, 0x11348, 0x1134B, 0x1134D, + 0x11357, 0x11357, 0x11362, 0x11363, 0x113B8, 0x113BA, 0x113C2, 0x113C2, + 0x113C5, 0x113C5, 0x113C7, 0x113CA, 0x113CC, 0x113CD, 0x113CF, 0x113CF, + 0x11435, 0x11437, 0x11440, 0x11441, 0x11445, 0x11445, 0x114B0, 0x114B2, + 0x114B9, 0x114B9, 0x114BB, 0x114BE, 0x114C1, 0x114C1, 0x115AF, 0x115B1, + 0x115B8, 0x115BB, 0x115BE, 0x115BE, 0x11630, 0x11632, 0x1163B, 0x1163C, + 0x1163E, 0x1163E, 0x116AC, 0x116AC, 0x116AE, 0x116AF, 0x116B6, 0x116B6, + 0x1171E, 0x1171E, 0x11720, 0x11721, 0x11726, 0x11726, 0x1182C, 0x1182E, + 0x11838, 0x11838, 0x11930, 0x11935, 0x11937, 0x11938, 0x1193D, 0x1193D, + 0x11940, 0x11940, 0x11942, 0x11942, 0x119D1, 0x119D3, 0x119DC, 0x119DF, + 0x119E4, 0x119E4, 0x11A39, 0x11A39, 0x11A57, 0x11A58, 0x11A97, 0x11A97, + 0x11B61, 0x11B61, 0x11B65, 0x11B65, 0x11B67, 0x11B67, 0x11C2F, 0x11C2F, + 0x11C3E, 0x11C3E, 0x11CA9, 0x11CA9, 0x11CB1, 0x11CB1, 0x11CB4, 0x11CB4, + 0x11D8A, 0x11D8E, 0x11D93, 0x11D94, 0x11D96, 0x11D96, 0x11EF5, 0x11EF6, + 0x11F03, 0x11F03, 0x11F34, 0x11F35, 0x11F3E, 0x11F3F, 0x11F41, 0x11F41, + 0x1612A, 0x1612C, 0x16F51, 0x16F87, 0x16FF0, 0x16FF1, 0x1D165, 0x1D166, + 0x1D16D, 0x1D172, + // #20 (2900+5): gc=Enclosing_Mark:Me + 0x0488, 0x0489, 0x1ABE, 0x1ABE, 0x20DD, 0x20E0, 0x20E2, 0x20E4, + 0xA670, 0xA672, + // #21 (2905+365): gc=Nonspacing_Mark:Mn + 0x0300, 0x036F, 0x0483, 0x0487, 0x0591, 0x05BD, 0x05BF, 0x05BF, + 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x0610, 0x061A, + 0x064B, 0x065F, 0x0670, 0x0670, 0x06D6, 0x06DC, 0x06DF, 0x06E4, + 0x06E7, 0x06E8, 0x06EA, 0x06ED, 0x0711, 0x0711, 0x0730, 0x074A, + 0x07A6, 0x07B0, 0x07EB, 0x07F3, 0x07FD, 0x07FD, 0x0816, 0x0819, + 0x081B, 0x0823, 0x0825, 0x0827, 0x0829, 0x082D, 0x0859, 0x085B, + 0x0897, 0x089F, 0x08CA, 0x08E1, 0x08E3, 0x0902, 0x093A, 0x093A, + 0x093C, 0x093C, 0x0941, 0x0948, 0x094D, 0x094D, 0x0951, 0x0957, + 0x0962, 0x0963, 0x0981, 0x0981, 0x09BC, 0x09BC, 0x09C1, 0x09C4, + 0x09CD, 0x09CD, 0x09E2, 0x09E3, 0x09FE, 0x09FE, 0x0A01, 0x0A02, + 0x0A3C, 0x0A3C, 0x0A41, 0x0A42, 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, + 0x0A51, 0x0A51, 0x0A70, 0x0A71, 0x0A75, 0x0A75, 0x0A81, 0x0A82, + 0x0ABC, 0x0ABC, 0x0AC1, 0x0AC5, 0x0AC7, 0x0AC8, 0x0ACD, 0x0ACD, + 0x0AE2, 0x0AE3, 0x0AFA, 0x0AFF, 0x0B01, 0x0B01, 0x0B3C, 0x0B3C, + 0x0B3F, 0x0B3F, 0x0B41, 0x0B44, 0x0B4D, 0x0B4D, 0x0B55, 0x0B56, + 0x0B62, 0x0B63, 0x0B82, 0x0B82, 0x0BC0, 0x0BC0, 0x0BCD, 0x0BCD, + 0x0C00, 0x0C00, 0x0C04, 0x0C04, 0x0C3C, 0x0C3C, 0x0C3E, 0x0C40, + 0x0C46, 0x0C48, 0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0C62, 0x0C63, + 0x0C81, 0x0C81, 0x0CBC, 0x0CBC, 0x0CBF, 0x0CBF, 0x0CC6, 0x0CC6, + 0x0CCC, 0x0CCD, 0x0CE2, 0x0CE3, 0x0D00, 0x0D01, 0x0D3B, 0x0D3C, + 0x0D41, 0x0D44, 0x0D4D, 0x0D4D, 0x0D62, 0x0D63, 0x0D81, 0x0D81, + 0x0DCA, 0x0DCA, 0x0DD2, 0x0DD4, 0x0DD6, 0x0DD6, 0x0E31, 0x0E31, + 0x0E34, 0x0E3A, 0x0E47, 0x0E4E, 0x0EB1, 0x0EB1, 0x0EB4, 0x0EBC, + 0x0EC8, 0x0ECE, 0x0F18, 0x0F19, 0x0F35, 0x0F35, 0x0F37, 0x0F37, + 0x0F39, 0x0F39, 0x0F71, 0x0F7E, 0x0F80, 0x0F84, 0x0F86, 0x0F87, + 0x0F8D, 0x0F97, 0x0F99, 0x0FBC, 0x0FC6, 0x0FC6, 0x102D, 0x1030, + 0x1032, 0x1037, 0x1039, 0x103A, 0x103D, 0x103E, 0x1058, 0x1059, + 0x105E, 0x1060, 0x1071, 0x1074, 0x1082, 0x1082, 0x1085, 0x1086, + 0x108D, 0x108D, 0x109D, 0x109D, 0x135D, 0x135F, 0x1712, 0x1714, + 0x1732, 0x1733, 0x1752, 0x1753, 0x1772, 0x1773, 0x17B4, 0x17B5, + 0x17B7, 0x17BD, 0x17C6, 0x17C6, 0x17C9, 0x17D3, 0x17DD, 0x17DD, + 0x180B, 0x180D, 0x180F, 0x180F, 0x1885, 0x1886, 0x18A9, 0x18A9, + 0x1920, 0x1922, 0x1927, 0x1928, 0x1932, 0x1932, 0x1939, 0x193B, + 0x1A17, 0x1A18, 0x1A1B, 0x1A1B, 0x1A56, 0x1A56, 0x1A58, 0x1A5E, + 0x1A60, 0x1A60, 0x1A62, 0x1A62, 0x1A65, 0x1A6C, 0x1A73, 0x1A7C, + 0x1A7F, 0x1A7F, 0x1AB0, 0x1ABD, 0x1ABF, 0x1ADD, 0x1AE0, 0x1AEB, + 0x1B00, 0x1B03, 0x1B34, 0x1B34, 0x1B36, 0x1B3A, 0x1B3C, 0x1B3C, + 0x1B42, 0x1B42, 0x1B6B, 0x1B73, 0x1B80, 0x1B81, 0x1BA2, 0x1BA5, + 0x1BA8, 0x1BA9, 0x1BAB, 0x1BAD, 0x1BE6, 0x1BE6, 0x1BE8, 0x1BE9, + 0x1BED, 0x1BED, 0x1BEF, 0x1BF1, 0x1C2C, 0x1C33, 0x1C36, 0x1C37, + 0x1CD0, 0x1CD2, 0x1CD4, 0x1CE0, 0x1CE2, 0x1CE8, 0x1CED, 0x1CED, + 0x1CF4, 0x1CF4, 0x1CF8, 0x1CF9, 0x1DC0, 0x1DFF, 0x20D0, 0x20DC, + 0x20E1, 0x20E1, 0x20E5, 0x20F0, 0x2CEF, 0x2CF1, 0x2D7F, 0x2D7F, + 0x2DE0, 0x2DFF, 0x302A, 0x302D, 0x3099, 0x309A, 0xA66F, 0xA66F, + 0xA674, 0xA67D, 0xA69E, 0xA69F, 0xA6F0, 0xA6F1, 0xA802, 0xA802, + 0xA806, 0xA806, 0xA80B, 0xA80B, 0xA825, 0xA826, 0xA82C, 0xA82C, + 0xA8C4, 0xA8C5, 0xA8E0, 0xA8F1, 0xA8FF, 0xA8FF, 0xA926, 0xA92D, + 0xA947, 0xA951, 0xA980, 0xA982, 0xA9B3, 0xA9B3, 0xA9B6, 0xA9B9, + 0xA9BC, 0xA9BD, 0xA9E5, 0xA9E5, 0xAA29, 0xAA2E, 0xAA31, 0xAA32, + 0xAA35, 0xAA36, 0xAA43, 0xAA43, 0xAA4C, 0xAA4C, 0xAA7C, 0xAA7C, + 0xAAB0, 0xAAB0, 0xAAB2, 0xAAB4, 0xAAB7, 0xAAB8, 0xAABE, 0xAABF, + 0xAAC1, 0xAAC1, 0xAAEC, 0xAAED, 0xAAF6, 0xAAF6, 0xABE5, 0xABE5, + 0xABE8, 0xABE8, 0xABED, 0xABED, 0xFB1E, 0xFB1E, 0xFE00, 0xFE0F, + 0xFE20, 0xFE2F, 0x101FD, 0x101FD, 0x102E0, 0x102E0, 0x10376, 0x1037A, + 0x10A01, 0x10A03, 0x10A05, 0x10A06, 0x10A0C, 0x10A0F, 0x10A38, 0x10A3A, + 0x10A3F, 0x10A3F, 0x10AE5, 0x10AE6, 0x10D24, 0x10D27, 0x10D69, 0x10D6D, + 0x10EAB, 0x10EAC, 0x10EFA, 0x10EFF, 0x10F46, 0x10F50, 0x10F82, 0x10F85, + 0x11001, 0x11001, 0x11038, 0x11046, 0x11070, 0x11070, 0x11073, 0x11074, + 0x1107F, 0x11081, 0x110B3, 0x110B6, 0x110B9, 0x110BA, 0x110C2, 0x110C2, + 0x11100, 0x11102, 0x11127, 0x1112B, 0x1112D, 0x11134, 0x11173, 0x11173, + 0x11180, 0x11181, 0x111B6, 0x111BE, 0x111C9, 0x111CC, 0x111CF, 0x111CF, + 0x1122F, 0x11231, 0x11234, 0x11234, 0x11236, 0x11237, 0x1123E, 0x1123E, + 0x11241, 0x11241, 0x112DF, 0x112DF, 0x112E3, 0x112EA, 0x11300, 0x11301, + 0x1133B, 0x1133C, 0x11340, 0x11340, 0x11366, 0x1136C, 0x11370, 0x11374, + 0x113BB, 0x113C0, 0x113CE, 0x113CE, 0x113D0, 0x113D0, 0x113D2, 0x113D2, + 0x113E1, 0x113E2, 0x11438, 0x1143F, 0x11442, 0x11444, 0x11446, 0x11446, + 0x1145E, 0x1145E, 0x114B3, 0x114B8, 0x114BA, 0x114BA, 0x114BF, 0x114C0, + 0x114C2, 0x114C3, 0x115B2, 0x115B5, 0x115BC, 0x115BD, 0x115BF, 0x115C0, + 0x115DC, 0x115DD, 0x11633, 0x1163A, 0x1163D, 0x1163D, 0x1163F, 0x11640, + 0x116AB, 0x116AB, 0x116AD, 0x116AD, 0x116B0, 0x116B5, 0x116B7, 0x116B7, + 0x1171D, 0x1171D, 0x1171F, 0x1171F, 0x11722, 0x11725, 0x11727, 0x1172B, + 0x1182F, 0x11837, 0x11839, 0x1183A, 0x1193B, 0x1193C, 0x1193E, 0x1193E, + 0x11943, 0x11943, 0x119D4, 0x119D7, 0x119DA, 0x119DB, 0x119E0, 0x119E0, + 0x11A01, 0x11A0A, 0x11A33, 0x11A38, 0x11A3B, 0x11A3E, 0x11A47, 0x11A47, + 0x11A51, 0x11A56, 0x11A59, 0x11A5B, 0x11A8A, 0x11A96, 0x11A98, 0x11A99, + 0x11B60, 0x11B60, 0x11B62, 0x11B64, 0x11B66, 0x11B66, 0x11C30, 0x11C36, + 0x11C38, 0x11C3D, 0x11C3F, 0x11C3F, 0x11C92, 0x11CA7, 0x11CAA, 0x11CB0, + 0x11CB2, 0x11CB3, 0x11CB5, 0x11CB6, 0x11D31, 0x11D36, 0x11D3A, 0x11D3A, + 0x11D3C, 0x11D3D, 0x11D3F, 0x11D45, 0x11D47, 0x11D47, 0x11D90, 0x11D91, + 0x11D95, 0x11D95, 0x11D97, 0x11D97, 0x11EF3, 0x11EF4, 0x11F00, 0x11F01, + 0x11F36, 0x11F3A, 0x11F40, 0x11F40, 0x11F42, 0x11F42, 0x11F5A, 0x11F5A, + 0x13440, 0x13440, 0x13447, 0x13455, 0x1611E, 0x16129, 0x1612D, 0x1612F, + 0x16AF0, 0x16AF4, 0x16B30, 0x16B36, 0x16F4F, 0x16F4F, 0x16F8F, 0x16F92, + 0x16FE4, 0x16FE4, 0x1BC9D, 0x1BC9E, 0x1CF00, 0x1CF2D, 0x1CF30, 0x1CF46, + 0x1D167, 0x1D169, 0x1D17B, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, + 0x1D242, 0x1D244, 0x1DA00, 0x1DA36, 0x1DA3B, 0x1DA6C, 0x1DA75, 0x1DA75, + 0x1DA84, 0x1DA84, 0x1DA9B, 0x1DA9F, 0x1DAA1, 0x1DAAF, 0x1E000, 0x1E006, + 0x1E008, 0x1E018, 0x1E01B, 0x1E021, 0x1E023, 0x1E024, 0x1E026, 0x1E02A, + 0x1E08F, 0x1E08F, 0x1E130, 0x1E136, 0x1E2AE, 0x1E2AE, 0x1E2EC, 0x1E2EF, + 0x1E4EC, 0x1E4EF, 0x1E5EE, 0x1E5EF, 0x1E6E3, 0x1E6E3, 0x1E6E6, 0x1E6E6, + 0x1E6EE, 0x1E6EF, 0x1E6F5, 0x1E6F5, 0x1E8D0, 0x1E8D6, 0x1E944, 0x1E94A, + 0xE0100, 0xE01EF, + // #22 (3270+157): gc=Number:N + // Nd:72 + Nl:13 + No:72 + // #23 (3270+72): gc=Decimal_Number:Nd:digit + 0x0030, 0x0039, 0x0660, 0x0669, 0x06F0, 0x06F9, 0x07C0, 0x07C9, + 0x0966, 0x096F, 0x09E6, 0x09EF, 0x0A66, 0x0A6F, 0x0AE6, 0x0AEF, + 0x0B66, 0x0B6F, 0x0BE6, 0x0BEF, 0x0C66, 0x0C6F, 0x0CE6, 0x0CEF, + 0x0D66, 0x0D6F, 0x0DE6, 0x0DEF, 0x0E50, 0x0E59, 0x0ED0, 0x0ED9, + 0x0F20, 0x0F29, 0x1040, 0x1049, 0x1090, 0x1099, 0x17E0, 0x17E9, + 0x1810, 0x1819, 0x1946, 0x194F, 0x19D0, 0x19D9, 0x1A80, 0x1A89, + 0x1A90, 0x1A99, 0x1B50, 0x1B59, 0x1BB0, 0x1BB9, 0x1C40, 0x1C49, + 0x1C50, 0x1C59, 0xA620, 0xA629, 0xA8D0, 0xA8D9, 0xA900, 0xA909, + 0xA9D0, 0xA9D9, 0xA9F0, 0xA9F9, 0xAA50, 0xAA59, 0xABF0, 0xABF9, + 0xFF10, 0xFF19, 0x104A0, 0x104A9, 0x10D30, 0x10D39, 0x10D40, 0x10D49, + 0x11066, 0x1106F, 0x110F0, 0x110F9, 0x11136, 0x1113F, 0x111D0, 0x111D9, + 0x112F0, 0x112F9, 0x11450, 0x11459, 0x114D0, 0x114D9, 0x11650, 0x11659, + 0x116C0, 0x116C9, 0x116D0, 0x116E3, 0x11730, 0x11739, 0x118E0, 0x118E9, + 0x11950, 0x11959, 0x11BF0, 0x11BF9, 0x11C50, 0x11C59, 0x11D50, 0x11D59, + 0x11DA0, 0x11DA9, 0x11DE0, 0x11DE9, 0x11F50, 0x11F59, 0x16130, 0x16139, + 0x16A60, 0x16A69, 0x16AC0, 0x16AC9, 0x16B50, 0x16B59, 0x16D70, 0x16D79, + 0x1CCF0, 0x1CCF9, 0x1D7CE, 0x1D7FF, 0x1E140, 0x1E149, 0x1E2F0, 0x1E2F9, + 0x1E4F0, 0x1E4F9, 0x1E5F1, 0x1E5FA, 0x1E950, 0x1E959, 0x1FBF0, 0x1FBF9, + // #24 (3342+13): gc=Letter_Number:Nl + 0x16EE, 0x16F0, 0x2160, 0x2182, 0x2185, 0x2188, 0x3007, 0x3007, + 0x3021, 0x3029, 0x3038, 0x303A, 0xA6E6, 0xA6EF, 0x10140, 0x10174, + 0x10341, 0x10341, 0x1034A, 0x1034A, 0x103D1, 0x103D5, 0x12400, 0x1246E, + 0x16FF4, 0x16FF6, + // #25 (3355+72): gc=Other_Number:No + 0x00B2, 0x00B3, 0x00B9, 0x00B9, 0x00BC, 0x00BE, 0x09F4, 0x09F9, + 0x0B72, 0x0B77, 0x0BF0, 0x0BF2, 0x0C78, 0x0C7E, 0x0D58, 0x0D5E, + 0x0D70, 0x0D78, 0x0F2A, 0x0F33, 0x1369, 0x137C, 0x17F0, 0x17F9, + 0x19DA, 0x19DA, 0x2070, 0x2070, 0x2074, 0x2079, 0x2080, 0x2089, + 0x2150, 0x215F, 0x2189, 0x2189, 0x2460, 0x249B, 0x24EA, 0x24FF, + 0x2776, 0x2793, 0x2CFD, 0x2CFD, 0x3192, 0x3195, 0x3220, 0x3229, + 0x3248, 0x324F, 0x3251, 0x325F, 0x3280, 0x3289, 0x32B1, 0x32BF, + 0xA830, 0xA835, 0x10107, 0x10133, 0x10175, 0x10178, 0x1018A, 0x1018B, + 0x102E1, 0x102FB, 0x10320, 0x10323, 0x10858, 0x1085F, 0x10879, 0x1087F, + 0x108A7, 0x108AF, 0x108FB, 0x108FF, 0x10916, 0x1091B, 0x109BC, 0x109BD, + 0x109C0, 0x109CF, 0x109D2, 0x109FF, 0x10A40, 0x10A48, 0x10A7D, 0x10A7E, + 0x10A9D, 0x10A9F, 0x10AEB, 0x10AEF, 0x10B58, 0x10B5F, 0x10B78, 0x10B7F, + 0x10BA9, 0x10BAF, 0x10CFA, 0x10CFF, 0x10E60, 0x10E7E, 0x10F1D, 0x10F26, + 0x10F51, 0x10F54, 0x10FC5, 0x10FCB, 0x11052, 0x11065, 0x111E1, 0x111F4, + 0x1173A, 0x1173B, 0x118EA, 0x118F2, 0x11C5A, 0x11C6C, 0x11FC0, 0x11FD4, + 0x16B5B, 0x16B61, 0x16E80, 0x16E96, 0x1D2C0, 0x1D2D3, 0x1D2E0, 0x1D2F3, + 0x1D360, 0x1D378, 0x1E8C7, 0x1E8CF, 0x1EC71, 0x1ECAB, 0x1ECAD, 0x1ECAF, + 0x1ECB1, 0x1ECB4, 0x1ED01, 0x1ED2D, 0x1ED2F, 0x1ED3D, 0x1F100, 0x1F10C, + // #26 (3427+396): gc=Punctuation:P:punct + // Pc:6 + Pd:20 + Pe:76 + Pf:10 + Pi:11 + Po:194 + Ps:79 + // #27 (3427+6): gc=Connector_Punctuation:Pc + 0x005F, 0x005F, 0x203F, 0x2040, 0x2054, 0x2054, 0xFE33, 0xFE34, + 0xFE4D, 0xFE4F, 0xFF3F, 0xFF3F, + // #28 (3433+20): gc=Dash_Punctuation:Pd + 0x002D, 0x002D, 0x058A, 0x058A, 0x05BE, 0x05BE, 0x1400, 0x1400, + 0x1806, 0x1806, 0x2010, 0x2015, 0x2E17, 0x2E17, 0x2E1A, 0x2E1A, + 0x2E3A, 0x2E3B, 0x2E40, 0x2E40, 0x2E5D, 0x2E5D, 0x301C, 0x301C, + 0x3030, 0x3030, 0x30A0, 0x30A0, 0xFE31, 0xFE32, 0xFE58, 0xFE58, + 0xFE63, 0xFE63, 0xFF0D, 0xFF0D, 0x10D6E, 0x10D6E, 0x10EAD, 0x10EAD, + // #29 (3453+76): gc=Close_Punctuation:Pe + 0x0029, 0x0029, 0x005D, 0x005D, 0x007D, 0x007D, 0x0F3B, 0x0F3B, + 0x0F3D, 0x0F3D, 0x169C, 0x169C, 0x2046, 0x2046, 0x207E, 0x207E, + 0x208E, 0x208E, 0x2309, 0x2309, 0x230B, 0x230B, 0x232A, 0x232A, + 0x2769, 0x2769, 0x276B, 0x276B, 0x276D, 0x276D, 0x276F, 0x276F, + 0x2771, 0x2771, 0x2773, 0x2773, 0x2775, 0x2775, 0x27C6, 0x27C6, + 0x27E7, 0x27E7, 0x27E9, 0x27E9, 0x27EB, 0x27EB, 0x27ED, 0x27ED, + 0x27EF, 0x27EF, 0x2984, 0x2984, 0x2986, 0x2986, 0x2988, 0x2988, + 0x298A, 0x298A, 0x298C, 0x298C, 0x298E, 0x298E, 0x2990, 0x2990, + 0x2992, 0x2992, 0x2994, 0x2994, 0x2996, 0x2996, 0x2998, 0x2998, + 0x29D9, 0x29D9, 0x29DB, 0x29DB, 0x29FD, 0x29FD, 0x2E23, 0x2E23, + 0x2E25, 0x2E25, 0x2E27, 0x2E27, 0x2E29, 0x2E29, 0x2E56, 0x2E56, + 0x2E58, 0x2E58, 0x2E5A, 0x2E5A, 0x2E5C, 0x2E5C, 0x3009, 0x3009, + 0x300B, 0x300B, 0x300D, 0x300D, 0x300F, 0x300F, 0x3011, 0x3011, + 0x3015, 0x3015, 0x3017, 0x3017, 0x3019, 0x3019, 0x301B, 0x301B, + 0x301E, 0x301F, 0xFD3E, 0xFD3E, 0xFE18, 0xFE18, 0xFE36, 0xFE36, + 0xFE38, 0xFE38, 0xFE3A, 0xFE3A, 0xFE3C, 0xFE3C, 0xFE3E, 0xFE3E, + 0xFE40, 0xFE40, 0xFE42, 0xFE42, 0xFE44, 0xFE44, 0xFE48, 0xFE48, + 0xFE5A, 0xFE5A, 0xFE5C, 0xFE5C, 0xFE5E, 0xFE5E, 0xFF09, 0xFF09, + 0xFF3D, 0xFF3D, 0xFF5D, 0xFF5D, 0xFF60, 0xFF60, 0xFF63, 0xFF63, + // #30 (3529+10): gc=Final_Punctuation:Pf + 0x00BB, 0x00BB, 0x2019, 0x2019, 0x201D, 0x201D, 0x203A, 0x203A, + 0x2E03, 0x2E03, 0x2E05, 0x2E05, 0x2E0A, 0x2E0A, 0x2E0D, 0x2E0D, + 0x2E1D, 0x2E1D, 0x2E21, 0x2E21, + // #31 (3539+11): gc=Initial_Punctuation:Pi + 0x00AB, 0x00AB, 0x2018, 0x2018, 0x201B, 0x201C, 0x201F, 0x201F, + 0x2039, 0x2039, 0x2E02, 0x2E02, 0x2E04, 0x2E04, 0x2E09, 0x2E09, + 0x2E0C, 0x2E0C, 0x2E1C, 0x2E1C, 0x2E20, 0x2E20, + // #32 (3550+194): gc=Other_Punctuation:Po + 0x0021, 0x0023, 0x0025, 0x0027, 0x002A, 0x002A, 0x002C, 0x002C, + 0x002E, 0x002F, 0x003A, 0x003B, 0x003F, 0x0040, 0x005C, 0x005C, + 0x00A1, 0x00A1, 0x00A7, 0x00A7, 0x00B6, 0x00B7, 0x00BF, 0x00BF, + 0x037E, 0x037E, 0x0387, 0x0387, 0x055A, 0x055F, 0x0589, 0x0589, + 0x05C0, 0x05C0, 0x05C3, 0x05C3, 0x05C6, 0x05C6, 0x05F3, 0x05F4, + 0x0609, 0x060A, 0x060C, 0x060D, 0x061B, 0x061B, 0x061D, 0x061F, + 0x066A, 0x066D, 0x06D4, 0x06D4, 0x0700, 0x070D, 0x07F7, 0x07F9, + 0x0830, 0x083E, 0x085E, 0x085E, 0x0964, 0x0965, 0x0970, 0x0970, + 0x09FD, 0x09FD, 0x0A76, 0x0A76, 0x0AF0, 0x0AF0, 0x0C77, 0x0C77, + 0x0C84, 0x0C84, 0x0DF4, 0x0DF4, 0x0E4F, 0x0E4F, 0x0E5A, 0x0E5B, + 0x0F04, 0x0F12, 0x0F14, 0x0F14, 0x0F85, 0x0F85, 0x0FD0, 0x0FD4, + 0x0FD9, 0x0FDA, 0x104A, 0x104F, 0x10FB, 0x10FB, 0x1360, 0x1368, + 0x166E, 0x166E, 0x16EB, 0x16ED, 0x1735, 0x1736, 0x17D4, 0x17D6, + 0x17D8, 0x17DA, 0x1800, 0x1805, 0x1807, 0x180A, 0x1944, 0x1945, + 0x1A1E, 0x1A1F, 0x1AA0, 0x1AA6, 0x1AA8, 0x1AAD, 0x1B4E, 0x1B4F, + 0x1B5A, 0x1B60, 0x1B7D, 0x1B7F, 0x1BFC, 0x1BFF, 0x1C3B, 0x1C3F, + 0x1C7E, 0x1C7F, 0x1CC0, 0x1CC7, 0x1CD3, 0x1CD3, 0x2016, 0x2017, + 0x2020, 0x2027, 0x2030, 0x2038, 0x203B, 0x203E, 0x2041, 0x2043, + 0x2047, 0x2051, 0x2053, 0x2053, 0x2055, 0x205E, 0x2CF9, 0x2CFC, + 0x2CFE, 0x2CFF, 0x2D70, 0x2D70, 0x2E00, 0x2E01, 0x2E06, 0x2E08, + 0x2E0B, 0x2E0B, 0x2E0E, 0x2E16, 0x2E18, 0x2E19, 0x2E1B, 0x2E1B, + 0x2E1E, 0x2E1F, 0x2E2A, 0x2E2E, 0x2E30, 0x2E39, 0x2E3C, 0x2E3F, + 0x2E41, 0x2E41, 0x2E43, 0x2E4F, 0x2E52, 0x2E54, 0x3001, 0x3003, + 0x303D, 0x303D, 0x30FB, 0x30FB, 0xA4FE, 0xA4FF, 0xA60D, 0xA60F, + 0xA673, 0xA673, 0xA67E, 0xA67E, 0xA6F2, 0xA6F7, 0xA874, 0xA877, + 0xA8CE, 0xA8CF, 0xA8F8, 0xA8FA, 0xA8FC, 0xA8FC, 0xA92E, 0xA92F, + 0xA95F, 0xA95F, 0xA9C1, 0xA9CD, 0xA9DE, 0xA9DF, 0xAA5C, 0xAA5F, + 0xAADE, 0xAADF, 0xAAF0, 0xAAF1, 0xABEB, 0xABEB, 0xFE10, 0xFE16, + 0xFE19, 0xFE19, 0xFE30, 0xFE30, 0xFE45, 0xFE46, 0xFE49, 0xFE4C, + 0xFE50, 0xFE52, 0xFE54, 0xFE57, 0xFE5F, 0xFE61, 0xFE68, 0xFE68, + 0xFE6A, 0xFE6B, 0xFF01, 0xFF03, 0xFF05, 0xFF07, 0xFF0A, 0xFF0A, + 0xFF0C, 0xFF0C, 0xFF0E, 0xFF0F, 0xFF1A, 0xFF1B, 0xFF1F, 0xFF20, + 0xFF3C, 0xFF3C, 0xFF61, 0xFF61, 0xFF64, 0xFF65, 0x10100, 0x10102, + 0x1039F, 0x1039F, 0x103D0, 0x103D0, 0x1056F, 0x1056F, 0x10857, 0x10857, + 0x1091F, 0x1091F, 0x1093F, 0x1093F, 0x10A50, 0x10A58, 0x10A7F, 0x10A7F, + 0x10AF0, 0x10AF6, 0x10B39, 0x10B3F, 0x10B99, 0x10B9C, 0x10ED0, 0x10ED0, + 0x10F55, 0x10F59, 0x10F86, 0x10F89, 0x11047, 0x1104D, 0x110BB, 0x110BC, + 0x110BE, 0x110C1, 0x11140, 0x11143, 0x11174, 0x11175, 0x111C5, 0x111C8, + 0x111CD, 0x111CD, 0x111DB, 0x111DB, 0x111DD, 0x111DF, 0x11238, 0x1123D, + 0x112A9, 0x112A9, 0x113D4, 0x113D5, 0x113D7, 0x113D8, 0x1144B, 0x1144F, + 0x1145A, 0x1145B, 0x1145D, 0x1145D, 0x114C6, 0x114C6, 0x115C1, 0x115D7, + 0x11641, 0x11643, 0x11660, 0x1166C, 0x116B9, 0x116B9, 0x1173C, 0x1173E, + 0x1183B, 0x1183B, 0x11944, 0x11946, 0x119E2, 0x119E2, 0x11A3F, 0x11A46, + 0x11A9A, 0x11A9C, 0x11A9E, 0x11AA2, 0x11B00, 0x11B09, 0x11BE1, 0x11BE1, + 0x11C41, 0x11C45, 0x11C70, 0x11C71, 0x11EF7, 0x11EF8, 0x11F43, 0x11F4F, + 0x11FFF, 0x11FFF, 0x12470, 0x12474, 0x12FF1, 0x12FF2, 0x16A6E, 0x16A6F, + 0x16AF5, 0x16AF5, 0x16B37, 0x16B3B, 0x16B44, 0x16B44, 0x16D6D, 0x16D6F, + 0x16E97, 0x16E9A, 0x16FE2, 0x16FE2, 0x1BC9F, 0x1BC9F, 0x1DA87, 0x1DA8B, + 0x1E5FF, 0x1E5FF, 0x1E95E, 0x1E95F, + // #33 (3744+79): gc=Open_Punctuation:Ps + 0x0028, 0x0028, 0x005B, 0x005B, 0x007B, 0x007B, 0x0F3A, 0x0F3A, + 0x0F3C, 0x0F3C, 0x169B, 0x169B, 0x201A, 0x201A, 0x201E, 0x201E, + 0x2045, 0x2045, 0x207D, 0x207D, 0x208D, 0x208D, 0x2308, 0x2308, + 0x230A, 0x230A, 0x2329, 0x2329, 0x2768, 0x2768, 0x276A, 0x276A, + 0x276C, 0x276C, 0x276E, 0x276E, 0x2770, 0x2770, 0x2772, 0x2772, + 0x2774, 0x2774, 0x27C5, 0x27C5, 0x27E6, 0x27E6, 0x27E8, 0x27E8, + 0x27EA, 0x27EA, 0x27EC, 0x27EC, 0x27EE, 0x27EE, 0x2983, 0x2983, + 0x2985, 0x2985, 0x2987, 0x2987, 0x2989, 0x2989, 0x298B, 0x298B, + 0x298D, 0x298D, 0x298F, 0x298F, 0x2991, 0x2991, 0x2993, 0x2993, + 0x2995, 0x2995, 0x2997, 0x2997, 0x29D8, 0x29D8, 0x29DA, 0x29DA, + 0x29FC, 0x29FC, 0x2E22, 0x2E22, 0x2E24, 0x2E24, 0x2E26, 0x2E26, + 0x2E28, 0x2E28, 0x2E42, 0x2E42, 0x2E55, 0x2E55, 0x2E57, 0x2E57, + 0x2E59, 0x2E59, 0x2E5B, 0x2E5B, 0x3008, 0x3008, 0x300A, 0x300A, + 0x300C, 0x300C, 0x300E, 0x300E, 0x3010, 0x3010, 0x3014, 0x3014, + 0x3016, 0x3016, 0x3018, 0x3018, 0x301A, 0x301A, 0x301D, 0x301D, + 0xFD3F, 0xFD3F, 0xFE17, 0xFE17, 0xFE35, 0xFE35, 0xFE37, 0xFE37, + 0xFE39, 0xFE39, 0xFE3B, 0xFE3B, 0xFE3D, 0xFE3D, 0xFE3F, 0xFE3F, + 0xFE41, 0xFE41, 0xFE43, 0xFE43, 0xFE47, 0xFE47, 0xFE59, 0xFE59, + 0xFE5B, 0xFE5B, 0xFE5D, 0xFE5D, 0xFF08, 0xFF08, 0xFF3B, 0xFF3B, + 0xFF5B, 0xFF5B, 0xFF5F, 0xFF5F, 0xFF62, 0xFF62, + // #34 (3823+312): gc=Symbol:S + // Sc:21 + Sk:31 + Sm:67 + So:193 + // #35 (3823+21): gc=Currency_Symbol:Sc + 0x0024, 0x0024, 0x00A2, 0x00A5, 0x058F, 0x058F, 0x060B, 0x060B, + 0x07FE, 0x07FF, 0x09F2, 0x09F3, 0x09FB, 0x09FB, 0x0AF1, 0x0AF1, + 0x0BF9, 0x0BF9, 0x0E3F, 0x0E3F, 0x17DB, 0x17DB, 0x20A0, 0x20C1, + 0xA838, 0xA838, 0xFDFC, 0xFDFC, 0xFE69, 0xFE69, 0xFF04, 0xFF04, + 0xFFE0, 0xFFE1, 0xFFE5, 0xFFE6, 0x11FDD, 0x11FE0, 0x1E2FF, 0x1E2FF, + 0x1ECB0, 0x1ECB0, + // #36 (3844+31): gc=Modifier_Symbol:Sk + 0x005E, 0x005E, 0x0060, 0x0060, 0x00A8, 0x00A8, 0x00AF, 0x00AF, + 0x00B4, 0x00B4, 0x00B8, 0x00B8, 0x02C2, 0x02C5, 0x02D2, 0x02DF, + 0x02E5, 0x02EB, 0x02ED, 0x02ED, 0x02EF, 0x02FF, 0x0375, 0x0375, + 0x0384, 0x0385, 0x0888, 0x0888, 0x1FBD, 0x1FBD, 0x1FBF, 0x1FC1, + 0x1FCD, 0x1FCF, 0x1FDD, 0x1FDF, 0x1FED, 0x1FEF, 0x1FFD, 0x1FFE, + 0x309B, 0x309C, 0xA700, 0xA716, 0xA720, 0xA721, 0xA789, 0xA78A, + 0xAB5B, 0xAB5B, 0xAB6A, 0xAB6B, 0xFBB2, 0xFBC2, 0xFF3E, 0xFF3E, + 0xFF40, 0xFF40, 0xFFE3, 0xFFE3, 0x1F3FB, 0x1F3FF, + // #37 (3875+67): gc=Math_Symbol:Sm + 0x002B, 0x002B, 0x003C, 0x003E, 0x007C, 0x007C, 0x007E, 0x007E, + 0x00AC, 0x00AC, 0x00B1, 0x00B1, 0x00D7, 0x00D7, 0x00F7, 0x00F7, + 0x03F6, 0x03F6, 0x0606, 0x0608, 0x2044, 0x2044, 0x2052, 0x2052, + 0x207A, 0x207C, 0x208A, 0x208C, 0x2118, 0x2118, 0x2140, 0x2144, + 0x214B, 0x214B, 0x2190, 0x2194, 0x219A, 0x219B, 0x21A0, 0x21A0, + 0x21A3, 0x21A3, 0x21A6, 0x21A6, 0x21AE, 0x21AE, 0x21CE, 0x21CF, + 0x21D2, 0x21D2, 0x21D4, 0x21D4, 0x21F4, 0x22FF, 0x2320, 0x2321, + 0x237C, 0x237C, 0x239B, 0x23B3, 0x23DC, 0x23E1, 0x25B7, 0x25B7, + 0x25C1, 0x25C1, 0x25F8, 0x25FF, 0x266F, 0x266F, 0x27C0, 0x27C4, + 0x27C7, 0x27E5, 0x27F0, 0x27FF, 0x2900, 0x2982, 0x2999, 0x29D7, + 0x29DC, 0x29FB, 0x29FE, 0x2AFF, 0x2B30, 0x2B44, 0x2B47, 0x2B4C, + 0xFB29, 0xFB29, 0xFE62, 0xFE62, 0xFE64, 0xFE66, 0xFF0B, 0xFF0B, + 0xFF1C, 0xFF1E, 0xFF5C, 0xFF5C, 0xFF5E, 0xFF5E, 0xFFE2, 0xFFE2, + 0xFFE9, 0xFFEC, 0x10D8E, 0x10D8F, 0x1CEF0, 0x1CEF0, 0x1D6C1, 0x1D6C1, + 0x1D6DB, 0x1D6DB, 0x1D6FB, 0x1D6FB, 0x1D715, 0x1D715, 0x1D735, 0x1D735, + 0x1D74F, 0x1D74F, 0x1D76F, 0x1D76F, 0x1D789, 0x1D789, 0x1D7A9, 0x1D7A9, + 0x1D7C3, 0x1D7C3, 0x1EEF0, 0x1EEF1, 0x1F8D0, 0x1F8D8, + // #38 (3942+193): gc=Other_Symbol:So + 0x00A6, 0x00A6, 0x00A9, 0x00A9, 0x00AE, 0x00AE, 0x00B0, 0x00B0, + 0x0482, 0x0482, 0x058D, 0x058E, 0x060E, 0x060F, 0x06DE, 0x06DE, + 0x06E9, 0x06E9, 0x06FD, 0x06FE, 0x07F6, 0x07F6, 0x09FA, 0x09FA, + 0x0B70, 0x0B70, 0x0BF3, 0x0BF8, 0x0BFA, 0x0BFA, 0x0C7F, 0x0C7F, + 0x0D4F, 0x0D4F, 0x0D79, 0x0D79, 0x0F01, 0x0F03, 0x0F13, 0x0F13, + 0x0F15, 0x0F17, 0x0F1A, 0x0F1F, 0x0F34, 0x0F34, 0x0F36, 0x0F36, + 0x0F38, 0x0F38, 0x0FBE, 0x0FC5, 0x0FC7, 0x0FCC, 0x0FCE, 0x0FCF, + 0x0FD5, 0x0FD8, 0x109E, 0x109F, 0x1390, 0x1399, 0x166D, 0x166D, + 0x1940, 0x1940, 0x19DE, 0x19FF, 0x1B61, 0x1B6A, 0x1B74, 0x1B7C, + 0x2100, 0x2101, 0x2103, 0x2106, 0x2108, 0x2109, 0x2114, 0x2114, + 0x2116, 0x2117, 0x211E, 0x2123, 0x2125, 0x2125, 0x2127, 0x2127, + 0x2129, 0x2129, 0x212E, 0x212E, 0x213A, 0x213B, 0x214A, 0x214A, + 0x214C, 0x214D, 0x214F, 0x214F, 0x218A, 0x218B, 0x2195, 0x2199, + 0x219C, 0x219F, 0x21A1, 0x21A2, 0x21A4, 0x21A5, 0x21A7, 0x21AD, + 0x21AF, 0x21CD, 0x21D0, 0x21D1, 0x21D3, 0x21D3, 0x21D5, 0x21F3, + 0x2300, 0x2307, 0x230C, 0x231F, 0x2322, 0x2328, 0x232B, 0x237B, + 0x237D, 0x239A, 0x23B4, 0x23DB, 0x23E2, 0x2429, 0x2440, 0x244A, + 0x249C, 0x24E9, 0x2500, 0x25B6, 0x25B8, 0x25C0, 0x25C2, 0x25F7, + 0x2600, 0x266E, 0x2670, 0x2767, 0x2794, 0x27BF, 0x2800, 0x28FF, + 0x2B00, 0x2B2F, 0x2B45, 0x2B46, 0x2B4D, 0x2B73, 0x2B76, 0x2BFF, + 0x2CE5, 0x2CEA, 0x2E50, 0x2E51, 0x2E80, 0x2E99, 0x2E9B, 0x2EF3, + 0x2F00, 0x2FD5, 0x2FF0, 0x2FFF, 0x3004, 0x3004, 0x3012, 0x3013, + 0x3020, 0x3020, 0x3036, 0x3037, 0x303E, 0x303F, 0x3190, 0x3191, + 0x3196, 0x319F, 0x31C0, 0x31E5, 0x31EF, 0x31EF, 0x3200, 0x321E, + 0x322A, 0x3247, 0x3250, 0x3250, 0x3260, 0x327F, 0x328A, 0x32B0, + 0x32C0, 0x33FF, 0x4DC0, 0x4DFF, 0xA490, 0xA4C6, 0xA828, 0xA82B, + 0xA836, 0xA837, 0xA839, 0xA839, 0xAA77, 0xAA79, 0xFBC3, 0xFBD2, + 0xFD40, 0xFD4F, 0xFD90, 0xFD91, 0xFDC8, 0xFDCF, 0xFDFD, 0xFDFF, + 0xFFE4, 0xFFE4, 0xFFE8, 0xFFE8, 0xFFED, 0xFFEE, 0xFFFC, 0xFFFD, + 0x10137, 0x1013F, 0x10179, 0x10189, 0x1018C, 0x1018E, 0x10190, 0x1019C, + 0x101A0, 0x101A0, 0x101D0, 0x101FC, 0x10877, 0x10878, 0x10AC8, 0x10AC8, + 0x10ED1, 0x10ED8, 0x1173F, 0x1173F, 0x11FD5, 0x11FDC, 0x11FE1, 0x11FF1, + 0x16B3C, 0x16B3F, 0x16B45, 0x16B45, 0x1BC9C, 0x1BC9C, 0x1CC00, 0x1CCEF, + 0x1CCFA, 0x1CCFC, 0x1CD00, 0x1CEB3, 0x1CEBA, 0x1CED0, 0x1CEE0, 0x1CEEF, + 0x1CF50, 0x1CFC3, 0x1D000, 0x1D0F5, 0x1D100, 0x1D126, 0x1D129, 0x1D164, + 0x1D16A, 0x1D16C, 0x1D183, 0x1D184, 0x1D18C, 0x1D1A9, 0x1D1AE, 0x1D1EA, + 0x1D200, 0x1D241, 0x1D245, 0x1D245, 0x1D300, 0x1D356, 0x1D800, 0x1D9FF, + 0x1DA37, 0x1DA3A, 0x1DA6D, 0x1DA74, 0x1DA76, 0x1DA83, 0x1DA85, 0x1DA86, + 0x1E14F, 0x1E14F, 0x1ECAC, 0x1ECAC, 0x1ED2E, 0x1ED2E, 0x1F000, 0x1F02B, + 0x1F030, 0x1F093, 0x1F0A0, 0x1F0AE, 0x1F0B1, 0x1F0BF, 0x1F0C1, 0x1F0CF, + 0x1F0D1, 0x1F0F5, 0x1F10D, 0x1F1AD, 0x1F1E6, 0x1F202, 0x1F210, 0x1F23B, + 0x1F240, 0x1F248, 0x1F250, 0x1F251, 0x1F260, 0x1F265, 0x1F300, 0x1F3FA, + 0x1F400, 0x1F6D8, 0x1F6DC, 0x1F6EC, 0x1F6F0, 0x1F6FC, 0x1F700, 0x1F7D9, + 0x1F7E0, 0x1F7EB, 0x1F7F0, 0x1F7F0, 0x1F800, 0x1F80B, 0x1F810, 0x1F847, + 0x1F850, 0x1F859, 0x1F860, 0x1F887, 0x1F890, 0x1F8AD, 0x1F8B0, 0x1F8BB, + 0x1F8C0, 0x1F8C1, 0x1F900, 0x1FA57, 0x1FA60, 0x1FA6D, 0x1FA70, 0x1FA7C, + 0x1FA80, 0x1FA8A, 0x1FA8E, 0x1FAC6, 0x1FAC8, 0x1FAC8, 0x1FACD, 0x1FADC, + 0x1FADF, 0x1FAEA, 0x1FAEF, 0x1FAF8, 0x1FB00, 0x1FB92, 0x1FB94, 0x1FBEF, + 0x1FBFA, 0x1FBFA, + // #39 (4135+9): gc=Separator:Z + // Zl:1 + Zp:1 + Zs:7 + // #40 (4135+1): gc=Line_Separator:Zl + 0x2028, 0x2028, + // #41 (4136+1): gc=Paragraph_Separator:Zp + 0x2029, 0x2029, + // #42 (4137+7): gc=Space_Separator:Zs + 0x0020, 0x0020, 0x00A0, 0x00A0, 0x1680, 0x1680, 0x2000, 0x200A, + 0x202F, 0x202F, 0x205F, 0x205F, 0x3000, 0x3000, + // #43 (4144+1): bp=ASCII + 0x0000, 0x007F, + // #44 (4145+3): bp=ASCII_Hex_Digit:AHex + 0x0030, 0x0039, 0x0041, 0x0046, 0x0061, 0x0066, + // #45 (4148+761): bp=Alphabetic:Alpha + 0x0041, 0x005A, 0x0061, 0x007A, 0x00AA, 0x00AA, 0x00B5, 0x00B5, + 0x00BA, 0x00BA, 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x02C1, + 0x02C6, 0x02D1, 0x02E0, 0x02E4, 0x02EC, 0x02EC, 0x02EE, 0x02EE, + 0x0345, 0x0345, 0x0363, 0x0374, 0x0376, 0x0377, 0x037A, 0x037D, + 0x037F, 0x037F, 0x0386, 0x0386, 0x0388, 0x038A, 0x038C, 0x038C, + 0x038E, 0x03A1, 0x03A3, 0x03F5, 0x03F7, 0x0481, 0x048A, 0x052F, + 0x0531, 0x0556, 0x0559, 0x0559, 0x0560, 0x0588, 0x05B0, 0x05BD, + 0x05BF, 0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7, 0x05C7, + 0x05D0, 0x05EA, 0x05EF, 0x05F2, 0x0610, 0x061A, 0x0620, 0x0657, + 0x0659, 0x065F, 0x066E, 0x06D3, 0x06D5, 0x06DC, 0x06E1, 0x06E8, + 0x06ED, 0x06EF, 0x06FA, 0x06FC, 0x06FF, 0x06FF, 0x0710, 0x073F, + 0x074D, 0x07B1, 0x07CA, 0x07EA, 0x07F4, 0x07F5, 0x07FA, 0x07FA, + 0x0800, 0x0817, 0x081A, 0x082C, 0x0840, 0x0858, 0x0860, 0x086A, + 0x0870, 0x0887, 0x0889, 0x088F, 0x0897, 0x0897, 0x08A0, 0x08C9, + 0x08D4, 0x08DF, 0x08E3, 0x08E9, 0x08F0, 0x093B, 0x093D, 0x094C, + 0x094E, 0x0950, 0x0955, 0x0963, 0x0971, 0x0983, 0x0985, 0x098C, + 0x098F, 0x0990, 0x0993, 0x09A8, 0x09AA, 0x09B0, 0x09B2, 0x09B2, + 0x09B6, 0x09B9, 0x09BD, 0x09C4, 0x09C7, 0x09C8, 0x09CB, 0x09CC, + 0x09CE, 0x09CE, 0x09D7, 0x09D7, 0x09DC, 0x09DD, 0x09DF, 0x09E3, + 0x09F0, 0x09F1, 0x09FC, 0x09FC, 0x0A01, 0x0A03, 0x0A05, 0x0A0A, + 0x0A0F, 0x0A10, 0x0A13, 0x0A28, 0x0A2A, 0x0A30, 0x0A32, 0x0A33, + 0x0A35, 0x0A36, 0x0A38, 0x0A39, 0x0A3E, 0x0A42, 0x0A47, 0x0A48, + 0x0A4B, 0x0A4C, 0x0A51, 0x0A51, 0x0A59, 0x0A5C, 0x0A5E, 0x0A5E, + 0x0A70, 0x0A75, 0x0A81, 0x0A83, 0x0A85, 0x0A8D, 0x0A8F, 0x0A91, + 0x0A93, 0x0AA8, 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, 0x0AB5, 0x0AB9, + 0x0ABD, 0x0AC5, 0x0AC7, 0x0AC9, 0x0ACB, 0x0ACC, 0x0AD0, 0x0AD0, + 0x0AE0, 0x0AE3, 0x0AF9, 0x0AFC, 0x0B01, 0x0B03, 0x0B05, 0x0B0C, + 0x0B0F, 0x0B10, 0x0B13, 0x0B28, 0x0B2A, 0x0B30, 0x0B32, 0x0B33, + 0x0B35, 0x0B39, 0x0B3D, 0x0B44, 0x0B47, 0x0B48, 0x0B4B, 0x0B4C, + 0x0B56, 0x0B57, 0x0B5C, 0x0B5D, 0x0B5F, 0x0B63, 0x0B71, 0x0B71, + 0x0B82, 0x0B83, 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, 0x0B92, 0x0B95, + 0x0B99, 0x0B9A, 0x0B9C, 0x0B9C, 0x0B9E, 0x0B9F, 0x0BA3, 0x0BA4, + 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB9, 0x0BBE, 0x0BC2, 0x0BC6, 0x0BC8, + 0x0BCA, 0x0BCC, 0x0BD0, 0x0BD0, 0x0BD7, 0x0BD7, 0x0C00, 0x0C0C, + 0x0C0E, 0x0C10, 0x0C12, 0x0C28, 0x0C2A, 0x0C39, 0x0C3D, 0x0C44, + 0x0C46, 0x0C48, 0x0C4A, 0x0C4C, 0x0C55, 0x0C56, 0x0C58, 0x0C5A, + 0x0C5C, 0x0C5D, 0x0C60, 0x0C63, 0x0C80, 0x0C83, 0x0C85, 0x0C8C, + 0x0C8E, 0x0C90, 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, 0x0CB5, 0x0CB9, + 0x0CBD, 0x0CC4, 0x0CC6, 0x0CC8, 0x0CCA, 0x0CCC, 0x0CD5, 0x0CD6, + 0x0CDC, 0x0CDE, 0x0CE0, 0x0CE3, 0x0CF1, 0x0CF3, 0x0D00, 0x0D0C, + 0x0D0E, 0x0D10, 0x0D12, 0x0D3A, 0x0D3D, 0x0D44, 0x0D46, 0x0D48, + 0x0D4A, 0x0D4C, 0x0D4E, 0x0D4E, 0x0D54, 0x0D57, 0x0D5F, 0x0D63, + 0x0D7A, 0x0D7F, 0x0D81, 0x0D83, 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, + 0x0DB3, 0x0DBB, 0x0DBD, 0x0DBD, 0x0DC0, 0x0DC6, 0x0DCF, 0x0DD4, + 0x0DD6, 0x0DD6, 0x0DD8, 0x0DDF, 0x0DF2, 0x0DF3, 0x0E01, 0x0E3A, + 0x0E40, 0x0E46, 0x0E4D, 0x0E4D, 0x0E81, 0x0E82, 0x0E84, 0x0E84, + 0x0E86, 0x0E8A, 0x0E8C, 0x0EA3, 0x0EA5, 0x0EA5, 0x0EA7, 0x0EB9, + 0x0EBB, 0x0EBD, 0x0EC0, 0x0EC4, 0x0EC6, 0x0EC6, 0x0ECD, 0x0ECD, + 0x0EDC, 0x0EDF, 0x0F00, 0x0F00, 0x0F40, 0x0F47, 0x0F49, 0x0F6C, + 0x0F71, 0x0F83, 0x0F88, 0x0F97, 0x0F99, 0x0FBC, 0x1000, 0x1036, + 0x1038, 0x1038, 0x103B, 0x103F, 0x1050, 0x108F, 0x109A, 0x109D, + 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, + 0x10FC, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, + 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, + 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, + 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, 0x1318, 0x135A, + 0x1380, 0x138F, 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0x1401, 0x166C, + 0x166F, 0x167F, 0x1681, 0x169A, 0x16A0, 0x16EA, 0x16EE, 0x16F8, + 0x1700, 0x1713, 0x171F, 0x1733, 0x1740, 0x1753, 0x1760, 0x176C, + 0x176E, 0x1770, 0x1772, 0x1773, 0x1780, 0x17B3, 0x17B6, 0x17C8, + 0x17D7, 0x17D7, 0x17DC, 0x17DC, 0x1820, 0x1878, 0x1880, 0x18AA, + 0x18B0, 0x18F5, 0x1900, 0x191E, 0x1920, 0x192B, 0x1930, 0x1938, + 0x1950, 0x196D, 0x1970, 0x1974, 0x1980, 0x19AB, 0x19B0, 0x19C9, + 0x1A00, 0x1A1B, 0x1A20, 0x1A5E, 0x1A61, 0x1A74, 0x1AA7, 0x1AA7, + 0x1ABF, 0x1AC0, 0x1ACC, 0x1ACE, 0x1B00, 0x1B33, 0x1B35, 0x1B43, + 0x1B45, 0x1B4C, 0x1B80, 0x1BA9, 0x1BAC, 0x1BAF, 0x1BBA, 0x1BE5, + 0x1BE7, 0x1BF1, 0x1C00, 0x1C36, 0x1C4D, 0x1C4F, 0x1C5A, 0x1C7D, + 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1CE9, 0x1CEC, + 0x1CEE, 0x1CF3, 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, + 0x1DD3, 0x1DF4, 0x1E00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, + 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, + 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, 0x1FBC, + 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, + 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFC, + 0x2071, 0x2071, 0x207F, 0x207F, 0x2090, 0x209C, 0x2102, 0x2102, + 0x2107, 0x2107, 0x210A, 0x2113, 0x2115, 0x2115, 0x2119, 0x211D, + 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x212D, + 0x212F, 0x2139, 0x213C, 0x213F, 0x2145, 0x2149, 0x214E, 0x214E, + 0x2160, 0x2188, 0x24B6, 0x24E9, 0x2C00, 0x2CE4, 0x2CEB, 0x2CEE, + 0x2CF2, 0x2CF3, 0x2D00, 0x2D25, 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, + 0x2D30, 0x2D67, 0x2D6F, 0x2D6F, 0x2D80, 0x2D96, 0x2DA0, 0x2DA6, + 0x2DA8, 0x2DAE, 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, + 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, 0x2DE0, 0x2DFF, + 0x2E2F, 0x2E2F, 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, + 0x3038, 0x303C, 0x3041, 0x3096, 0x309D, 0x309F, 0x30A1, 0x30FA, + 0x30FC, 0x30FF, 0x3105, 0x312F, 0x3131, 0x318E, 0x31A0, 0x31BF, + 0x31F0, 0x31FF, 0x3400, 0x4DBF, 0x4E00, 0xA48C, 0xA4D0, 0xA4FD, + 0xA500, 0xA60C, 0xA610, 0xA61F, 0xA62A, 0xA62B, 0xA640, 0xA66E, + 0xA674, 0xA67B, 0xA67F, 0xA6EF, 0xA717, 0xA71F, 0xA722, 0xA788, + 0xA78B, 0xA7DC, 0xA7F1, 0xA805, 0xA807, 0xA827, 0xA840, 0xA873, + 0xA880, 0xA8C3, 0xA8C5, 0xA8C5, 0xA8F2, 0xA8F7, 0xA8FB, 0xA8FB, + 0xA8FD, 0xA8FF, 0xA90A, 0xA92A, 0xA930, 0xA952, 0xA960, 0xA97C, + 0xA980, 0xA9B2, 0xA9B4, 0xA9BF, 0xA9CF, 0xA9CF, 0xA9E0, 0xA9EF, + 0xA9FA, 0xA9FE, 0xAA00, 0xAA36, 0xAA40, 0xAA4D, 0xAA60, 0xAA76, + 0xAA7A, 0xAABE, 0xAAC0, 0xAAC0, 0xAAC2, 0xAAC2, 0xAADB, 0xAADD, + 0xAAE0, 0xAAEF, 0xAAF2, 0xAAF5, 0xAB01, 0xAB06, 0xAB09, 0xAB0E, + 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, 0xAB30, 0xAB5A, + 0xAB5C, 0xAB69, 0xAB70, 0xABEA, 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, + 0xD7CB, 0xD7FB, 0xF900, 0xFA6D, 0xFA70, 0xFAD9, 0xFB00, 0xFB06, + 0xFB13, 0xFB17, 0xFB1D, 0xFB28, 0xFB2A, 0xFB36, 0xFB38, 0xFB3C, + 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFBB1, + 0xFBD3, 0xFD3D, 0xFD50, 0xFD8F, 0xFD92, 0xFDC7, 0xFDF0, 0xFDFB, + 0xFE70, 0xFE74, 0xFE76, 0xFEFC, 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, + 0xFF66, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, + 0xFFDA, 0xFFDC, 0x10000, 0x1000B, 0x1000D, 0x10026, 0x10028, 0x1003A, + 0x1003C, 0x1003D, 0x1003F, 0x1004D, 0x10050, 0x1005D, 0x10080, 0x100FA, + 0x10140, 0x10174, 0x10280, 0x1029C, 0x102A0, 0x102D0, 0x10300, 0x1031F, + 0x1032D, 0x1034A, 0x10350, 0x1037A, 0x10380, 0x1039D, 0x103A0, 0x103C3, + 0x103C8, 0x103CF, 0x103D1, 0x103D5, 0x10400, 0x1049D, 0x104B0, 0x104D3, + 0x104D8, 0x104FB, 0x10500, 0x10527, 0x10530, 0x10563, 0x10570, 0x1057A, + 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, 0x10597, 0x105A1, + 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x105C0, 0x105F3, + 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, 0x10780, 0x10785, + 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10800, 0x10805, 0x10808, 0x10808, + 0x1080A, 0x10835, 0x10837, 0x10838, 0x1083C, 0x1083C, 0x1083F, 0x10855, + 0x10860, 0x10876, 0x10880, 0x1089E, 0x108E0, 0x108F2, 0x108F4, 0x108F5, + 0x10900, 0x10915, 0x10920, 0x10939, 0x10940, 0x10959, 0x10980, 0x109B7, + 0x109BE, 0x109BF, 0x10A00, 0x10A03, 0x10A05, 0x10A06, 0x10A0C, 0x10A13, + 0x10A15, 0x10A17, 0x10A19, 0x10A35, 0x10A60, 0x10A7C, 0x10A80, 0x10A9C, + 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE4, 0x10B00, 0x10B35, 0x10B40, 0x10B55, + 0x10B60, 0x10B72, 0x10B80, 0x10B91, 0x10C00, 0x10C48, 0x10C80, 0x10CB2, + 0x10CC0, 0x10CF2, 0x10D00, 0x10D27, 0x10D4A, 0x10D65, 0x10D69, 0x10D69, + 0x10D6F, 0x10D85, 0x10E80, 0x10EA9, 0x10EAB, 0x10EAC, 0x10EB0, 0x10EB1, + 0x10EC2, 0x10EC7, 0x10EFA, 0x10EFC, 0x10F00, 0x10F1C, 0x10F27, 0x10F27, + 0x10F30, 0x10F45, 0x10F70, 0x10F81, 0x10FB0, 0x10FC4, 0x10FE0, 0x10FF6, + 0x11000, 0x11045, 0x11071, 0x11075, 0x11080, 0x110B8, 0x110C2, 0x110C2, + 0x110D0, 0x110E8, 0x11100, 0x11132, 0x11144, 0x11147, 0x11150, 0x11172, + 0x11176, 0x11176, 0x11180, 0x111BF, 0x111C1, 0x111C4, 0x111CE, 0x111CF, + 0x111DA, 0x111DA, 0x111DC, 0x111DC, 0x11200, 0x11211, 0x11213, 0x11234, + 0x11237, 0x11237, 0x1123E, 0x11241, 0x11280, 0x11286, 0x11288, 0x11288, + 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A8, 0x112B0, 0x112E8, + 0x11300, 0x11303, 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, + 0x1132A, 0x11330, 0x11332, 0x11333, 0x11335, 0x11339, 0x1133D, 0x11344, + 0x11347, 0x11348, 0x1134B, 0x1134C, 0x11350, 0x11350, 0x11357, 0x11357, + 0x1135D, 0x11363, 0x11380, 0x11389, 0x1138B, 0x1138B, 0x1138E, 0x1138E, + 0x11390, 0x113B5, 0x113B7, 0x113C0, 0x113C2, 0x113C2, 0x113C5, 0x113C5, + 0x113C7, 0x113CA, 0x113CC, 0x113CD, 0x113D1, 0x113D1, 0x113D3, 0x113D3, + 0x11400, 0x11441, 0x11443, 0x11445, 0x11447, 0x1144A, 0x1145F, 0x11461, + 0x11480, 0x114C1, 0x114C4, 0x114C5, 0x114C7, 0x114C7, 0x11580, 0x115B5, + 0x115B8, 0x115BE, 0x115D8, 0x115DD, 0x11600, 0x1163E, 0x11640, 0x11640, + 0x11644, 0x11644, 0x11680, 0x116B5, 0x116B8, 0x116B8, 0x11700, 0x1171A, + 0x1171D, 0x1172A, 0x11740, 0x11746, 0x11800, 0x11838, 0x118A0, 0x118DF, + 0x118FF, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, 0x11916, + 0x11918, 0x11935, 0x11937, 0x11938, 0x1193B, 0x1193C, 0x1193F, 0x11942, + 0x119A0, 0x119A7, 0x119AA, 0x119D7, 0x119DA, 0x119DF, 0x119E1, 0x119E1, + 0x119E3, 0x119E4, 0x11A00, 0x11A32, 0x11A35, 0x11A3E, 0x11A50, 0x11A97, + 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, 0x11B60, 0x11B67, 0x11BC0, 0x11BE0, + 0x11C00, 0x11C08, 0x11C0A, 0x11C36, 0x11C38, 0x11C3E, 0x11C40, 0x11C40, + 0x11C72, 0x11C8F, 0x11C92, 0x11CA7, 0x11CA9, 0x11CB6, 0x11D00, 0x11D06, + 0x11D08, 0x11D09, 0x11D0B, 0x11D36, 0x11D3A, 0x11D3A, 0x11D3C, 0x11D3D, + 0x11D3F, 0x11D41, 0x11D43, 0x11D43, 0x11D46, 0x11D47, 0x11D60, 0x11D65, + 0x11D67, 0x11D68, 0x11D6A, 0x11D8E, 0x11D90, 0x11D91, 0x11D93, 0x11D96, + 0x11D98, 0x11D98, 0x11DB0, 0x11DDB, 0x11EE0, 0x11EF6, 0x11F00, 0x11F10, + 0x11F12, 0x11F3A, 0x11F3E, 0x11F40, 0x11FB0, 0x11FB0, 0x12000, 0x12399, + 0x12400, 0x1246E, 0x12480, 0x12543, 0x12F90, 0x12FF0, 0x13000, 0x1342F, + 0x13441, 0x13446, 0x13460, 0x143FA, 0x14400, 0x14646, 0x16100, 0x1612E, + 0x16800, 0x16A38, 0x16A40, 0x16A5E, 0x16A70, 0x16ABE, 0x16AD0, 0x16AED, + 0x16B00, 0x16B2F, 0x16B40, 0x16B43, 0x16B63, 0x16B77, 0x16B7D, 0x16B8F, + 0x16D40, 0x16D6C, 0x16E40, 0x16E7F, 0x16EA0, 0x16EB8, 0x16EBB, 0x16ED3, + 0x16F00, 0x16F4A, 0x16F4F, 0x16F87, 0x16F8F, 0x16F9F, 0x16FE0, 0x16FE1, + 0x16FE3, 0x16FE3, 0x16FF0, 0x16FF6, 0x17000, 0x18CD5, 0x18CFF, 0x18D1E, + 0x18D80, 0x18DF2, 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, + 0x1B000, 0x1B122, 0x1B132, 0x1B132, 0x1B150, 0x1B152, 0x1B155, 0x1B155, + 0x1B164, 0x1B167, 0x1B170, 0x1B2FB, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, + 0x1BC80, 0x1BC88, 0x1BC90, 0x1BC99, 0x1BC9E, 0x1BC9E, 0x1D400, 0x1D454, + 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, + 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, + 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, + 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, + 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, 0x1D6A8, 0x1D6C0, 0x1D6C2, 0x1D6DA, + 0x1D6DC, 0x1D6FA, 0x1D6FC, 0x1D714, 0x1D716, 0x1D734, 0x1D736, 0x1D74E, + 0x1D750, 0x1D76E, 0x1D770, 0x1D788, 0x1D78A, 0x1D7A8, 0x1D7AA, 0x1D7C2, + 0x1D7C4, 0x1D7CB, 0x1DF00, 0x1DF1E, 0x1DF25, 0x1DF2A, 0x1E000, 0x1E006, + 0x1E008, 0x1E018, 0x1E01B, 0x1E021, 0x1E023, 0x1E024, 0x1E026, 0x1E02A, + 0x1E030, 0x1E06D, 0x1E08F, 0x1E08F, 0x1E100, 0x1E12C, 0x1E137, 0x1E13D, + 0x1E14E, 0x1E14E, 0x1E290, 0x1E2AD, 0x1E2C0, 0x1E2EB, 0x1E4D0, 0x1E4EB, + 0x1E5D0, 0x1E5ED, 0x1E5F0, 0x1E5F0, 0x1E6C0, 0x1E6DE, 0x1E6E0, 0x1E6F5, + 0x1E6FE, 0x1E6FF, 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, + 0x1E7F0, 0x1E7FE, 0x1E800, 0x1E8C4, 0x1E900, 0x1E943, 0x1E947, 0x1E947, + 0x1E94B, 0x1E94B, 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, 0x1EE21, 0x1EE22, + 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, + 0x1EE39, 0x1EE39, 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, + 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, 0x1EE4F, 0x1EE51, 0x1EE52, + 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, + 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, + 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, 0x1EE74, 0x1EE77, 0x1EE79, 0x1EE7C, + 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, + 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, 0x1F130, 0x1F149, 0x1F150, 0x1F169, + 0x1F170, 0x1F189, 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, + 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, + 0x31350, 0x33479, + // #46 (4909+1): bp=Any + 0x0000, 0x10FFFF, + // #47 (4910+0): bp=Assigned + + // #48 (4910+4): bp=Bidi_Control:Bidi_C + 0x061C, 0x061C, 0x200E, 0x200F, 0x202A, 0x202E, 0x2066, 0x2069, + // #49 (4914+114): bp=Bidi_Mirrored:Bidi_M + 0x0028, 0x0029, 0x003C, 0x003C, 0x003E, 0x003E, 0x005B, 0x005B, + 0x005D, 0x005D, 0x007B, 0x007B, 0x007D, 0x007D, 0x00AB, 0x00AB, + 0x00BB, 0x00BB, 0x0F3A, 0x0F3D, 0x169B, 0x169C, 0x2039, 0x203A, + 0x2045, 0x2046, 0x207D, 0x207E, 0x208D, 0x208E, 0x2140, 0x2140, + 0x2201, 0x2204, 0x2208, 0x220D, 0x2211, 0x2211, 0x2215, 0x2216, + 0x221A, 0x221D, 0x221F, 0x2222, 0x2224, 0x2224, 0x2226, 0x2226, + 0x222B, 0x2233, 0x2239, 0x2239, 0x223B, 0x224C, 0x2252, 0x2255, + 0x225F, 0x2260, 0x2262, 0x2262, 0x2264, 0x226B, 0x226D, 0x228C, + 0x228F, 0x2292, 0x2298, 0x2298, 0x22A2, 0x22A3, 0x22A6, 0x22B8, + 0x22BE, 0x22BF, 0x22C9, 0x22CD, 0x22D0, 0x22D1, 0x22D6, 0x22ED, + 0x22F0, 0x22FF, 0x2308, 0x230B, 0x2320, 0x2321, 0x2329, 0x232A, + 0x2768, 0x2775, 0x27C0, 0x27C0, 0x27C3, 0x27C6, 0x27C8, 0x27C9, + 0x27CB, 0x27CD, 0x27D3, 0x27D6, 0x27DC, 0x27DE, 0x27E2, 0x27EF, + 0x2983, 0x2998, 0x299B, 0x29A0, 0x29A2, 0x29AF, 0x29B8, 0x29B8, + 0x29C0, 0x29C5, 0x29C9, 0x29C9, 0x29CE, 0x29D2, 0x29D4, 0x29D5, + 0x29D8, 0x29DC, 0x29E1, 0x29E1, 0x29E3, 0x29E5, 0x29E8, 0x29E9, + 0x29F4, 0x29F9, 0x29FC, 0x29FD, 0x2A0A, 0x2A1C, 0x2A1E, 0x2A21, + 0x2A24, 0x2A24, 0x2A26, 0x2A26, 0x2A29, 0x2A29, 0x2A2B, 0x2A2E, + 0x2A34, 0x2A35, 0x2A3C, 0x2A3E, 0x2A57, 0x2A58, 0x2A64, 0x2A65, + 0x2A6A, 0x2A6D, 0x2A6F, 0x2A70, 0x2A73, 0x2A74, 0x2A79, 0x2AA3, + 0x2AA6, 0x2AAD, 0x2AAF, 0x2AD6, 0x2ADC, 0x2ADC, 0x2ADE, 0x2ADE, + 0x2AE2, 0x2AE6, 0x2AEC, 0x2AEE, 0x2AF3, 0x2AF3, 0x2AF7, 0x2AFB, + 0x2AFD, 0x2AFD, 0x2BFE, 0x2BFE, 0x2E02, 0x2E05, 0x2E09, 0x2E0A, + 0x2E0C, 0x2E0D, 0x2E1C, 0x2E1D, 0x2E20, 0x2E29, 0x2E55, 0x2E5C, + 0x3008, 0x3011, 0x3014, 0x301B, 0xFE59, 0xFE5E, 0xFE64, 0xFE65, + 0xFF08, 0xFF09, 0xFF1C, 0xFF1C, 0xFF1E, 0xFF1E, 0xFF3B, 0xFF3B, + 0xFF3D, 0xFF3D, 0xFF5B, 0xFF5B, 0xFF5D, 0xFF5D, 0xFF5F, 0xFF60, + 0xFF62, 0xFF63, 0x1D6DB, 0x1D6DB, 0x1D715, 0x1D715, 0x1D74F, 0x1D74F, + 0x1D789, 0x1D789, 0x1D7C3, 0x1D7C3, + // #50 (5028+464): bp=Case_Ignorable:CI + 0x0027, 0x0027, 0x002E, 0x002E, 0x003A, 0x003A, 0x005E, 0x005E, + 0x0060, 0x0060, 0x00A8, 0x00A8, 0x00AD, 0x00AD, 0x00AF, 0x00AF, + 0x00B4, 0x00B4, 0x00B7, 0x00B8, 0x02B0, 0x036F, 0x0374, 0x0375, + 0x037A, 0x037A, 0x0384, 0x0385, 0x0387, 0x0387, 0x0483, 0x0489, + 0x0559, 0x0559, 0x055F, 0x055F, 0x0591, 0x05BD, 0x05BF, 0x05BF, + 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x05F4, 0x05F4, + 0x0600, 0x0605, 0x0610, 0x061A, 0x061C, 0x061C, 0x0640, 0x0640, + 0x064B, 0x065F, 0x0670, 0x0670, 0x06D6, 0x06DD, 0x06DF, 0x06E8, + 0x06EA, 0x06ED, 0x070F, 0x070F, 0x0711, 0x0711, 0x0730, 0x074A, + 0x07A6, 0x07B0, 0x07EB, 0x07F5, 0x07FA, 0x07FA, 0x07FD, 0x07FD, + 0x0816, 0x082D, 0x0859, 0x085B, 0x0888, 0x0888, 0x0890, 0x0891, + 0x0897, 0x089F, 0x08C9, 0x0902, 0x093A, 0x093A, 0x093C, 0x093C, + 0x0941, 0x0948, 0x094D, 0x094D, 0x0951, 0x0957, 0x0962, 0x0963, + 0x0971, 0x0971, 0x0981, 0x0981, 0x09BC, 0x09BC, 0x09C1, 0x09C4, + 0x09CD, 0x09CD, 0x09E2, 0x09E3, 0x09FE, 0x09FE, 0x0A01, 0x0A02, + 0x0A3C, 0x0A3C, 0x0A41, 0x0A42, 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, + 0x0A51, 0x0A51, 0x0A70, 0x0A71, 0x0A75, 0x0A75, 0x0A81, 0x0A82, + 0x0ABC, 0x0ABC, 0x0AC1, 0x0AC5, 0x0AC7, 0x0AC8, 0x0ACD, 0x0ACD, + 0x0AE2, 0x0AE3, 0x0AFA, 0x0AFF, 0x0B01, 0x0B01, 0x0B3C, 0x0B3C, + 0x0B3F, 0x0B3F, 0x0B41, 0x0B44, 0x0B4D, 0x0B4D, 0x0B55, 0x0B56, + 0x0B62, 0x0B63, 0x0B82, 0x0B82, 0x0BC0, 0x0BC0, 0x0BCD, 0x0BCD, + 0x0C00, 0x0C00, 0x0C04, 0x0C04, 0x0C3C, 0x0C3C, 0x0C3E, 0x0C40, + 0x0C46, 0x0C48, 0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0C62, 0x0C63, + 0x0C81, 0x0C81, 0x0CBC, 0x0CBC, 0x0CBF, 0x0CBF, 0x0CC6, 0x0CC6, + 0x0CCC, 0x0CCD, 0x0CE2, 0x0CE3, 0x0D00, 0x0D01, 0x0D3B, 0x0D3C, + 0x0D41, 0x0D44, 0x0D4D, 0x0D4D, 0x0D62, 0x0D63, 0x0D81, 0x0D81, + 0x0DCA, 0x0DCA, 0x0DD2, 0x0DD4, 0x0DD6, 0x0DD6, 0x0E31, 0x0E31, + 0x0E34, 0x0E3A, 0x0E46, 0x0E4E, 0x0EB1, 0x0EB1, 0x0EB4, 0x0EBC, + 0x0EC6, 0x0EC6, 0x0EC8, 0x0ECE, 0x0F18, 0x0F19, 0x0F35, 0x0F35, + 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F71, 0x0F7E, 0x0F80, 0x0F84, + 0x0F86, 0x0F87, 0x0F8D, 0x0F97, 0x0F99, 0x0FBC, 0x0FC6, 0x0FC6, + 0x102D, 0x1030, 0x1032, 0x1037, 0x1039, 0x103A, 0x103D, 0x103E, + 0x1058, 0x1059, 0x105E, 0x1060, 0x1071, 0x1074, 0x1082, 0x1082, + 0x1085, 0x1086, 0x108D, 0x108D, 0x109D, 0x109D, 0x10FC, 0x10FC, + 0x135D, 0x135F, 0x1712, 0x1714, 0x1732, 0x1733, 0x1752, 0x1753, + 0x1772, 0x1773, 0x17B4, 0x17B5, 0x17B7, 0x17BD, 0x17C6, 0x17C6, + 0x17C9, 0x17D3, 0x17D7, 0x17D7, 0x17DD, 0x17DD, 0x180B, 0x180F, + 0x1843, 0x1843, 0x1885, 0x1886, 0x18A9, 0x18A9, 0x1920, 0x1922, + 0x1927, 0x1928, 0x1932, 0x1932, 0x1939, 0x193B, 0x1A17, 0x1A18, + 0x1A1B, 0x1A1B, 0x1A56, 0x1A56, 0x1A58, 0x1A5E, 0x1A60, 0x1A60, + 0x1A62, 0x1A62, 0x1A65, 0x1A6C, 0x1A73, 0x1A7C, 0x1A7F, 0x1A7F, + 0x1AA7, 0x1AA7, 0x1AB0, 0x1ADD, 0x1AE0, 0x1AEB, 0x1B00, 0x1B03, + 0x1B34, 0x1B34, 0x1B36, 0x1B3A, 0x1B3C, 0x1B3C, 0x1B42, 0x1B42, + 0x1B6B, 0x1B73, 0x1B80, 0x1B81, 0x1BA2, 0x1BA5, 0x1BA8, 0x1BA9, + 0x1BAB, 0x1BAD, 0x1BE6, 0x1BE6, 0x1BE8, 0x1BE9, 0x1BED, 0x1BED, + 0x1BEF, 0x1BF1, 0x1C2C, 0x1C33, 0x1C36, 0x1C37, 0x1C78, 0x1C7D, + 0x1CD0, 0x1CD2, 0x1CD4, 0x1CE0, 0x1CE2, 0x1CE8, 0x1CED, 0x1CED, + 0x1CF4, 0x1CF4, 0x1CF8, 0x1CF9, 0x1D2C, 0x1D6A, 0x1D78, 0x1D78, + 0x1D9B, 0x1DFF, 0x1FBD, 0x1FBD, 0x1FBF, 0x1FC1, 0x1FCD, 0x1FCF, + 0x1FDD, 0x1FDF, 0x1FED, 0x1FEF, 0x1FFD, 0x1FFE, 0x200B, 0x200F, + 0x2018, 0x2019, 0x2024, 0x2024, 0x2027, 0x2027, 0x202A, 0x202E, + 0x2060, 0x2064, 0x2066, 0x206F, 0x2071, 0x2071, 0x207F, 0x207F, + 0x2090, 0x209C, 0x20D0, 0x20F0, 0x2C7C, 0x2C7D, 0x2CEF, 0x2CF1, + 0x2D6F, 0x2D6F, 0x2D7F, 0x2D7F, 0x2DE0, 0x2DFF, 0x2E2F, 0x2E2F, + 0x3005, 0x3005, 0x302A, 0x302D, 0x3031, 0x3035, 0x303B, 0x303B, + 0x3099, 0x309E, 0x30FC, 0x30FE, 0xA015, 0xA015, 0xA4F8, 0xA4FD, + 0xA60C, 0xA60C, 0xA66F, 0xA672, 0xA674, 0xA67D, 0xA67F, 0xA67F, + 0xA69C, 0xA69F, 0xA6F0, 0xA6F1, 0xA700, 0xA721, 0xA770, 0xA770, + 0xA788, 0xA78A, 0xA7F1, 0xA7F4, 0xA7F8, 0xA7F9, 0xA802, 0xA802, + 0xA806, 0xA806, 0xA80B, 0xA80B, 0xA825, 0xA826, 0xA82C, 0xA82C, + 0xA8C4, 0xA8C5, 0xA8E0, 0xA8F1, 0xA8FF, 0xA8FF, 0xA926, 0xA92D, + 0xA947, 0xA951, 0xA980, 0xA982, 0xA9B3, 0xA9B3, 0xA9B6, 0xA9B9, + 0xA9BC, 0xA9BD, 0xA9CF, 0xA9CF, 0xA9E5, 0xA9E6, 0xAA29, 0xAA2E, + 0xAA31, 0xAA32, 0xAA35, 0xAA36, 0xAA43, 0xAA43, 0xAA4C, 0xAA4C, + 0xAA70, 0xAA70, 0xAA7C, 0xAA7C, 0xAAB0, 0xAAB0, 0xAAB2, 0xAAB4, + 0xAAB7, 0xAAB8, 0xAABE, 0xAABF, 0xAAC1, 0xAAC1, 0xAADD, 0xAADD, + 0xAAEC, 0xAAED, 0xAAF3, 0xAAF4, 0xAAF6, 0xAAF6, 0xAB5B, 0xAB5F, + 0xAB69, 0xAB6B, 0xABE5, 0xABE5, 0xABE8, 0xABE8, 0xABED, 0xABED, + 0xFB1E, 0xFB1E, 0xFBB2, 0xFBC2, 0xFE00, 0xFE0F, 0xFE13, 0xFE13, + 0xFE20, 0xFE2F, 0xFE52, 0xFE52, 0xFE55, 0xFE55, 0xFEFF, 0xFEFF, + 0xFF07, 0xFF07, 0xFF0E, 0xFF0E, 0xFF1A, 0xFF1A, 0xFF3E, 0xFF3E, + 0xFF40, 0xFF40, 0xFF70, 0xFF70, 0xFF9E, 0xFF9F, 0xFFE3, 0xFFE3, + 0xFFF9, 0xFFFB, 0x101FD, 0x101FD, 0x102E0, 0x102E0, 0x10376, 0x1037A, + 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10A01, 0x10A03, + 0x10A05, 0x10A06, 0x10A0C, 0x10A0F, 0x10A38, 0x10A3A, 0x10A3F, 0x10A3F, + 0x10AE5, 0x10AE6, 0x10D24, 0x10D27, 0x10D4E, 0x10D4E, 0x10D69, 0x10D6D, + 0x10D6F, 0x10D6F, 0x10EAB, 0x10EAC, 0x10EC5, 0x10EC5, 0x10EFA, 0x10EFF, + 0x10F46, 0x10F50, 0x10F82, 0x10F85, 0x11001, 0x11001, 0x11038, 0x11046, + 0x11070, 0x11070, 0x11073, 0x11074, 0x1107F, 0x11081, 0x110B3, 0x110B6, + 0x110B9, 0x110BA, 0x110BD, 0x110BD, 0x110C2, 0x110C2, 0x110CD, 0x110CD, + 0x11100, 0x11102, 0x11127, 0x1112B, 0x1112D, 0x11134, 0x11173, 0x11173, + 0x11180, 0x11181, 0x111B6, 0x111BE, 0x111C9, 0x111CC, 0x111CF, 0x111CF, + 0x1122F, 0x11231, 0x11234, 0x11234, 0x11236, 0x11237, 0x1123E, 0x1123E, + 0x11241, 0x11241, 0x112DF, 0x112DF, 0x112E3, 0x112EA, 0x11300, 0x11301, + 0x1133B, 0x1133C, 0x11340, 0x11340, 0x11366, 0x1136C, 0x11370, 0x11374, + 0x113BB, 0x113C0, 0x113CE, 0x113CE, 0x113D0, 0x113D0, 0x113D2, 0x113D2, + 0x113E1, 0x113E2, 0x11438, 0x1143F, 0x11442, 0x11444, 0x11446, 0x11446, + 0x1145E, 0x1145E, 0x114B3, 0x114B8, 0x114BA, 0x114BA, 0x114BF, 0x114C0, + 0x114C2, 0x114C3, 0x115B2, 0x115B5, 0x115BC, 0x115BD, 0x115BF, 0x115C0, + 0x115DC, 0x115DD, 0x11633, 0x1163A, 0x1163D, 0x1163D, 0x1163F, 0x11640, + 0x116AB, 0x116AB, 0x116AD, 0x116AD, 0x116B0, 0x116B5, 0x116B7, 0x116B7, + 0x1171D, 0x1171D, 0x1171F, 0x1171F, 0x11722, 0x11725, 0x11727, 0x1172B, + 0x1182F, 0x11837, 0x11839, 0x1183A, 0x1193B, 0x1193C, 0x1193E, 0x1193E, + 0x11943, 0x11943, 0x119D4, 0x119D7, 0x119DA, 0x119DB, 0x119E0, 0x119E0, + 0x11A01, 0x11A0A, 0x11A33, 0x11A38, 0x11A3B, 0x11A3E, 0x11A47, 0x11A47, + 0x11A51, 0x11A56, 0x11A59, 0x11A5B, 0x11A8A, 0x11A96, 0x11A98, 0x11A99, + 0x11B60, 0x11B60, 0x11B62, 0x11B64, 0x11B66, 0x11B66, 0x11C30, 0x11C36, + 0x11C38, 0x11C3D, 0x11C3F, 0x11C3F, 0x11C92, 0x11CA7, 0x11CAA, 0x11CB0, + 0x11CB2, 0x11CB3, 0x11CB5, 0x11CB6, 0x11D31, 0x11D36, 0x11D3A, 0x11D3A, + 0x11D3C, 0x11D3D, 0x11D3F, 0x11D45, 0x11D47, 0x11D47, 0x11D90, 0x11D91, + 0x11D95, 0x11D95, 0x11D97, 0x11D97, 0x11DD9, 0x11DD9, 0x11EF3, 0x11EF4, + 0x11F00, 0x11F01, 0x11F36, 0x11F3A, 0x11F40, 0x11F40, 0x11F42, 0x11F42, + 0x11F5A, 0x11F5A, 0x13430, 0x13440, 0x13447, 0x13455, 0x1611E, 0x16129, + 0x1612D, 0x1612F, 0x16AF0, 0x16AF4, 0x16B30, 0x16B36, 0x16B40, 0x16B43, + 0x16D40, 0x16D42, 0x16D6B, 0x16D6C, 0x16F4F, 0x16F4F, 0x16F8F, 0x16F9F, + 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE4, 0x16FF2, 0x16FF3, 0x1AFF0, 0x1AFF3, + 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1BC9D, 0x1BC9E, 0x1BCA0, 0x1BCA3, + 0x1CF00, 0x1CF2D, 0x1CF30, 0x1CF46, 0x1D167, 0x1D169, 0x1D173, 0x1D182, + 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, 0x1D242, 0x1D244, 0x1DA00, 0x1DA36, + 0x1DA3B, 0x1DA6C, 0x1DA75, 0x1DA75, 0x1DA84, 0x1DA84, 0x1DA9B, 0x1DA9F, + 0x1DAA1, 0x1DAAF, 0x1E000, 0x1E006, 0x1E008, 0x1E018, 0x1E01B, 0x1E021, + 0x1E023, 0x1E024, 0x1E026, 0x1E02A, 0x1E030, 0x1E06D, 0x1E08F, 0x1E08F, + 0x1E130, 0x1E13D, 0x1E2AE, 0x1E2AE, 0x1E2EC, 0x1E2EF, 0x1E4EB, 0x1E4EF, + 0x1E5EE, 0x1E5EF, 0x1E6E3, 0x1E6E3, 0x1E6E6, 0x1E6E6, 0x1E6EE, 0x1E6EF, + 0x1E6F5, 0x1E6F5, 0x1E6FF, 0x1E6FF, 0x1E8D0, 0x1E8D6, 0x1E944, 0x1E94B, + 0x1F3FB, 0x1F3FF, 0xE0001, 0xE0001, 0xE0020, 0xE007F, 0xE0100, 0xE01EF, + // #51 (5492+158): bp=Cased + 0x0041, 0x005A, 0x0061, 0x007A, 0x00AA, 0x00AA, 0x00B5, 0x00B5, + 0x00BA, 0x00BA, 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x01BA, + 0x01BC, 0x01BF, 0x01C4, 0x0293, 0x0296, 0x02B8, 0x02C0, 0x02C1, + 0x02E0, 0x02E4, 0x0345, 0x0345, 0x0370, 0x0373, 0x0376, 0x0377, + 0x037A, 0x037D, 0x037F, 0x037F, 0x0386, 0x0386, 0x0388, 0x038A, + 0x038C, 0x038C, 0x038E, 0x03A1, 0x03A3, 0x03F5, 0x03F7, 0x0481, + 0x048A, 0x052F, 0x0531, 0x0556, 0x0560, 0x0588, 0x10A0, 0x10C5, + 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, 0x10FC, 0x10FF, + 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, + 0x1CBD, 0x1CBF, 0x1D00, 0x1DBF, 0x1E00, 0x1F15, 0x1F18, 0x1F1D, + 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, + 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, + 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, + 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, + 0x1FF6, 0x1FFC, 0x2071, 0x2071, 0x207F, 0x207F, 0x2090, 0x209C, + 0x2102, 0x2102, 0x2107, 0x2107, 0x210A, 0x2113, 0x2115, 0x2115, + 0x2119, 0x211D, 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, + 0x212A, 0x212D, 0x212F, 0x2134, 0x2139, 0x2139, 0x213C, 0x213F, + 0x2145, 0x2149, 0x214E, 0x214E, 0x2160, 0x217F, 0x2183, 0x2184, + 0x24B6, 0x24E9, 0x2C00, 0x2CE4, 0x2CEB, 0x2CEE, 0x2CF2, 0x2CF3, + 0x2D00, 0x2D25, 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, 0xA640, 0xA66D, + 0xA680, 0xA69D, 0xA722, 0xA787, 0xA78B, 0xA78E, 0xA790, 0xA7DC, + 0xA7F1, 0xA7F6, 0xA7F8, 0xA7FA, 0xAB30, 0xAB5A, 0xAB5C, 0xAB69, + 0xAB70, 0xABBF, 0xFB00, 0xFB06, 0xFB13, 0xFB17, 0xFF21, 0xFF3A, + 0xFF41, 0xFF5A, 0x10400, 0x1044F, 0x104B0, 0x104D3, 0x104D8, 0x104FB, + 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, + 0x10597, 0x105A1, 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, + 0x10780, 0x10780, 0x10783, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, + 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, 0x10D50, 0x10D65, 0x10D70, 0x10D85, + 0x118A0, 0x118DF, 0x16E40, 0x16E7F, 0x16EA0, 0x16EB8, 0x16EBB, 0x16ED3, + 0x1D400, 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, + 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, 0x1D4BB, 0x1D4BB, + 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, + 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, + 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, 0x1D6A8, 0x1D6C0, + 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6FA, 0x1D6FC, 0x1D714, 0x1D716, 0x1D734, + 0x1D736, 0x1D74E, 0x1D750, 0x1D76E, 0x1D770, 0x1D788, 0x1D78A, 0x1D7A8, + 0x1D7AA, 0x1D7C2, 0x1D7C4, 0x1D7CB, 0x1DF00, 0x1DF09, 0x1DF0B, 0x1DF1E, + 0x1DF25, 0x1DF2A, 0x1E030, 0x1E06D, 0x1E900, 0x1E943, 0x1F130, 0x1F149, + 0x1F150, 0x1F169, 0x1F170, 0x1F189, + // #52 (5650+630): bp=Changes_When_Casefolded:CWCF + 0x0041, 0x005A, 0x00B5, 0x00B5, 0x00C0, 0x00D6, 0x00D8, 0x00DF, + 0x0100, 0x0100, 0x0102, 0x0102, 0x0104, 0x0104, 0x0106, 0x0106, + 0x0108, 0x0108, 0x010A, 0x010A, 0x010C, 0x010C, 0x010E, 0x010E, + 0x0110, 0x0110, 0x0112, 0x0112, 0x0114, 0x0114, 0x0116, 0x0116, + 0x0118, 0x0118, 0x011A, 0x011A, 0x011C, 0x011C, 0x011E, 0x011E, + 0x0120, 0x0120, 0x0122, 0x0122, 0x0124, 0x0124, 0x0126, 0x0126, + 0x0128, 0x0128, 0x012A, 0x012A, 0x012C, 0x012C, 0x012E, 0x012E, + 0x0130, 0x0130, 0x0132, 0x0132, 0x0134, 0x0134, 0x0136, 0x0136, + 0x0139, 0x0139, 0x013B, 0x013B, 0x013D, 0x013D, 0x013F, 0x013F, + 0x0141, 0x0141, 0x0143, 0x0143, 0x0145, 0x0145, 0x0147, 0x0147, + 0x0149, 0x014A, 0x014C, 0x014C, 0x014E, 0x014E, 0x0150, 0x0150, + 0x0152, 0x0152, 0x0154, 0x0154, 0x0156, 0x0156, 0x0158, 0x0158, + 0x015A, 0x015A, 0x015C, 0x015C, 0x015E, 0x015E, 0x0160, 0x0160, + 0x0162, 0x0162, 0x0164, 0x0164, 0x0166, 0x0166, 0x0168, 0x0168, + 0x016A, 0x016A, 0x016C, 0x016C, 0x016E, 0x016E, 0x0170, 0x0170, + 0x0172, 0x0172, 0x0174, 0x0174, 0x0176, 0x0176, 0x0178, 0x0179, + 0x017B, 0x017B, 0x017D, 0x017D, 0x017F, 0x017F, 0x0181, 0x0182, + 0x0184, 0x0184, 0x0186, 0x0187, 0x0189, 0x018B, 0x018E, 0x0191, + 0x0193, 0x0194, 0x0196, 0x0198, 0x019C, 0x019D, 0x019F, 0x01A0, + 0x01A2, 0x01A2, 0x01A4, 0x01A4, 0x01A6, 0x01A7, 0x01A9, 0x01A9, + 0x01AC, 0x01AC, 0x01AE, 0x01AF, 0x01B1, 0x01B3, 0x01B5, 0x01B5, + 0x01B7, 0x01B8, 0x01BC, 0x01BC, 0x01C4, 0x01C5, 0x01C7, 0x01C8, + 0x01CA, 0x01CB, 0x01CD, 0x01CD, 0x01CF, 0x01CF, 0x01D1, 0x01D1, + 0x01D3, 0x01D3, 0x01D5, 0x01D5, 0x01D7, 0x01D7, 0x01D9, 0x01D9, + 0x01DB, 0x01DB, 0x01DE, 0x01DE, 0x01E0, 0x01E0, 0x01E2, 0x01E2, + 0x01E4, 0x01E4, 0x01E6, 0x01E6, 0x01E8, 0x01E8, 0x01EA, 0x01EA, + 0x01EC, 0x01EC, 0x01EE, 0x01EE, 0x01F1, 0x01F2, 0x01F4, 0x01F4, + 0x01F6, 0x01F8, 0x01FA, 0x01FA, 0x01FC, 0x01FC, 0x01FE, 0x01FE, + 0x0200, 0x0200, 0x0202, 0x0202, 0x0204, 0x0204, 0x0206, 0x0206, + 0x0208, 0x0208, 0x020A, 0x020A, 0x020C, 0x020C, 0x020E, 0x020E, + 0x0210, 0x0210, 0x0212, 0x0212, 0x0214, 0x0214, 0x0216, 0x0216, + 0x0218, 0x0218, 0x021A, 0x021A, 0x021C, 0x021C, 0x021E, 0x021E, + 0x0220, 0x0220, 0x0222, 0x0222, 0x0224, 0x0224, 0x0226, 0x0226, + 0x0228, 0x0228, 0x022A, 0x022A, 0x022C, 0x022C, 0x022E, 0x022E, + 0x0230, 0x0230, 0x0232, 0x0232, 0x023A, 0x023B, 0x023D, 0x023E, + 0x0241, 0x0241, 0x0243, 0x0246, 0x0248, 0x0248, 0x024A, 0x024A, + 0x024C, 0x024C, 0x024E, 0x024E, 0x0345, 0x0345, 0x0370, 0x0370, + 0x0372, 0x0372, 0x0376, 0x0376, 0x037F, 0x037F, 0x0386, 0x0386, + 0x0388, 0x038A, 0x038C, 0x038C, 0x038E, 0x038F, 0x0391, 0x03A1, + 0x03A3, 0x03AB, 0x03C2, 0x03C2, 0x03CF, 0x03D1, 0x03D5, 0x03D6, + 0x03D8, 0x03D8, 0x03DA, 0x03DA, 0x03DC, 0x03DC, 0x03DE, 0x03DE, + 0x03E0, 0x03E0, 0x03E2, 0x03E2, 0x03E4, 0x03E4, 0x03E6, 0x03E6, + 0x03E8, 0x03E8, 0x03EA, 0x03EA, 0x03EC, 0x03EC, 0x03EE, 0x03EE, + 0x03F0, 0x03F1, 0x03F4, 0x03F5, 0x03F7, 0x03F7, 0x03F9, 0x03FA, + 0x03FD, 0x042F, 0x0460, 0x0460, 0x0462, 0x0462, 0x0464, 0x0464, + 0x0466, 0x0466, 0x0468, 0x0468, 0x046A, 0x046A, 0x046C, 0x046C, + 0x046E, 0x046E, 0x0470, 0x0470, 0x0472, 0x0472, 0x0474, 0x0474, + 0x0476, 0x0476, 0x0478, 0x0478, 0x047A, 0x047A, 0x047C, 0x047C, + 0x047E, 0x047E, 0x0480, 0x0480, 0x048A, 0x048A, 0x048C, 0x048C, + 0x048E, 0x048E, 0x0490, 0x0490, 0x0492, 0x0492, 0x0494, 0x0494, + 0x0496, 0x0496, 0x0498, 0x0498, 0x049A, 0x049A, 0x049C, 0x049C, + 0x049E, 0x049E, 0x04A0, 0x04A0, 0x04A2, 0x04A2, 0x04A4, 0x04A4, + 0x04A6, 0x04A6, 0x04A8, 0x04A8, 0x04AA, 0x04AA, 0x04AC, 0x04AC, + 0x04AE, 0x04AE, 0x04B0, 0x04B0, 0x04B2, 0x04B2, 0x04B4, 0x04B4, + 0x04B6, 0x04B6, 0x04B8, 0x04B8, 0x04BA, 0x04BA, 0x04BC, 0x04BC, + 0x04BE, 0x04BE, 0x04C0, 0x04C1, 0x04C3, 0x04C3, 0x04C5, 0x04C5, + 0x04C7, 0x04C7, 0x04C9, 0x04C9, 0x04CB, 0x04CB, 0x04CD, 0x04CD, + 0x04D0, 0x04D0, 0x04D2, 0x04D2, 0x04D4, 0x04D4, 0x04D6, 0x04D6, + 0x04D8, 0x04D8, 0x04DA, 0x04DA, 0x04DC, 0x04DC, 0x04DE, 0x04DE, + 0x04E0, 0x04E0, 0x04E2, 0x04E2, 0x04E4, 0x04E4, 0x04E6, 0x04E6, + 0x04E8, 0x04E8, 0x04EA, 0x04EA, 0x04EC, 0x04EC, 0x04EE, 0x04EE, + 0x04F0, 0x04F0, 0x04F2, 0x04F2, 0x04F4, 0x04F4, 0x04F6, 0x04F6, + 0x04F8, 0x04F8, 0x04FA, 0x04FA, 0x04FC, 0x04FC, 0x04FE, 0x04FE, + 0x0500, 0x0500, 0x0502, 0x0502, 0x0504, 0x0504, 0x0506, 0x0506, + 0x0508, 0x0508, 0x050A, 0x050A, 0x050C, 0x050C, 0x050E, 0x050E, + 0x0510, 0x0510, 0x0512, 0x0512, 0x0514, 0x0514, 0x0516, 0x0516, + 0x0518, 0x0518, 0x051A, 0x051A, 0x051C, 0x051C, 0x051E, 0x051E, + 0x0520, 0x0520, 0x0522, 0x0522, 0x0524, 0x0524, 0x0526, 0x0526, + 0x0528, 0x0528, 0x052A, 0x052A, 0x052C, 0x052C, 0x052E, 0x052E, + 0x0531, 0x0556, 0x0587, 0x0587, 0x10A0, 0x10C5, 0x10C7, 0x10C7, + 0x10CD, 0x10CD, 0x13F8, 0x13FD, 0x1C80, 0x1C89, 0x1C90, 0x1CBA, + 0x1CBD, 0x1CBF, 0x1E00, 0x1E00, 0x1E02, 0x1E02, 0x1E04, 0x1E04, + 0x1E06, 0x1E06, 0x1E08, 0x1E08, 0x1E0A, 0x1E0A, 0x1E0C, 0x1E0C, + 0x1E0E, 0x1E0E, 0x1E10, 0x1E10, 0x1E12, 0x1E12, 0x1E14, 0x1E14, + 0x1E16, 0x1E16, 0x1E18, 0x1E18, 0x1E1A, 0x1E1A, 0x1E1C, 0x1E1C, + 0x1E1E, 0x1E1E, 0x1E20, 0x1E20, 0x1E22, 0x1E22, 0x1E24, 0x1E24, + 0x1E26, 0x1E26, 0x1E28, 0x1E28, 0x1E2A, 0x1E2A, 0x1E2C, 0x1E2C, + 0x1E2E, 0x1E2E, 0x1E30, 0x1E30, 0x1E32, 0x1E32, 0x1E34, 0x1E34, + 0x1E36, 0x1E36, 0x1E38, 0x1E38, 0x1E3A, 0x1E3A, 0x1E3C, 0x1E3C, + 0x1E3E, 0x1E3E, 0x1E40, 0x1E40, 0x1E42, 0x1E42, 0x1E44, 0x1E44, + 0x1E46, 0x1E46, 0x1E48, 0x1E48, 0x1E4A, 0x1E4A, 0x1E4C, 0x1E4C, + 0x1E4E, 0x1E4E, 0x1E50, 0x1E50, 0x1E52, 0x1E52, 0x1E54, 0x1E54, + 0x1E56, 0x1E56, 0x1E58, 0x1E58, 0x1E5A, 0x1E5A, 0x1E5C, 0x1E5C, + 0x1E5E, 0x1E5E, 0x1E60, 0x1E60, 0x1E62, 0x1E62, 0x1E64, 0x1E64, + 0x1E66, 0x1E66, 0x1E68, 0x1E68, 0x1E6A, 0x1E6A, 0x1E6C, 0x1E6C, + 0x1E6E, 0x1E6E, 0x1E70, 0x1E70, 0x1E72, 0x1E72, 0x1E74, 0x1E74, + 0x1E76, 0x1E76, 0x1E78, 0x1E78, 0x1E7A, 0x1E7A, 0x1E7C, 0x1E7C, + 0x1E7E, 0x1E7E, 0x1E80, 0x1E80, 0x1E82, 0x1E82, 0x1E84, 0x1E84, + 0x1E86, 0x1E86, 0x1E88, 0x1E88, 0x1E8A, 0x1E8A, 0x1E8C, 0x1E8C, + 0x1E8E, 0x1E8E, 0x1E90, 0x1E90, 0x1E92, 0x1E92, 0x1E94, 0x1E94, + 0x1E9A, 0x1E9B, 0x1E9E, 0x1E9E, 0x1EA0, 0x1EA0, 0x1EA2, 0x1EA2, + 0x1EA4, 0x1EA4, 0x1EA6, 0x1EA6, 0x1EA8, 0x1EA8, 0x1EAA, 0x1EAA, + 0x1EAC, 0x1EAC, 0x1EAE, 0x1EAE, 0x1EB0, 0x1EB0, 0x1EB2, 0x1EB2, + 0x1EB4, 0x1EB4, 0x1EB6, 0x1EB6, 0x1EB8, 0x1EB8, 0x1EBA, 0x1EBA, + 0x1EBC, 0x1EBC, 0x1EBE, 0x1EBE, 0x1EC0, 0x1EC0, 0x1EC2, 0x1EC2, + 0x1EC4, 0x1EC4, 0x1EC6, 0x1EC6, 0x1EC8, 0x1EC8, 0x1ECA, 0x1ECA, + 0x1ECC, 0x1ECC, 0x1ECE, 0x1ECE, 0x1ED0, 0x1ED0, 0x1ED2, 0x1ED2, + 0x1ED4, 0x1ED4, 0x1ED6, 0x1ED6, 0x1ED8, 0x1ED8, 0x1EDA, 0x1EDA, + 0x1EDC, 0x1EDC, 0x1EDE, 0x1EDE, 0x1EE0, 0x1EE0, 0x1EE2, 0x1EE2, + 0x1EE4, 0x1EE4, 0x1EE6, 0x1EE6, 0x1EE8, 0x1EE8, 0x1EEA, 0x1EEA, + 0x1EEC, 0x1EEC, 0x1EEE, 0x1EEE, 0x1EF0, 0x1EF0, 0x1EF2, 0x1EF2, + 0x1EF4, 0x1EF4, 0x1EF6, 0x1EF6, 0x1EF8, 0x1EF8, 0x1EFA, 0x1EFA, + 0x1EFC, 0x1EFC, 0x1EFE, 0x1EFE, 0x1F08, 0x1F0F, 0x1F18, 0x1F1D, + 0x1F28, 0x1F2F, 0x1F38, 0x1F3F, 0x1F48, 0x1F4D, 0x1F59, 0x1F59, + 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F5F, 0x1F68, 0x1F6F, + 0x1F80, 0x1FAF, 0x1FB2, 0x1FB4, 0x1FB7, 0x1FBC, 0x1FC2, 0x1FC4, + 0x1FC7, 0x1FCC, 0x1FD8, 0x1FDB, 0x1FE8, 0x1FEC, 0x1FF2, 0x1FF4, + 0x1FF7, 0x1FFC, 0x2126, 0x2126, 0x212A, 0x212B, 0x2132, 0x2132, + 0x2160, 0x216F, 0x2183, 0x2183, 0x24B6, 0x24CF, 0x2C00, 0x2C2F, + 0x2C60, 0x2C60, 0x2C62, 0x2C64, 0x2C67, 0x2C67, 0x2C69, 0x2C69, + 0x2C6B, 0x2C6B, 0x2C6D, 0x2C70, 0x2C72, 0x2C72, 0x2C75, 0x2C75, + 0x2C7E, 0x2C80, 0x2C82, 0x2C82, 0x2C84, 0x2C84, 0x2C86, 0x2C86, + 0x2C88, 0x2C88, 0x2C8A, 0x2C8A, 0x2C8C, 0x2C8C, 0x2C8E, 0x2C8E, + 0x2C90, 0x2C90, 0x2C92, 0x2C92, 0x2C94, 0x2C94, 0x2C96, 0x2C96, + 0x2C98, 0x2C98, 0x2C9A, 0x2C9A, 0x2C9C, 0x2C9C, 0x2C9E, 0x2C9E, + 0x2CA0, 0x2CA0, 0x2CA2, 0x2CA2, 0x2CA4, 0x2CA4, 0x2CA6, 0x2CA6, + 0x2CA8, 0x2CA8, 0x2CAA, 0x2CAA, 0x2CAC, 0x2CAC, 0x2CAE, 0x2CAE, + 0x2CB0, 0x2CB0, 0x2CB2, 0x2CB2, 0x2CB4, 0x2CB4, 0x2CB6, 0x2CB6, + 0x2CB8, 0x2CB8, 0x2CBA, 0x2CBA, 0x2CBC, 0x2CBC, 0x2CBE, 0x2CBE, + 0x2CC0, 0x2CC0, 0x2CC2, 0x2CC2, 0x2CC4, 0x2CC4, 0x2CC6, 0x2CC6, + 0x2CC8, 0x2CC8, 0x2CCA, 0x2CCA, 0x2CCC, 0x2CCC, 0x2CCE, 0x2CCE, + 0x2CD0, 0x2CD0, 0x2CD2, 0x2CD2, 0x2CD4, 0x2CD4, 0x2CD6, 0x2CD6, + 0x2CD8, 0x2CD8, 0x2CDA, 0x2CDA, 0x2CDC, 0x2CDC, 0x2CDE, 0x2CDE, + 0x2CE0, 0x2CE0, 0x2CE2, 0x2CE2, 0x2CEB, 0x2CEB, 0x2CED, 0x2CED, + 0x2CF2, 0x2CF2, 0xA640, 0xA640, 0xA642, 0xA642, 0xA644, 0xA644, + 0xA646, 0xA646, 0xA648, 0xA648, 0xA64A, 0xA64A, 0xA64C, 0xA64C, + 0xA64E, 0xA64E, 0xA650, 0xA650, 0xA652, 0xA652, 0xA654, 0xA654, + 0xA656, 0xA656, 0xA658, 0xA658, 0xA65A, 0xA65A, 0xA65C, 0xA65C, + 0xA65E, 0xA65E, 0xA660, 0xA660, 0xA662, 0xA662, 0xA664, 0xA664, + 0xA666, 0xA666, 0xA668, 0xA668, 0xA66A, 0xA66A, 0xA66C, 0xA66C, + 0xA680, 0xA680, 0xA682, 0xA682, 0xA684, 0xA684, 0xA686, 0xA686, + 0xA688, 0xA688, 0xA68A, 0xA68A, 0xA68C, 0xA68C, 0xA68E, 0xA68E, + 0xA690, 0xA690, 0xA692, 0xA692, 0xA694, 0xA694, 0xA696, 0xA696, + 0xA698, 0xA698, 0xA69A, 0xA69A, 0xA722, 0xA722, 0xA724, 0xA724, + 0xA726, 0xA726, 0xA728, 0xA728, 0xA72A, 0xA72A, 0xA72C, 0xA72C, + 0xA72E, 0xA72E, 0xA732, 0xA732, 0xA734, 0xA734, 0xA736, 0xA736, + 0xA738, 0xA738, 0xA73A, 0xA73A, 0xA73C, 0xA73C, 0xA73E, 0xA73E, + 0xA740, 0xA740, 0xA742, 0xA742, 0xA744, 0xA744, 0xA746, 0xA746, + 0xA748, 0xA748, 0xA74A, 0xA74A, 0xA74C, 0xA74C, 0xA74E, 0xA74E, + 0xA750, 0xA750, 0xA752, 0xA752, 0xA754, 0xA754, 0xA756, 0xA756, + 0xA758, 0xA758, 0xA75A, 0xA75A, 0xA75C, 0xA75C, 0xA75E, 0xA75E, + 0xA760, 0xA760, 0xA762, 0xA762, 0xA764, 0xA764, 0xA766, 0xA766, + 0xA768, 0xA768, 0xA76A, 0xA76A, 0xA76C, 0xA76C, 0xA76E, 0xA76E, + 0xA779, 0xA779, 0xA77B, 0xA77B, 0xA77D, 0xA77E, 0xA780, 0xA780, + 0xA782, 0xA782, 0xA784, 0xA784, 0xA786, 0xA786, 0xA78B, 0xA78B, + 0xA78D, 0xA78D, 0xA790, 0xA790, 0xA792, 0xA792, 0xA796, 0xA796, + 0xA798, 0xA798, 0xA79A, 0xA79A, 0xA79C, 0xA79C, 0xA79E, 0xA79E, + 0xA7A0, 0xA7A0, 0xA7A2, 0xA7A2, 0xA7A4, 0xA7A4, 0xA7A6, 0xA7A6, + 0xA7A8, 0xA7A8, 0xA7AA, 0xA7AE, 0xA7B0, 0xA7B4, 0xA7B6, 0xA7B6, + 0xA7B8, 0xA7B8, 0xA7BA, 0xA7BA, 0xA7BC, 0xA7BC, 0xA7BE, 0xA7BE, + 0xA7C0, 0xA7C0, 0xA7C2, 0xA7C2, 0xA7C4, 0xA7C7, 0xA7C9, 0xA7C9, + 0xA7CB, 0xA7CC, 0xA7CE, 0xA7CE, 0xA7D0, 0xA7D0, 0xA7D2, 0xA7D2, + 0xA7D4, 0xA7D4, 0xA7D6, 0xA7D6, 0xA7D8, 0xA7D8, 0xA7DA, 0xA7DA, + 0xA7DC, 0xA7DC, 0xA7F5, 0xA7F5, 0xAB70, 0xABBF, 0xFB00, 0xFB06, + 0xFB13, 0xFB17, 0xFF21, 0xFF3A, 0x10400, 0x10427, 0x104B0, 0x104D3, + 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, + 0x10C80, 0x10CB2, 0x10D50, 0x10D65, 0x118A0, 0x118BF, 0x16E40, 0x16E5F, + 0x16EA0, 0x16EB8, 0x1E900, 0x1E921, + // #53 (6280+131): bp=Changes_When_Casemapped:CWCM + 0x0041, 0x005A, 0x0061, 0x007A, 0x00B5, 0x00B5, 0x00C0, 0x00D6, + 0x00D8, 0x00F6, 0x00F8, 0x0137, 0x0139, 0x018C, 0x018E, 0x01A9, + 0x01AC, 0x01B9, 0x01BC, 0x01BD, 0x01BF, 0x01BF, 0x01C4, 0x0220, + 0x0222, 0x0233, 0x023A, 0x0254, 0x0256, 0x0257, 0x0259, 0x0259, + 0x025B, 0x025C, 0x0260, 0x0261, 0x0263, 0x0266, 0x0268, 0x026C, + 0x026F, 0x026F, 0x0271, 0x0272, 0x0275, 0x0275, 0x027D, 0x027D, + 0x0280, 0x0280, 0x0282, 0x0283, 0x0287, 0x028C, 0x0292, 0x0292, + 0x029D, 0x029E, 0x0345, 0x0345, 0x0370, 0x0373, 0x0376, 0x0377, + 0x037B, 0x037D, 0x037F, 0x037F, 0x0386, 0x0386, 0x0388, 0x038A, + 0x038C, 0x038C, 0x038E, 0x03A1, 0x03A3, 0x03D1, 0x03D5, 0x03F5, + 0x03F7, 0x03FB, 0x03FD, 0x0481, 0x048A, 0x052F, 0x0531, 0x0556, + 0x0561, 0x0587, 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, + 0x10D0, 0x10FA, 0x10FD, 0x10FF, 0x13A0, 0x13F5, 0x13F8, 0x13FD, + 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1D79, 0x1D79, + 0x1D7D, 0x1D7D, 0x1D8E, 0x1D8E, 0x1E00, 0x1E9B, 0x1E9E, 0x1E9E, + 0x1EA0, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, 0x1F48, 0x1F4D, + 0x1F50, 0x1F57, 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, + 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, + 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, + 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFC, 0x2126, 0x2126, + 0x212A, 0x212B, 0x2132, 0x2132, 0x214E, 0x214E, 0x2160, 0x217F, + 0x2183, 0x2184, 0x24B6, 0x24E9, 0x2C00, 0x2C70, 0x2C72, 0x2C73, + 0x2C75, 0x2C76, 0x2C7E, 0x2CE3, 0x2CEB, 0x2CEE, 0x2CF2, 0x2CF3, + 0x2D00, 0x2D25, 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, 0xA640, 0xA66D, + 0xA680, 0xA69B, 0xA722, 0xA72F, 0xA732, 0xA76F, 0xA779, 0xA787, + 0xA78B, 0xA78D, 0xA790, 0xA794, 0xA796, 0xA7AE, 0xA7B0, 0xA7DC, + 0xA7F5, 0xA7F6, 0xAB53, 0xAB53, 0xAB70, 0xABBF, 0xFB00, 0xFB06, + 0xFB13, 0xFB17, 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, 0x10400, 0x1044F, + 0x104B0, 0x104D3, 0x104D8, 0x104FB, 0x10570, 0x1057A, 0x1057C, 0x1058A, + 0x1058C, 0x10592, 0x10594, 0x10595, 0x10597, 0x105A1, 0x105A3, 0x105B1, + 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, + 0x10D50, 0x10D65, 0x10D70, 0x10D85, 0x118A0, 0x118DF, 0x16E40, 0x16E7F, + 0x16EA0, 0x16EB8, 0x16EBB, 0x16ED3, 0x1E900, 0x1E943, + // #54 (6411+618): bp=Changes_When_Lowercased:CWL + 0x0041, 0x005A, 0x00C0, 0x00D6, 0x00D8, 0x00DE, 0x0100, 0x0100, + 0x0102, 0x0102, 0x0104, 0x0104, 0x0106, 0x0106, 0x0108, 0x0108, + 0x010A, 0x010A, 0x010C, 0x010C, 0x010E, 0x010E, 0x0110, 0x0110, + 0x0112, 0x0112, 0x0114, 0x0114, 0x0116, 0x0116, 0x0118, 0x0118, + 0x011A, 0x011A, 0x011C, 0x011C, 0x011E, 0x011E, 0x0120, 0x0120, + 0x0122, 0x0122, 0x0124, 0x0124, 0x0126, 0x0126, 0x0128, 0x0128, + 0x012A, 0x012A, 0x012C, 0x012C, 0x012E, 0x012E, 0x0130, 0x0130, + 0x0132, 0x0132, 0x0134, 0x0134, 0x0136, 0x0136, 0x0139, 0x0139, + 0x013B, 0x013B, 0x013D, 0x013D, 0x013F, 0x013F, 0x0141, 0x0141, + 0x0143, 0x0143, 0x0145, 0x0145, 0x0147, 0x0147, 0x014A, 0x014A, + 0x014C, 0x014C, 0x014E, 0x014E, 0x0150, 0x0150, 0x0152, 0x0152, + 0x0154, 0x0154, 0x0156, 0x0156, 0x0158, 0x0158, 0x015A, 0x015A, + 0x015C, 0x015C, 0x015E, 0x015E, 0x0160, 0x0160, 0x0162, 0x0162, + 0x0164, 0x0164, 0x0166, 0x0166, 0x0168, 0x0168, 0x016A, 0x016A, + 0x016C, 0x016C, 0x016E, 0x016E, 0x0170, 0x0170, 0x0172, 0x0172, + 0x0174, 0x0174, 0x0176, 0x0176, 0x0178, 0x0179, 0x017B, 0x017B, + 0x017D, 0x017D, 0x0181, 0x0182, 0x0184, 0x0184, 0x0186, 0x0187, + 0x0189, 0x018B, 0x018E, 0x0191, 0x0193, 0x0194, 0x0196, 0x0198, + 0x019C, 0x019D, 0x019F, 0x01A0, 0x01A2, 0x01A2, 0x01A4, 0x01A4, + 0x01A6, 0x01A7, 0x01A9, 0x01A9, 0x01AC, 0x01AC, 0x01AE, 0x01AF, + 0x01B1, 0x01B3, 0x01B5, 0x01B5, 0x01B7, 0x01B8, 0x01BC, 0x01BC, + 0x01C4, 0x01C5, 0x01C7, 0x01C8, 0x01CA, 0x01CB, 0x01CD, 0x01CD, + 0x01CF, 0x01CF, 0x01D1, 0x01D1, 0x01D3, 0x01D3, 0x01D5, 0x01D5, + 0x01D7, 0x01D7, 0x01D9, 0x01D9, 0x01DB, 0x01DB, 0x01DE, 0x01DE, + 0x01E0, 0x01E0, 0x01E2, 0x01E2, 0x01E4, 0x01E4, 0x01E6, 0x01E6, + 0x01E8, 0x01E8, 0x01EA, 0x01EA, 0x01EC, 0x01EC, 0x01EE, 0x01EE, + 0x01F1, 0x01F2, 0x01F4, 0x01F4, 0x01F6, 0x01F8, 0x01FA, 0x01FA, + 0x01FC, 0x01FC, 0x01FE, 0x01FE, 0x0200, 0x0200, 0x0202, 0x0202, + 0x0204, 0x0204, 0x0206, 0x0206, 0x0208, 0x0208, 0x020A, 0x020A, + 0x020C, 0x020C, 0x020E, 0x020E, 0x0210, 0x0210, 0x0212, 0x0212, + 0x0214, 0x0214, 0x0216, 0x0216, 0x0218, 0x0218, 0x021A, 0x021A, + 0x021C, 0x021C, 0x021E, 0x021E, 0x0220, 0x0220, 0x0222, 0x0222, + 0x0224, 0x0224, 0x0226, 0x0226, 0x0228, 0x0228, 0x022A, 0x022A, + 0x022C, 0x022C, 0x022E, 0x022E, 0x0230, 0x0230, 0x0232, 0x0232, + 0x023A, 0x023B, 0x023D, 0x023E, 0x0241, 0x0241, 0x0243, 0x0246, + 0x0248, 0x0248, 0x024A, 0x024A, 0x024C, 0x024C, 0x024E, 0x024E, + 0x0370, 0x0370, 0x0372, 0x0372, 0x0376, 0x0376, 0x037F, 0x037F, + 0x0386, 0x0386, 0x0388, 0x038A, 0x038C, 0x038C, 0x038E, 0x038F, + 0x0391, 0x03A1, 0x03A3, 0x03AB, 0x03CF, 0x03CF, 0x03D8, 0x03D8, + 0x03DA, 0x03DA, 0x03DC, 0x03DC, 0x03DE, 0x03DE, 0x03E0, 0x03E0, + 0x03E2, 0x03E2, 0x03E4, 0x03E4, 0x03E6, 0x03E6, 0x03E8, 0x03E8, + 0x03EA, 0x03EA, 0x03EC, 0x03EC, 0x03EE, 0x03EE, 0x03F4, 0x03F4, + 0x03F7, 0x03F7, 0x03F9, 0x03FA, 0x03FD, 0x042F, 0x0460, 0x0460, + 0x0462, 0x0462, 0x0464, 0x0464, 0x0466, 0x0466, 0x0468, 0x0468, + 0x046A, 0x046A, 0x046C, 0x046C, 0x046E, 0x046E, 0x0470, 0x0470, + 0x0472, 0x0472, 0x0474, 0x0474, 0x0476, 0x0476, 0x0478, 0x0478, + 0x047A, 0x047A, 0x047C, 0x047C, 0x047E, 0x047E, 0x0480, 0x0480, + 0x048A, 0x048A, 0x048C, 0x048C, 0x048E, 0x048E, 0x0490, 0x0490, + 0x0492, 0x0492, 0x0494, 0x0494, 0x0496, 0x0496, 0x0498, 0x0498, + 0x049A, 0x049A, 0x049C, 0x049C, 0x049E, 0x049E, 0x04A0, 0x04A0, + 0x04A2, 0x04A2, 0x04A4, 0x04A4, 0x04A6, 0x04A6, 0x04A8, 0x04A8, + 0x04AA, 0x04AA, 0x04AC, 0x04AC, 0x04AE, 0x04AE, 0x04B0, 0x04B0, + 0x04B2, 0x04B2, 0x04B4, 0x04B4, 0x04B6, 0x04B6, 0x04B8, 0x04B8, + 0x04BA, 0x04BA, 0x04BC, 0x04BC, 0x04BE, 0x04BE, 0x04C0, 0x04C1, + 0x04C3, 0x04C3, 0x04C5, 0x04C5, 0x04C7, 0x04C7, 0x04C9, 0x04C9, + 0x04CB, 0x04CB, 0x04CD, 0x04CD, 0x04D0, 0x04D0, 0x04D2, 0x04D2, + 0x04D4, 0x04D4, 0x04D6, 0x04D6, 0x04D8, 0x04D8, 0x04DA, 0x04DA, + 0x04DC, 0x04DC, 0x04DE, 0x04DE, 0x04E0, 0x04E0, 0x04E2, 0x04E2, + 0x04E4, 0x04E4, 0x04E6, 0x04E6, 0x04E8, 0x04E8, 0x04EA, 0x04EA, + 0x04EC, 0x04EC, 0x04EE, 0x04EE, 0x04F0, 0x04F0, 0x04F2, 0x04F2, + 0x04F4, 0x04F4, 0x04F6, 0x04F6, 0x04F8, 0x04F8, 0x04FA, 0x04FA, + 0x04FC, 0x04FC, 0x04FE, 0x04FE, 0x0500, 0x0500, 0x0502, 0x0502, + 0x0504, 0x0504, 0x0506, 0x0506, 0x0508, 0x0508, 0x050A, 0x050A, + 0x050C, 0x050C, 0x050E, 0x050E, 0x0510, 0x0510, 0x0512, 0x0512, + 0x0514, 0x0514, 0x0516, 0x0516, 0x0518, 0x0518, 0x051A, 0x051A, + 0x051C, 0x051C, 0x051E, 0x051E, 0x0520, 0x0520, 0x0522, 0x0522, + 0x0524, 0x0524, 0x0526, 0x0526, 0x0528, 0x0528, 0x052A, 0x052A, + 0x052C, 0x052C, 0x052E, 0x052E, 0x0531, 0x0556, 0x10A0, 0x10C5, + 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x13A0, 0x13F5, 0x1C89, 0x1C89, + 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1E00, 0x1E00, 0x1E02, 0x1E02, + 0x1E04, 0x1E04, 0x1E06, 0x1E06, 0x1E08, 0x1E08, 0x1E0A, 0x1E0A, + 0x1E0C, 0x1E0C, 0x1E0E, 0x1E0E, 0x1E10, 0x1E10, 0x1E12, 0x1E12, + 0x1E14, 0x1E14, 0x1E16, 0x1E16, 0x1E18, 0x1E18, 0x1E1A, 0x1E1A, + 0x1E1C, 0x1E1C, 0x1E1E, 0x1E1E, 0x1E20, 0x1E20, 0x1E22, 0x1E22, + 0x1E24, 0x1E24, 0x1E26, 0x1E26, 0x1E28, 0x1E28, 0x1E2A, 0x1E2A, + 0x1E2C, 0x1E2C, 0x1E2E, 0x1E2E, 0x1E30, 0x1E30, 0x1E32, 0x1E32, + 0x1E34, 0x1E34, 0x1E36, 0x1E36, 0x1E38, 0x1E38, 0x1E3A, 0x1E3A, + 0x1E3C, 0x1E3C, 0x1E3E, 0x1E3E, 0x1E40, 0x1E40, 0x1E42, 0x1E42, + 0x1E44, 0x1E44, 0x1E46, 0x1E46, 0x1E48, 0x1E48, 0x1E4A, 0x1E4A, + 0x1E4C, 0x1E4C, 0x1E4E, 0x1E4E, 0x1E50, 0x1E50, 0x1E52, 0x1E52, + 0x1E54, 0x1E54, 0x1E56, 0x1E56, 0x1E58, 0x1E58, 0x1E5A, 0x1E5A, + 0x1E5C, 0x1E5C, 0x1E5E, 0x1E5E, 0x1E60, 0x1E60, 0x1E62, 0x1E62, + 0x1E64, 0x1E64, 0x1E66, 0x1E66, 0x1E68, 0x1E68, 0x1E6A, 0x1E6A, + 0x1E6C, 0x1E6C, 0x1E6E, 0x1E6E, 0x1E70, 0x1E70, 0x1E72, 0x1E72, + 0x1E74, 0x1E74, 0x1E76, 0x1E76, 0x1E78, 0x1E78, 0x1E7A, 0x1E7A, + 0x1E7C, 0x1E7C, 0x1E7E, 0x1E7E, 0x1E80, 0x1E80, 0x1E82, 0x1E82, + 0x1E84, 0x1E84, 0x1E86, 0x1E86, 0x1E88, 0x1E88, 0x1E8A, 0x1E8A, + 0x1E8C, 0x1E8C, 0x1E8E, 0x1E8E, 0x1E90, 0x1E90, 0x1E92, 0x1E92, + 0x1E94, 0x1E94, 0x1E9E, 0x1E9E, 0x1EA0, 0x1EA0, 0x1EA2, 0x1EA2, + 0x1EA4, 0x1EA4, 0x1EA6, 0x1EA6, 0x1EA8, 0x1EA8, 0x1EAA, 0x1EAA, + 0x1EAC, 0x1EAC, 0x1EAE, 0x1EAE, 0x1EB0, 0x1EB0, 0x1EB2, 0x1EB2, + 0x1EB4, 0x1EB4, 0x1EB6, 0x1EB6, 0x1EB8, 0x1EB8, 0x1EBA, 0x1EBA, + 0x1EBC, 0x1EBC, 0x1EBE, 0x1EBE, 0x1EC0, 0x1EC0, 0x1EC2, 0x1EC2, + 0x1EC4, 0x1EC4, 0x1EC6, 0x1EC6, 0x1EC8, 0x1EC8, 0x1ECA, 0x1ECA, + 0x1ECC, 0x1ECC, 0x1ECE, 0x1ECE, 0x1ED0, 0x1ED0, 0x1ED2, 0x1ED2, + 0x1ED4, 0x1ED4, 0x1ED6, 0x1ED6, 0x1ED8, 0x1ED8, 0x1EDA, 0x1EDA, + 0x1EDC, 0x1EDC, 0x1EDE, 0x1EDE, 0x1EE0, 0x1EE0, 0x1EE2, 0x1EE2, + 0x1EE4, 0x1EE4, 0x1EE6, 0x1EE6, 0x1EE8, 0x1EE8, 0x1EEA, 0x1EEA, + 0x1EEC, 0x1EEC, 0x1EEE, 0x1EEE, 0x1EF0, 0x1EF0, 0x1EF2, 0x1EF2, + 0x1EF4, 0x1EF4, 0x1EF6, 0x1EF6, 0x1EF8, 0x1EF8, 0x1EFA, 0x1EFA, + 0x1EFC, 0x1EFC, 0x1EFE, 0x1EFE, 0x1F08, 0x1F0F, 0x1F18, 0x1F1D, + 0x1F28, 0x1F2F, 0x1F38, 0x1F3F, 0x1F48, 0x1F4D, 0x1F59, 0x1F59, + 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F5F, 0x1F68, 0x1F6F, + 0x1F88, 0x1F8F, 0x1F98, 0x1F9F, 0x1FA8, 0x1FAF, 0x1FB8, 0x1FBC, + 0x1FC8, 0x1FCC, 0x1FD8, 0x1FDB, 0x1FE8, 0x1FEC, 0x1FF8, 0x1FFC, + 0x2126, 0x2126, 0x212A, 0x212B, 0x2132, 0x2132, 0x2160, 0x216F, + 0x2183, 0x2183, 0x24B6, 0x24CF, 0x2C00, 0x2C2F, 0x2C60, 0x2C60, + 0x2C62, 0x2C64, 0x2C67, 0x2C67, 0x2C69, 0x2C69, 0x2C6B, 0x2C6B, + 0x2C6D, 0x2C70, 0x2C72, 0x2C72, 0x2C75, 0x2C75, 0x2C7E, 0x2C80, + 0x2C82, 0x2C82, 0x2C84, 0x2C84, 0x2C86, 0x2C86, 0x2C88, 0x2C88, + 0x2C8A, 0x2C8A, 0x2C8C, 0x2C8C, 0x2C8E, 0x2C8E, 0x2C90, 0x2C90, + 0x2C92, 0x2C92, 0x2C94, 0x2C94, 0x2C96, 0x2C96, 0x2C98, 0x2C98, + 0x2C9A, 0x2C9A, 0x2C9C, 0x2C9C, 0x2C9E, 0x2C9E, 0x2CA0, 0x2CA0, + 0x2CA2, 0x2CA2, 0x2CA4, 0x2CA4, 0x2CA6, 0x2CA6, 0x2CA8, 0x2CA8, + 0x2CAA, 0x2CAA, 0x2CAC, 0x2CAC, 0x2CAE, 0x2CAE, 0x2CB0, 0x2CB0, + 0x2CB2, 0x2CB2, 0x2CB4, 0x2CB4, 0x2CB6, 0x2CB6, 0x2CB8, 0x2CB8, + 0x2CBA, 0x2CBA, 0x2CBC, 0x2CBC, 0x2CBE, 0x2CBE, 0x2CC0, 0x2CC0, + 0x2CC2, 0x2CC2, 0x2CC4, 0x2CC4, 0x2CC6, 0x2CC6, 0x2CC8, 0x2CC8, + 0x2CCA, 0x2CCA, 0x2CCC, 0x2CCC, 0x2CCE, 0x2CCE, 0x2CD0, 0x2CD0, + 0x2CD2, 0x2CD2, 0x2CD4, 0x2CD4, 0x2CD6, 0x2CD6, 0x2CD8, 0x2CD8, + 0x2CDA, 0x2CDA, 0x2CDC, 0x2CDC, 0x2CDE, 0x2CDE, 0x2CE0, 0x2CE0, + 0x2CE2, 0x2CE2, 0x2CEB, 0x2CEB, 0x2CED, 0x2CED, 0x2CF2, 0x2CF2, + 0xA640, 0xA640, 0xA642, 0xA642, 0xA644, 0xA644, 0xA646, 0xA646, + 0xA648, 0xA648, 0xA64A, 0xA64A, 0xA64C, 0xA64C, 0xA64E, 0xA64E, + 0xA650, 0xA650, 0xA652, 0xA652, 0xA654, 0xA654, 0xA656, 0xA656, + 0xA658, 0xA658, 0xA65A, 0xA65A, 0xA65C, 0xA65C, 0xA65E, 0xA65E, + 0xA660, 0xA660, 0xA662, 0xA662, 0xA664, 0xA664, 0xA666, 0xA666, + 0xA668, 0xA668, 0xA66A, 0xA66A, 0xA66C, 0xA66C, 0xA680, 0xA680, + 0xA682, 0xA682, 0xA684, 0xA684, 0xA686, 0xA686, 0xA688, 0xA688, + 0xA68A, 0xA68A, 0xA68C, 0xA68C, 0xA68E, 0xA68E, 0xA690, 0xA690, + 0xA692, 0xA692, 0xA694, 0xA694, 0xA696, 0xA696, 0xA698, 0xA698, + 0xA69A, 0xA69A, 0xA722, 0xA722, 0xA724, 0xA724, 0xA726, 0xA726, + 0xA728, 0xA728, 0xA72A, 0xA72A, 0xA72C, 0xA72C, 0xA72E, 0xA72E, + 0xA732, 0xA732, 0xA734, 0xA734, 0xA736, 0xA736, 0xA738, 0xA738, + 0xA73A, 0xA73A, 0xA73C, 0xA73C, 0xA73E, 0xA73E, 0xA740, 0xA740, + 0xA742, 0xA742, 0xA744, 0xA744, 0xA746, 0xA746, 0xA748, 0xA748, + 0xA74A, 0xA74A, 0xA74C, 0xA74C, 0xA74E, 0xA74E, 0xA750, 0xA750, + 0xA752, 0xA752, 0xA754, 0xA754, 0xA756, 0xA756, 0xA758, 0xA758, + 0xA75A, 0xA75A, 0xA75C, 0xA75C, 0xA75E, 0xA75E, 0xA760, 0xA760, + 0xA762, 0xA762, 0xA764, 0xA764, 0xA766, 0xA766, 0xA768, 0xA768, + 0xA76A, 0xA76A, 0xA76C, 0xA76C, 0xA76E, 0xA76E, 0xA779, 0xA779, + 0xA77B, 0xA77B, 0xA77D, 0xA77E, 0xA780, 0xA780, 0xA782, 0xA782, + 0xA784, 0xA784, 0xA786, 0xA786, 0xA78B, 0xA78B, 0xA78D, 0xA78D, + 0xA790, 0xA790, 0xA792, 0xA792, 0xA796, 0xA796, 0xA798, 0xA798, + 0xA79A, 0xA79A, 0xA79C, 0xA79C, 0xA79E, 0xA79E, 0xA7A0, 0xA7A0, + 0xA7A2, 0xA7A2, 0xA7A4, 0xA7A4, 0xA7A6, 0xA7A6, 0xA7A8, 0xA7A8, + 0xA7AA, 0xA7AE, 0xA7B0, 0xA7B4, 0xA7B6, 0xA7B6, 0xA7B8, 0xA7B8, + 0xA7BA, 0xA7BA, 0xA7BC, 0xA7BC, 0xA7BE, 0xA7BE, 0xA7C0, 0xA7C0, + 0xA7C2, 0xA7C2, 0xA7C4, 0xA7C7, 0xA7C9, 0xA7C9, 0xA7CB, 0xA7CC, + 0xA7CE, 0xA7CE, 0xA7D0, 0xA7D0, 0xA7D2, 0xA7D2, 0xA7D4, 0xA7D4, + 0xA7D6, 0xA7D6, 0xA7D8, 0xA7D8, 0xA7DA, 0xA7DA, 0xA7DC, 0xA7DC, + 0xA7F5, 0xA7F5, 0xFF21, 0xFF3A, 0x10400, 0x10427, 0x104B0, 0x104D3, + 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, + 0x10C80, 0x10CB2, 0x10D50, 0x10D65, 0x118A0, 0x118BF, 0x16E40, 0x16E5F, + 0x16EA0, 0x16EB8, 0x1E900, 0x1E921, + // #55 (7029+848): bp=Changes_When_NFKC_Casefolded:CWKCF + 0x0041, 0x005A, 0x00A0, 0x00A0, 0x00A8, 0x00A8, 0x00AA, 0x00AA, + 0x00AD, 0x00AD, 0x00AF, 0x00AF, 0x00B2, 0x00B5, 0x00B8, 0x00BA, + 0x00BC, 0x00BE, 0x00C0, 0x00D6, 0x00D8, 0x00DF, 0x0100, 0x0100, + 0x0102, 0x0102, 0x0104, 0x0104, 0x0106, 0x0106, 0x0108, 0x0108, + 0x010A, 0x010A, 0x010C, 0x010C, 0x010E, 0x010E, 0x0110, 0x0110, + 0x0112, 0x0112, 0x0114, 0x0114, 0x0116, 0x0116, 0x0118, 0x0118, + 0x011A, 0x011A, 0x011C, 0x011C, 0x011E, 0x011E, 0x0120, 0x0120, + 0x0122, 0x0122, 0x0124, 0x0124, 0x0126, 0x0126, 0x0128, 0x0128, + 0x012A, 0x012A, 0x012C, 0x012C, 0x012E, 0x012E, 0x0130, 0x0130, + 0x0132, 0x0134, 0x0136, 0x0136, 0x0139, 0x0139, 0x013B, 0x013B, + 0x013D, 0x013D, 0x013F, 0x0141, 0x0143, 0x0143, 0x0145, 0x0145, + 0x0147, 0x0147, 0x0149, 0x014A, 0x014C, 0x014C, 0x014E, 0x014E, + 0x0150, 0x0150, 0x0152, 0x0152, 0x0154, 0x0154, 0x0156, 0x0156, + 0x0158, 0x0158, 0x015A, 0x015A, 0x015C, 0x015C, 0x015E, 0x015E, + 0x0160, 0x0160, 0x0162, 0x0162, 0x0164, 0x0164, 0x0166, 0x0166, + 0x0168, 0x0168, 0x016A, 0x016A, 0x016C, 0x016C, 0x016E, 0x016E, + 0x0170, 0x0170, 0x0172, 0x0172, 0x0174, 0x0174, 0x0176, 0x0176, + 0x0178, 0x0179, 0x017B, 0x017B, 0x017D, 0x017D, 0x017F, 0x017F, + 0x0181, 0x0182, 0x0184, 0x0184, 0x0186, 0x0187, 0x0189, 0x018B, + 0x018E, 0x0191, 0x0193, 0x0194, 0x0196, 0x0198, 0x019C, 0x019D, + 0x019F, 0x01A0, 0x01A2, 0x01A2, 0x01A4, 0x01A4, 0x01A6, 0x01A7, + 0x01A9, 0x01A9, 0x01AC, 0x01AC, 0x01AE, 0x01AF, 0x01B1, 0x01B3, + 0x01B5, 0x01B5, 0x01B7, 0x01B8, 0x01BC, 0x01BC, 0x01C4, 0x01CD, + 0x01CF, 0x01CF, 0x01D1, 0x01D1, 0x01D3, 0x01D3, 0x01D5, 0x01D5, + 0x01D7, 0x01D7, 0x01D9, 0x01D9, 0x01DB, 0x01DB, 0x01DE, 0x01DE, + 0x01E0, 0x01E0, 0x01E2, 0x01E2, 0x01E4, 0x01E4, 0x01E6, 0x01E6, + 0x01E8, 0x01E8, 0x01EA, 0x01EA, 0x01EC, 0x01EC, 0x01EE, 0x01EE, + 0x01F1, 0x01F4, 0x01F6, 0x01F8, 0x01FA, 0x01FA, 0x01FC, 0x01FC, + 0x01FE, 0x01FE, 0x0200, 0x0200, 0x0202, 0x0202, 0x0204, 0x0204, + 0x0206, 0x0206, 0x0208, 0x0208, 0x020A, 0x020A, 0x020C, 0x020C, + 0x020E, 0x020E, 0x0210, 0x0210, 0x0212, 0x0212, 0x0214, 0x0214, + 0x0216, 0x0216, 0x0218, 0x0218, 0x021A, 0x021A, 0x021C, 0x021C, + 0x021E, 0x021E, 0x0220, 0x0220, 0x0222, 0x0222, 0x0224, 0x0224, + 0x0226, 0x0226, 0x0228, 0x0228, 0x022A, 0x022A, 0x022C, 0x022C, + 0x022E, 0x022E, 0x0230, 0x0230, 0x0232, 0x0232, 0x023A, 0x023B, + 0x023D, 0x023E, 0x0241, 0x0241, 0x0243, 0x0246, 0x0248, 0x0248, + 0x024A, 0x024A, 0x024C, 0x024C, 0x024E, 0x024E, 0x02B0, 0x02B8, + 0x02D8, 0x02DD, 0x02E0, 0x02E4, 0x0340, 0x0341, 0x0343, 0x0345, + 0x034F, 0x034F, 0x0370, 0x0370, 0x0372, 0x0372, 0x0374, 0x0374, + 0x0376, 0x0376, 0x037A, 0x037A, 0x037E, 0x037F, 0x0384, 0x038A, + 0x038C, 0x038C, 0x038E, 0x038F, 0x0391, 0x03A1, 0x03A3, 0x03AB, + 0x03C2, 0x03C2, 0x03CF, 0x03D6, 0x03D8, 0x03D8, 0x03DA, 0x03DA, + 0x03DC, 0x03DC, 0x03DE, 0x03DE, 0x03E0, 0x03E0, 0x03E2, 0x03E2, + 0x03E4, 0x03E4, 0x03E6, 0x03E6, 0x03E8, 0x03E8, 0x03EA, 0x03EA, + 0x03EC, 0x03EC, 0x03EE, 0x03EE, 0x03F0, 0x03F2, 0x03F4, 0x03F5, + 0x03F7, 0x03F7, 0x03F9, 0x03FA, 0x03FD, 0x042F, 0x0460, 0x0460, + 0x0462, 0x0462, 0x0464, 0x0464, 0x0466, 0x0466, 0x0468, 0x0468, + 0x046A, 0x046A, 0x046C, 0x046C, 0x046E, 0x046E, 0x0470, 0x0470, + 0x0472, 0x0472, 0x0474, 0x0474, 0x0476, 0x0476, 0x0478, 0x0478, + 0x047A, 0x047A, 0x047C, 0x047C, 0x047E, 0x047E, 0x0480, 0x0480, + 0x048A, 0x048A, 0x048C, 0x048C, 0x048E, 0x048E, 0x0490, 0x0490, + 0x0492, 0x0492, 0x0494, 0x0494, 0x0496, 0x0496, 0x0498, 0x0498, + 0x049A, 0x049A, 0x049C, 0x049C, 0x049E, 0x049E, 0x04A0, 0x04A0, + 0x04A2, 0x04A2, 0x04A4, 0x04A4, 0x04A6, 0x04A6, 0x04A8, 0x04A8, + 0x04AA, 0x04AA, 0x04AC, 0x04AC, 0x04AE, 0x04AE, 0x04B0, 0x04B0, + 0x04B2, 0x04B2, 0x04B4, 0x04B4, 0x04B6, 0x04B6, 0x04B8, 0x04B8, + 0x04BA, 0x04BA, 0x04BC, 0x04BC, 0x04BE, 0x04BE, 0x04C0, 0x04C1, + 0x04C3, 0x04C3, 0x04C5, 0x04C5, 0x04C7, 0x04C7, 0x04C9, 0x04C9, + 0x04CB, 0x04CB, 0x04CD, 0x04CD, 0x04D0, 0x04D0, 0x04D2, 0x04D2, + 0x04D4, 0x04D4, 0x04D6, 0x04D6, 0x04D8, 0x04D8, 0x04DA, 0x04DA, + 0x04DC, 0x04DC, 0x04DE, 0x04DE, 0x04E0, 0x04E0, 0x04E2, 0x04E2, + 0x04E4, 0x04E4, 0x04E6, 0x04E6, 0x04E8, 0x04E8, 0x04EA, 0x04EA, + 0x04EC, 0x04EC, 0x04EE, 0x04EE, 0x04F0, 0x04F0, 0x04F2, 0x04F2, + 0x04F4, 0x04F4, 0x04F6, 0x04F6, 0x04F8, 0x04F8, 0x04FA, 0x04FA, + 0x04FC, 0x04FC, 0x04FE, 0x04FE, 0x0500, 0x0500, 0x0502, 0x0502, + 0x0504, 0x0504, 0x0506, 0x0506, 0x0508, 0x0508, 0x050A, 0x050A, + 0x050C, 0x050C, 0x050E, 0x050E, 0x0510, 0x0510, 0x0512, 0x0512, + 0x0514, 0x0514, 0x0516, 0x0516, 0x0518, 0x0518, 0x051A, 0x051A, + 0x051C, 0x051C, 0x051E, 0x051E, 0x0520, 0x0520, 0x0522, 0x0522, + 0x0524, 0x0524, 0x0526, 0x0526, 0x0528, 0x0528, 0x052A, 0x052A, + 0x052C, 0x052C, 0x052E, 0x052E, 0x0531, 0x0556, 0x0587, 0x0587, + 0x061C, 0x061C, 0x0675, 0x0678, 0x0958, 0x095F, 0x09DC, 0x09DD, + 0x09DF, 0x09DF, 0x0A33, 0x0A33, 0x0A36, 0x0A36, 0x0A59, 0x0A5B, + 0x0A5E, 0x0A5E, 0x0B5C, 0x0B5D, 0x0E33, 0x0E33, 0x0EB3, 0x0EB3, + 0x0EDC, 0x0EDD, 0x0F0C, 0x0F0C, 0x0F43, 0x0F43, 0x0F4D, 0x0F4D, + 0x0F52, 0x0F52, 0x0F57, 0x0F57, 0x0F5C, 0x0F5C, 0x0F69, 0x0F69, + 0x0F73, 0x0F73, 0x0F75, 0x0F79, 0x0F81, 0x0F81, 0x0F93, 0x0F93, + 0x0F9D, 0x0F9D, 0x0FA2, 0x0FA2, 0x0FA7, 0x0FA7, 0x0FAC, 0x0FAC, + 0x0FB9, 0x0FB9, 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, + 0x10FC, 0x10FC, 0x115F, 0x1160, 0x13F8, 0x13FD, 0x17B4, 0x17B5, + 0x180B, 0x180F, 0x1C80, 0x1C89, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, + 0x1D2C, 0x1D2E, 0x1D30, 0x1D3A, 0x1D3C, 0x1D4D, 0x1D4F, 0x1D6A, + 0x1D78, 0x1D78, 0x1D9B, 0x1DBF, 0x1E00, 0x1E00, 0x1E02, 0x1E02, + 0x1E04, 0x1E04, 0x1E06, 0x1E06, 0x1E08, 0x1E08, 0x1E0A, 0x1E0A, + 0x1E0C, 0x1E0C, 0x1E0E, 0x1E0E, 0x1E10, 0x1E10, 0x1E12, 0x1E12, + 0x1E14, 0x1E14, 0x1E16, 0x1E16, 0x1E18, 0x1E18, 0x1E1A, 0x1E1A, + 0x1E1C, 0x1E1C, 0x1E1E, 0x1E1E, 0x1E20, 0x1E20, 0x1E22, 0x1E22, + 0x1E24, 0x1E24, 0x1E26, 0x1E26, 0x1E28, 0x1E28, 0x1E2A, 0x1E2A, + 0x1E2C, 0x1E2C, 0x1E2E, 0x1E2E, 0x1E30, 0x1E30, 0x1E32, 0x1E32, + 0x1E34, 0x1E34, 0x1E36, 0x1E36, 0x1E38, 0x1E38, 0x1E3A, 0x1E3A, + 0x1E3C, 0x1E3C, 0x1E3E, 0x1E3E, 0x1E40, 0x1E40, 0x1E42, 0x1E42, + 0x1E44, 0x1E44, 0x1E46, 0x1E46, 0x1E48, 0x1E48, 0x1E4A, 0x1E4A, + 0x1E4C, 0x1E4C, 0x1E4E, 0x1E4E, 0x1E50, 0x1E50, 0x1E52, 0x1E52, + 0x1E54, 0x1E54, 0x1E56, 0x1E56, 0x1E58, 0x1E58, 0x1E5A, 0x1E5A, + 0x1E5C, 0x1E5C, 0x1E5E, 0x1E5E, 0x1E60, 0x1E60, 0x1E62, 0x1E62, + 0x1E64, 0x1E64, 0x1E66, 0x1E66, 0x1E68, 0x1E68, 0x1E6A, 0x1E6A, + 0x1E6C, 0x1E6C, 0x1E6E, 0x1E6E, 0x1E70, 0x1E70, 0x1E72, 0x1E72, + 0x1E74, 0x1E74, 0x1E76, 0x1E76, 0x1E78, 0x1E78, 0x1E7A, 0x1E7A, + 0x1E7C, 0x1E7C, 0x1E7E, 0x1E7E, 0x1E80, 0x1E80, 0x1E82, 0x1E82, + 0x1E84, 0x1E84, 0x1E86, 0x1E86, 0x1E88, 0x1E88, 0x1E8A, 0x1E8A, + 0x1E8C, 0x1E8C, 0x1E8E, 0x1E8E, 0x1E90, 0x1E90, 0x1E92, 0x1E92, + 0x1E94, 0x1E94, 0x1E9A, 0x1E9B, 0x1E9E, 0x1E9E, 0x1EA0, 0x1EA0, + 0x1EA2, 0x1EA2, 0x1EA4, 0x1EA4, 0x1EA6, 0x1EA6, 0x1EA8, 0x1EA8, + 0x1EAA, 0x1EAA, 0x1EAC, 0x1EAC, 0x1EAE, 0x1EAE, 0x1EB0, 0x1EB0, + 0x1EB2, 0x1EB2, 0x1EB4, 0x1EB4, 0x1EB6, 0x1EB6, 0x1EB8, 0x1EB8, + 0x1EBA, 0x1EBA, 0x1EBC, 0x1EBC, 0x1EBE, 0x1EBE, 0x1EC0, 0x1EC0, + 0x1EC2, 0x1EC2, 0x1EC4, 0x1EC4, 0x1EC6, 0x1EC6, 0x1EC8, 0x1EC8, + 0x1ECA, 0x1ECA, 0x1ECC, 0x1ECC, 0x1ECE, 0x1ECE, 0x1ED0, 0x1ED0, + 0x1ED2, 0x1ED2, 0x1ED4, 0x1ED4, 0x1ED6, 0x1ED6, 0x1ED8, 0x1ED8, + 0x1EDA, 0x1EDA, 0x1EDC, 0x1EDC, 0x1EDE, 0x1EDE, 0x1EE0, 0x1EE0, + 0x1EE2, 0x1EE2, 0x1EE4, 0x1EE4, 0x1EE6, 0x1EE6, 0x1EE8, 0x1EE8, + 0x1EEA, 0x1EEA, 0x1EEC, 0x1EEC, 0x1EEE, 0x1EEE, 0x1EF0, 0x1EF0, + 0x1EF2, 0x1EF2, 0x1EF4, 0x1EF4, 0x1EF6, 0x1EF6, 0x1EF8, 0x1EF8, + 0x1EFA, 0x1EFA, 0x1EFC, 0x1EFC, 0x1EFE, 0x1EFE, 0x1F08, 0x1F0F, + 0x1F18, 0x1F1D, 0x1F28, 0x1F2F, 0x1F38, 0x1F3F, 0x1F48, 0x1F4D, + 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F5F, + 0x1F68, 0x1F6F, 0x1F71, 0x1F71, 0x1F73, 0x1F73, 0x1F75, 0x1F75, + 0x1F77, 0x1F77, 0x1F79, 0x1F79, 0x1F7B, 0x1F7B, 0x1F7D, 0x1F7D, + 0x1F80, 0x1FAF, 0x1FB2, 0x1FB4, 0x1FB7, 0x1FC4, 0x1FC7, 0x1FCF, + 0x1FD3, 0x1FD3, 0x1FD8, 0x1FDB, 0x1FDD, 0x1FDF, 0x1FE3, 0x1FE3, + 0x1FE8, 0x1FEF, 0x1FF2, 0x1FF4, 0x1FF7, 0x1FFE, 0x2000, 0x200F, + 0x2011, 0x2011, 0x2017, 0x2017, 0x2024, 0x2026, 0x202A, 0x202F, + 0x2033, 0x2034, 0x2036, 0x2037, 0x203C, 0x203C, 0x203E, 0x203E, + 0x2047, 0x2049, 0x2057, 0x2057, 0x205F, 0x2071, 0x2074, 0x208E, + 0x2090, 0x209C, 0x20A8, 0x20A8, 0x2100, 0x2103, 0x2105, 0x2107, + 0x2109, 0x2113, 0x2115, 0x2116, 0x2119, 0x211D, 0x2120, 0x2122, + 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x212D, + 0x212F, 0x2139, 0x213B, 0x2140, 0x2145, 0x2149, 0x2150, 0x217F, + 0x2183, 0x2183, 0x2189, 0x2189, 0x222C, 0x222D, 0x222F, 0x2230, + 0x2329, 0x232A, 0x2460, 0x24EA, 0x2A0C, 0x2A0C, 0x2A74, 0x2A76, + 0x2ADC, 0x2ADC, 0x2C00, 0x2C2F, 0x2C60, 0x2C60, 0x2C62, 0x2C64, + 0x2C67, 0x2C67, 0x2C69, 0x2C69, 0x2C6B, 0x2C6B, 0x2C6D, 0x2C70, + 0x2C72, 0x2C72, 0x2C75, 0x2C75, 0x2C7C, 0x2C80, 0x2C82, 0x2C82, + 0x2C84, 0x2C84, 0x2C86, 0x2C86, 0x2C88, 0x2C88, 0x2C8A, 0x2C8A, + 0x2C8C, 0x2C8C, 0x2C8E, 0x2C8E, 0x2C90, 0x2C90, 0x2C92, 0x2C92, + 0x2C94, 0x2C94, 0x2C96, 0x2C96, 0x2C98, 0x2C98, 0x2C9A, 0x2C9A, + 0x2C9C, 0x2C9C, 0x2C9E, 0x2C9E, 0x2CA0, 0x2CA0, 0x2CA2, 0x2CA2, + 0x2CA4, 0x2CA4, 0x2CA6, 0x2CA6, 0x2CA8, 0x2CA8, 0x2CAA, 0x2CAA, + 0x2CAC, 0x2CAC, 0x2CAE, 0x2CAE, 0x2CB0, 0x2CB0, 0x2CB2, 0x2CB2, + 0x2CB4, 0x2CB4, 0x2CB6, 0x2CB6, 0x2CB8, 0x2CB8, 0x2CBA, 0x2CBA, + 0x2CBC, 0x2CBC, 0x2CBE, 0x2CBE, 0x2CC0, 0x2CC0, 0x2CC2, 0x2CC2, + 0x2CC4, 0x2CC4, 0x2CC6, 0x2CC6, 0x2CC8, 0x2CC8, 0x2CCA, 0x2CCA, + 0x2CCC, 0x2CCC, 0x2CCE, 0x2CCE, 0x2CD0, 0x2CD0, 0x2CD2, 0x2CD2, + 0x2CD4, 0x2CD4, 0x2CD6, 0x2CD6, 0x2CD8, 0x2CD8, 0x2CDA, 0x2CDA, + 0x2CDC, 0x2CDC, 0x2CDE, 0x2CDE, 0x2CE0, 0x2CE0, 0x2CE2, 0x2CE2, + 0x2CEB, 0x2CEB, 0x2CED, 0x2CED, 0x2CF2, 0x2CF2, 0x2D6F, 0x2D6F, + 0x2E9F, 0x2E9F, 0x2EF3, 0x2EF3, 0x2F00, 0x2FD5, 0x3000, 0x3000, + 0x3036, 0x3036, 0x3038, 0x303A, 0x309B, 0x309C, 0x309F, 0x309F, + 0x30FF, 0x30FF, 0x3131, 0x318E, 0x3192, 0x319F, 0x3200, 0x321E, + 0x3220, 0x3247, 0x3250, 0x327E, 0x3280, 0x33FF, 0xA640, 0xA640, + 0xA642, 0xA642, 0xA644, 0xA644, 0xA646, 0xA646, 0xA648, 0xA648, + 0xA64A, 0xA64A, 0xA64C, 0xA64C, 0xA64E, 0xA64E, 0xA650, 0xA650, + 0xA652, 0xA652, 0xA654, 0xA654, 0xA656, 0xA656, 0xA658, 0xA658, + 0xA65A, 0xA65A, 0xA65C, 0xA65C, 0xA65E, 0xA65E, 0xA660, 0xA660, + 0xA662, 0xA662, 0xA664, 0xA664, 0xA666, 0xA666, 0xA668, 0xA668, + 0xA66A, 0xA66A, 0xA66C, 0xA66C, 0xA680, 0xA680, 0xA682, 0xA682, + 0xA684, 0xA684, 0xA686, 0xA686, 0xA688, 0xA688, 0xA68A, 0xA68A, + 0xA68C, 0xA68C, 0xA68E, 0xA68E, 0xA690, 0xA690, 0xA692, 0xA692, + 0xA694, 0xA694, 0xA696, 0xA696, 0xA698, 0xA698, 0xA69A, 0xA69A, + 0xA69C, 0xA69D, 0xA722, 0xA722, 0xA724, 0xA724, 0xA726, 0xA726, + 0xA728, 0xA728, 0xA72A, 0xA72A, 0xA72C, 0xA72C, 0xA72E, 0xA72E, + 0xA732, 0xA732, 0xA734, 0xA734, 0xA736, 0xA736, 0xA738, 0xA738, + 0xA73A, 0xA73A, 0xA73C, 0xA73C, 0xA73E, 0xA73E, 0xA740, 0xA740, + 0xA742, 0xA742, 0xA744, 0xA744, 0xA746, 0xA746, 0xA748, 0xA748, + 0xA74A, 0xA74A, 0xA74C, 0xA74C, 0xA74E, 0xA74E, 0xA750, 0xA750, + 0xA752, 0xA752, 0xA754, 0xA754, 0xA756, 0xA756, 0xA758, 0xA758, + 0xA75A, 0xA75A, 0xA75C, 0xA75C, 0xA75E, 0xA75E, 0xA760, 0xA760, + 0xA762, 0xA762, 0xA764, 0xA764, 0xA766, 0xA766, 0xA768, 0xA768, + 0xA76A, 0xA76A, 0xA76C, 0xA76C, 0xA76E, 0xA76E, 0xA770, 0xA770, + 0xA779, 0xA779, 0xA77B, 0xA77B, 0xA77D, 0xA77E, 0xA780, 0xA780, + 0xA782, 0xA782, 0xA784, 0xA784, 0xA786, 0xA786, 0xA78B, 0xA78B, + 0xA78D, 0xA78D, 0xA790, 0xA790, 0xA792, 0xA792, 0xA796, 0xA796, + 0xA798, 0xA798, 0xA79A, 0xA79A, 0xA79C, 0xA79C, 0xA79E, 0xA79E, + 0xA7A0, 0xA7A0, 0xA7A2, 0xA7A2, 0xA7A4, 0xA7A4, 0xA7A6, 0xA7A6, + 0xA7A8, 0xA7A8, 0xA7AA, 0xA7AE, 0xA7B0, 0xA7B4, 0xA7B6, 0xA7B6, + 0xA7B8, 0xA7B8, 0xA7BA, 0xA7BA, 0xA7BC, 0xA7BC, 0xA7BE, 0xA7BE, + 0xA7C0, 0xA7C0, 0xA7C2, 0xA7C2, 0xA7C4, 0xA7C7, 0xA7C9, 0xA7C9, + 0xA7CB, 0xA7CC, 0xA7CE, 0xA7CE, 0xA7D0, 0xA7D0, 0xA7D2, 0xA7D2, + 0xA7D4, 0xA7D4, 0xA7D6, 0xA7D6, 0xA7D8, 0xA7D8, 0xA7DA, 0xA7DA, + 0xA7DC, 0xA7DC, 0xA7F1, 0xA7F5, 0xA7F8, 0xA7F9, 0xAB5C, 0xAB5F, + 0xAB69, 0xAB69, 0xAB70, 0xABBF, 0xF900, 0xFA0D, 0xFA10, 0xFA10, + 0xFA12, 0xFA12, 0xFA15, 0xFA1E, 0xFA20, 0xFA20, 0xFA22, 0xFA22, + 0xFA25, 0xFA26, 0xFA2A, 0xFA6D, 0xFA70, 0xFAD9, 0xFB00, 0xFB06, + 0xFB13, 0xFB17, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB36, 0xFB38, 0xFB3C, + 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFBB1, + 0xFBD3, 0xFD3D, 0xFD50, 0xFD8F, 0xFD92, 0xFDC7, 0xFDF0, 0xFDFC, + 0xFE00, 0xFE19, 0xFE30, 0xFE44, 0xFE47, 0xFE52, 0xFE54, 0xFE66, + 0xFE68, 0xFE6B, 0xFE70, 0xFE72, 0xFE74, 0xFE74, 0xFE76, 0xFEFC, + 0xFEFF, 0xFEFF, 0xFF01, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, + 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0xFFE0, 0xFFE6, 0xFFE8, 0xFFEE, + 0xFFF0, 0xFFF8, 0x10400, 0x10427, 0x104B0, 0x104D3, 0x10570, 0x1057A, + 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, 0x10781, 0x10785, + 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10C80, 0x10CB2, 0x10D50, 0x10D65, + 0x118A0, 0x118BF, 0x16E40, 0x16E5F, 0x16EA0, 0x16EB8, 0x1BCA0, 0x1BCA3, + 0x1CCD6, 0x1CCF9, 0x1D15E, 0x1D164, 0x1D173, 0x1D17A, 0x1D1BB, 0x1D1C0, + 0x1D400, 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, + 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, 0x1D4BB, 0x1D4BB, + 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, + 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, + 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, 0x1D6A8, 0x1D7CB, + 0x1D7CE, 0x1D7FF, 0x1E030, 0x1E06D, 0x1E900, 0x1E921, 0x1EE00, 0x1EE03, + 0x1EE05, 0x1EE1F, 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, + 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, 0x1EE3B, 0x1EE3B, + 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, + 0x1EE4D, 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, + 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, + 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, + 0x1EE74, 0x1EE77, 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, + 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, + 0x1F100, 0x1F10A, 0x1F110, 0x1F12E, 0x1F130, 0x1F14F, 0x1F16A, 0x1F16C, + 0x1F190, 0x1F190, 0x1F200, 0x1F202, 0x1F210, 0x1F23B, 0x1F240, 0x1F248, + 0x1F250, 0x1F251, 0x1FBF0, 0x1FBF9, 0x2F800, 0x2FA1D, 0xE0000, 0xE0FFF, + // #56 (7877+633): bp=Changes_When_Titlecased:CWT + 0x0061, 0x007A, 0x00B5, 0x00B5, 0x00DF, 0x00F6, 0x00F8, 0x00FF, + 0x0101, 0x0101, 0x0103, 0x0103, 0x0105, 0x0105, 0x0107, 0x0107, + 0x0109, 0x0109, 0x010B, 0x010B, 0x010D, 0x010D, 0x010F, 0x010F, + 0x0111, 0x0111, 0x0113, 0x0113, 0x0115, 0x0115, 0x0117, 0x0117, + 0x0119, 0x0119, 0x011B, 0x011B, 0x011D, 0x011D, 0x011F, 0x011F, + 0x0121, 0x0121, 0x0123, 0x0123, 0x0125, 0x0125, 0x0127, 0x0127, + 0x0129, 0x0129, 0x012B, 0x012B, 0x012D, 0x012D, 0x012F, 0x012F, + 0x0131, 0x0131, 0x0133, 0x0133, 0x0135, 0x0135, 0x0137, 0x0137, + 0x013A, 0x013A, 0x013C, 0x013C, 0x013E, 0x013E, 0x0140, 0x0140, + 0x0142, 0x0142, 0x0144, 0x0144, 0x0146, 0x0146, 0x0148, 0x0149, + 0x014B, 0x014B, 0x014D, 0x014D, 0x014F, 0x014F, 0x0151, 0x0151, + 0x0153, 0x0153, 0x0155, 0x0155, 0x0157, 0x0157, 0x0159, 0x0159, + 0x015B, 0x015B, 0x015D, 0x015D, 0x015F, 0x015F, 0x0161, 0x0161, + 0x0163, 0x0163, 0x0165, 0x0165, 0x0167, 0x0167, 0x0169, 0x0169, + 0x016B, 0x016B, 0x016D, 0x016D, 0x016F, 0x016F, 0x0171, 0x0171, + 0x0173, 0x0173, 0x0175, 0x0175, 0x0177, 0x0177, 0x017A, 0x017A, + 0x017C, 0x017C, 0x017E, 0x0180, 0x0183, 0x0183, 0x0185, 0x0185, + 0x0188, 0x0188, 0x018C, 0x018C, 0x0192, 0x0192, 0x0195, 0x0195, + 0x0199, 0x019B, 0x019E, 0x019E, 0x01A1, 0x01A1, 0x01A3, 0x01A3, + 0x01A5, 0x01A5, 0x01A8, 0x01A8, 0x01AD, 0x01AD, 0x01B0, 0x01B0, + 0x01B4, 0x01B4, 0x01B6, 0x01B6, 0x01B9, 0x01B9, 0x01BD, 0x01BD, + 0x01BF, 0x01BF, 0x01C4, 0x01C4, 0x01C6, 0x01C7, 0x01C9, 0x01CA, + 0x01CC, 0x01CC, 0x01CE, 0x01CE, 0x01D0, 0x01D0, 0x01D2, 0x01D2, + 0x01D4, 0x01D4, 0x01D6, 0x01D6, 0x01D8, 0x01D8, 0x01DA, 0x01DA, + 0x01DC, 0x01DD, 0x01DF, 0x01DF, 0x01E1, 0x01E1, 0x01E3, 0x01E3, + 0x01E5, 0x01E5, 0x01E7, 0x01E7, 0x01E9, 0x01E9, 0x01EB, 0x01EB, + 0x01ED, 0x01ED, 0x01EF, 0x01F1, 0x01F3, 0x01F3, 0x01F5, 0x01F5, + 0x01F9, 0x01F9, 0x01FB, 0x01FB, 0x01FD, 0x01FD, 0x01FF, 0x01FF, + 0x0201, 0x0201, 0x0203, 0x0203, 0x0205, 0x0205, 0x0207, 0x0207, + 0x0209, 0x0209, 0x020B, 0x020B, 0x020D, 0x020D, 0x020F, 0x020F, + 0x0211, 0x0211, 0x0213, 0x0213, 0x0215, 0x0215, 0x0217, 0x0217, + 0x0219, 0x0219, 0x021B, 0x021B, 0x021D, 0x021D, 0x021F, 0x021F, + 0x0223, 0x0223, 0x0225, 0x0225, 0x0227, 0x0227, 0x0229, 0x0229, + 0x022B, 0x022B, 0x022D, 0x022D, 0x022F, 0x022F, 0x0231, 0x0231, + 0x0233, 0x0233, 0x023C, 0x023C, 0x023F, 0x0240, 0x0242, 0x0242, + 0x0247, 0x0247, 0x0249, 0x0249, 0x024B, 0x024B, 0x024D, 0x024D, + 0x024F, 0x0254, 0x0256, 0x0257, 0x0259, 0x0259, 0x025B, 0x025C, + 0x0260, 0x0261, 0x0263, 0x0266, 0x0268, 0x026C, 0x026F, 0x026F, + 0x0271, 0x0272, 0x0275, 0x0275, 0x027D, 0x027D, 0x0280, 0x0280, + 0x0282, 0x0283, 0x0287, 0x028C, 0x0292, 0x0292, 0x029D, 0x029E, + 0x0345, 0x0345, 0x0371, 0x0371, 0x0373, 0x0373, 0x0377, 0x0377, + 0x037B, 0x037D, 0x0390, 0x0390, 0x03AC, 0x03CE, 0x03D0, 0x03D1, + 0x03D5, 0x03D7, 0x03D9, 0x03D9, 0x03DB, 0x03DB, 0x03DD, 0x03DD, + 0x03DF, 0x03DF, 0x03E1, 0x03E1, 0x03E3, 0x03E3, 0x03E5, 0x03E5, + 0x03E7, 0x03E7, 0x03E9, 0x03E9, 0x03EB, 0x03EB, 0x03ED, 0x03ED, + 0x03EF, 0x03F3, 0x03F5, 0x03F5, 0x03F8, 0x03F8, 0x03FB, 0x03FB, + 0x0430, 0x045F, 0x0461, 0x0461, 0x0463, 0x0463, 0x0465, 0x0465, + 0x0467, 0x0467, 0x0469, 0x0469, 0x046B, 0x046B, 0x046D, 0x046D, + 0x046F, 0x046F, 0x0471, 0x0471, 0x0473, 0x0473, 0x0475, 0x0475, + 0x0477, 0x0477, 0x0479, 0x0479, 0x047B, 0x047B, 0x047D, 0x047D, + 0x047F, 0x047F, 0x0481, 0x0481, 0x048B, 0x048B, 0x048D, 0x048D, + 0x048F, 0x048F, 0x0491, 0x0491, 0x0493, 0x0493, 0x0495, 0x0495, + 0x0497, 0x0497, 0x0499, 0x0499, 0x049B, 0x049B, 0x049D, 0x049D, + 0x049F, 0x049F, 0x04A1, 0x04A1, 0x04A3, 0x04A3, 0x04A5, 0x04A5, + 0x04A7, 0x04A7, 0x04A9, 0x04A9, 0x04AB, 0x04AB, 0x04AD, 0x04AD, + 0x04AF, 0x04AF, 0x04B1, 0x04B1, 0x04B3, 0x04B3, 0x04B5, 0x04B5, + 0x04B7, 0x04B7, 0x04B9, 0x04B9, 0x04BB, 0x04BB, 0x04BD, 0x04BD, + 0x04BF, 0x04BF, 0x04C2, 0x04C2, 0x04C4, 0x04C4, 0x04C6, 0x04C6, + 0x04C8, 0x04C8, 0x04CA, 0x04CA, 0x04CC, 0x04CC, 0x04CE, 0x04CF, + 0x04D1, 0x04D1, 0x04D3, 0x04D3, 0x04D5, 0x04D5, 0x04D7, 0x04D7, + 0x04D9, 0x04D9, 0x04DB, 0x04DB, 0x04DD, 0x04DD, 0x04DF, 0x04DF, + 0x04E1, 0x04E1, 0x04E3, 0x04E3, 0x04E5, 0x04E5, 0x04E7, 0x04E7, + 0x04E9, 0x04E9, 0x04EB, 0x04EB, 0x04ED, 0x04ED, 0x04EF, 0x04EF, + 0x04F1, 0x04F1, 0x04F3, 0x04F3, 0x04F5, 0x04F5, 0x04F7, 0x04F7, + 0x04F9, 0x04F9, 0x04FB, 0x04FB, 0x04FD, 0x04FD, 0x04FF, 0x04FF, + 0x0501, 0x0501, 0x0503, 0x0503, 0x0505, 0x0505, 0x0507, 0x0507, + 0x0509, 0x0509, 0x050B, 0x050B, 0x050D, 0x050D, 0x050F, 0x050F, + 0x0511, 0x0511, 0x0513, 0x0513, 0x0515, 0x0515, 0x0517, 0x0517, + 0x0519, 0x0519, 0x051B, 0x051B, 0x051D, 0x051D, 0x051F, 0x051F, + 0x0521, 0x0521, 0x0523, 0x0523, 0x0525, 0x0525, 0x0527, 0x0527, + 0x0529, 0x0529, 0x052B, 0x052B, 0x052D, 0x052D, 0x052F, 0x052F, + 0x0561, 0x0587, 0x13F8, 0x13FD, 0x1C80, 0x1C88, 0x1C8A, 0x1C8A, + 0x1D79, 0x1D79, 0x1D7D, 0x1D7D, 0x1D8E, 0x1D8E, 0x1E01, 0x1E01, + 0x1E03, 0x1E03, 0x1E05, 0x1E05, 0x1E07, 0x1E07, 0x1E09, 0x1E09, + 0x1E0B, 0x1E0B, 0x1E0D, 0x1E0D, 0x1E0F, 0x1E0F, 0x1E11, 0x1E11, + 0x1E13, 0x1E13, 0x1E15, 0x1E15, 0x1E17, 0x1E17, 0x1E19, 0x1E19, + 0x1E1B, 0x1E1B, 0x1E1D, 0x1E1D, 0x1E1F, 0x1E1F, 0x1E21, 0x1E21, + 0x1E23, 0x1E23, 0x1E25, 0x1E25, 0x1E27, 0x1E27, 0x1E29, 0x1E29, + 0x1E2B, 0x1E2B, 0x1E2D, 0x1E2D, 0x1E2F, 0x1E2F, 0x1E31, 0x1E31, + 0x1E33, 0x1E33, 0x1E35, 0x1E35, 0x1E37, 0x1E37, 0x1E39, 0x1E39, + 0x1E3B, 0x1E3B, 0x1E3D, 0x1E3D, 0x1E3F, 0x1E3F, 0x1E41, 0x1E41, + 0x1E43, 0x1E43, 0x1E45, 0x1E45, 0x1E47, 0x1E47, 0x1E49, 0x1E49, + 0x1E4B, 0x1E4B, 0x1E4D, 0x1E4D, 0x1E4F, 0x1E4F, 0x1E51, 0x1E51, + 0x1E53, 0x1E53, 0x1E55, 0x1E55, 0x1E57, 0x1E57, 0x1E59, 0x1E59, + 0x1E5B, 0x1E5B, 0x1E5D, 0x1E5D, 0x1E5F, 0x1E5F, 0x1E61, 0x1E61, + 0x1E63, 0x1E63, 0x1E65, 0x1E65, 0x1E67, 0x1E67, 0x1E69, 0x1E69, + 0x1E6B, 0x1E6B, 0x1E6D, 0x1E6D, 0x1E6F, 0x1E6F, 0x1E71, 0x1E71, + 0x1E73, 0x1E73, 0x1E75, 0x1E75, 0x1E77, 0x1E77, 0x1E79, 0x1E79, + 0x1E7B, 0x1E7B, 0x1E7D, 0x1E7D, 0x1E7F, 0x1E7F, 0x1E81, 0x1E81, + 0x1E83, 0x1E83, 0x1E85, 0x1E85, 0x1E87, 0x1E87, 0x1E89, 0x1E89, + 0x1E8B, 0x1E8B, 0x1E8D, 0x1E8D, 0x1E8F, 0x1E8F, 0x1E91, 0x1E91, + 0x1E93, 0x1E93, 0x1E95, 0x1E9B, 0x1EA1, 0x1EA1, 0x1EA3, 0x1EA3, + 0x1EA5, 0x1EA5, 0x1EA7, 0x1EA7, 0x1EA9, 0x1EA9, 0x1EAB, 0x1EAB, + 0x1EAD, 0x1EAD, 0x1EAF, 0x1EAF, 0x1EB1, 0x1EB1, 0x1EB3, 0x1EB3, + 0x1EB5, 0x1EB5, 0x1EB7, 0x1EB7, 0x1EB9, 0x1EB9, 0x1EBB, 0x1EBB, + 0x1EBD, 0x1EBD, 0x1EBF, 0x1EBF, 0x1EC1, 0x1EC1, 0x1EC3, 0x1EC3, + 0x1EC5, 0x1EC5, 0x1EC7, 0x1EC7, 0x1EC9, 0x1EC9, 0x1ECB, 0x1ECB, + 0x1ECD, 0x1ECD, 0x1ECF, 0x1ECF, 0x1ED1, 0x1ED1, 0x1ED3, 0x1ED3, + 0x1ED5, 0x1ED5, 0x1ED7, 0x1ED7, 0x1ED9, 0x1ED9, 0x1EDB, 0x1EDB, + 0x1EDD, 0x1EDD, 0x1EDF, 0x1EDF, 0x1EE1, 0x1EE1, 0x1EE3, 0x1EE3, + 0x1EE5, 0x1EE5, 0x1EE7, 0x1EE7, 0x1EE9, 0x1EE9, 0x1EEB, 0x1EEB, + 0x1EED, 0x1EED, 0x1EEF, 0x1EEF, 0x1EF1, 0x1EF1, 0x1EF3, 0x1EF3, + 0x1EF5, 0x1EF5, 0x1EF7, 0x1EF7, 0x1EF9, 0x1EF9, 0x1EFB, 0x1EFB, + 0x1EFD, 0x1EFD, 0x1EFF, 0x1F07, 0x1F10, 0x1F15, 0x1F20, 0x1F27, + 0x1F30, 0x1F37, 0x1F40, 0x1F45, 0x1F50, 0x1F57, 0x1F60, 0x1F67, + 0x1F70, 0x1F7D, 0x1F80, 0x1F87, 0x1F90, 0x1F97, 0x1FA0, 0x1FA7, + 0x1FB0, 0x1FB4, 0x1FB6, 0x1FB7, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, + 0x1FC6, 0x1FC7, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FD7, 0x1FE0, 0x1FE7, + 0x1FF2, 0x1FF4, 0x1FF6, 0x1FF7, 0x214E, 0x214E, 0x2170, 0x217F, + 0x2184, 0x2184, 0x24D0, 0x24E9, 0x2C30, 0x2C5F, 0x2C61, 0x2C61, + 0x2C65, 0x2C66, 0x2C68, 0x2C68, 0x2C6A, 0x2C6A, 0x2C6C, 0x2C6C, + 0x2C73, 0x2C73, 0x2C76, 0x2C76, 0x2C81, 0x2C81, 0x2C83, 0x2C83, + 0x2C85, 0x2C85, 0x2C87, 0x2C87, 0x2C89, 0x2C89, 0x2C8B, 0x2C8B, + 0x2C8D, 0x2C8D, 0x2C8F, 0x2C8F, 0x2C91, 0x2C91, 0x2C93, 0x2C93, + 0x2C95, 0x2C95, 0x2C97, 0x2C97, 0x2C99, 0x2C99, 0x2C9B, 0x2C9B, + 0x2C9D, 0x2C9D, 0x2C9F, 0x2C9F, 0x2CA1, 0x2CA1, 0x2CA3, 0x2CA3, + 0x2CA5, 0x2CA5, 0x2CA7, 0x2CA7, 0x2CA9, 0x2CA9, 0x2CAB, 0x2CAB, + 0x2CAD, 0x2CAD, 0x2CAF, 0x2CAF, 0x2CB1, 0x2CB1, 0x2CB3, 0x2CB3, + 0x2CB5, 0x2CB5, 0x2CB7, 0x2CB7, 0x2CB9, 0x2CB9, 0x2CBB, 0x2CBB, + 0x2CBD, 0x2CBD, 0x2CBF, 0x2CBF, 0x2CC1, 0x2CC1, 0x2CC3, 0x2CC3, + 0x2CC5, 0x2CC5, 0x2CC7, 0x2CC7, 0x2CC9, 0x2CC9, 0x2CCB, 0x2CCB, + 0x2CCD, 0x2CCD, 0x2CCF, 0x2CCF, 0x2CD1, 0x2CD1, 0x2CD3, 0x2CD3, + 0x2CD5, 0x2CD5, 0x2CD7, 0x2CD7, 0x2CD9, 0x2CD9, 0x2CDB, 0x2CDB, + 0x2CDD, 0x2CDD, 0x2CDF, 0x2CDF, 0x2CE1, 0x2CE1, 0x2CE3, 0x2CE3, + 0x2CEC, 0x2CEC, 0x2CEE, 0x2CEE, 0x2CF3, 0x2CF3, 0x2D00, 0x2D25, + 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, 0xA641, 0xA641, 0xA643, 0xA643, + 0xA645, 0xA645, 0xA647, 0xA647, 0xA649, 0xA649, 0xA64B, 0xA64B, + 0xA64D, 0xA64D, 0xA64F, 0xA64F, 0xA651, 0xA651, 0xA653, 0xA653, + 0xA655, 0xA655, 0xA657, 0xA657, 0xA659, 0xA659, 0xA65B, 0xA65B, + 0xA65D, 0xA65D, 0xA65F, 0xA65F, 0xA661, 0xA661, 0xA663, 0xA663, + 0xA665, 0xA665, 0xA667, 0xA667, 0xA669, 0xA669, 0xA66B, 0xA66B, + 0xA66D, 0xA66D, 0xA681, 0xA681, 0xA683, 0xA683, 0xA685, 0xA685, + 0xA687, 0xA687, 0xA689, 0xA689, 0xA68B, 0xA68B, 0xA68D, 0xA68D, + 0xA68F, 0xA68F, 0xA691, 0xA691, 0xA693, 0xA693, 0xA695, 0xA695, + 0xA697, 0xA697, 0xA699, 0xA699, 0xA69B, 0xA69B, 0xA723, 0xA723, + 0xA725, 0xA725, 0xA727, 0xA727, 0xA729, 0xA729, 0xA72B, 0xA72B, + 0xA72D, 0xA72D, 0xA72F, 0xA72F, 0xA733, 0xA733, 0xA735, 0xA735, + 0xA737, 0xA737, 0xA739, 0xA739, 0xA73B, 0xA73B, 0xA73D, 0xA73D, + 0xA73F, 0xA73F, 0xA741, 0xA741, 0xA743, 0xA743, 0xA745, 0xA745, + 0xA747, 0xA747, 0xA749, 0xA749, 0xA74B, 0xA74B, 0xA74D, 0xA74D, + 0xA74F, 0xA74F, 0xA751, 0xA751, 0xA753, 0xA753, 0xA755, 0xA755, + 0xA757, 0xA757, 0xA759, 0xA759, 0xA75B, 0xA75B, 0xA75D, 0xA75D, + 0xA75F, 0xA75F, 0xA761, 0xA761, 0xA763, 0xA763, 0xA765, 0xA765, + 0xA767, 0xA767, 0xA769, 0xA769, 0xA76B, 0xA76B, 0xA76D, 0xA76D, + 0xA76F, 0xA76F, 0xA77A, 0xA77A, 0xA77C, 0xA77C, 0xA77F, 0xA77F, + 0xA781, 0xA781, 0xA783, 0xA783, 0xA785, 0xA785, 0xA787, 0xA787, + 0xA78C, 0xA78C, 0xA791, 0xA791, 0xA793, 0xA794, 0xA797, 0xA797, + 0xA799, 0xA799, 0xA79B, 0xA79B, 0xA79D, 0xA79D, 0xA79F, 0xA79F, + 0xA7A1, 0xA7A1, 0xA7A3, 0xA7A3, 0xA7A5, 0xA7A5, 0xA7A7, 0xA7A7, + 0xA7A9, 0xA7A9, 0xA7B5, 0xA7B5, 0xA7B7, 0xA7B7, 0xA7B9, 0xA7B9, + 0xA7BB, 0xA7BB, 0xA7BD, 0xA7BD, 0xA7BF, 0xA7BF, 0xA7C1, 0xA7C1, + 0xA7C3, 0xA7C3, 0xA7C8, 0xA7C8, 0xA7CA, 0xA7CA, 0xA7CD, 0xA7CD, + 0xA7CF, 0xA7CF, 0xA7D1, 0xA7D1, 0xA7D3, 0xA7D3, 0xA7D5, 0xA7D5, + 0xA7D7, 0xA7D7, 0xA7D9, 0xA7D9, 0xA7DB, 0xA7DB, 0xA7F6, 0xA7F6, + 0xAB53, 0xAB53, 0xAB70, 0xABBF, 0xFB00, 0xFB06, 0xFB13, 0xFB17, + 0xFF41, 0xFF5A, 0x10428, 0x1044F, 0x104D8, 0x104FB, 0x10597, 0x105A1, + 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x10CC0, 0x10CF2, + 0x10D70, 0x10D85, 0x118C0, 0x118DF, 0x16E60, 0x16E7F, 0x16EBB, 0x16ED3, + 0x1E922, 0x1E943, + // #57 (8510+634): bp=Changes_When_Uppercased:CWU + 0x0061, 0x007A, 0x00B5, 0x00B5, 0x00DF, 0x00F6, 0x00F8, 0x00FF, + 0x0101, 0x0101, 0x0103, 0x0103, 0x0105, 0x0105, 0x0107, 0x0107, + 0x0109, 0x0109, 0x010B, 0x010B, 0x010D, 0x010D, 0x010F, 0x010F, + 0x0111, 0x0111, 0x0113, 0x0113, 0x0115, 0x0115, 0x0117, 0x0117, + 0x0119, 0x0119, 0x011B, 0x011B, 0x011D, 0x011D, 0x011F, 0x011F, + 0x0121, 0x0121, 0x0123, 0x0123, 0x0125, 0x0125, 0x0127, 0x0127, + 0x0129, 0x0129, 0x012B, 0x012B, 0x012D, 0x012D, 0x012F, 0x012F, + 0x0131, 0x0131, 0x0133, 0x0133, 0x0135, 0x0135, 0x0137, 0x0137, + 0x013A, 0x013A, 0x013C, 0x013C, 0x013E, 0x013E, 0x0140, 0x0140, + 0x0142, 0x0142, 0x0144, 0x0144, 0x0146, 0x0146, 0x0148, 0x0149, + 0x014B, 0x014B, 0x014D, 0x014D, 0x014F, 0x014F, 0x0151, 0x0151, + 0x0153, 0x0153, 0x0155, 0x0155, 0x0157, 0x0157, 0x0159, 0x0159, + 0x015B, 0x015B, 0x015D, 0x015D, 0x015F, 0x015F, 0x0161, 0x0161, + 0x0163, 0x0163, 0x0165, 0x0165, 0x0167, 0x0167, 0x0169, 0x0169, + 0x016B, 0x016B, 0x016D, 0x016D, 0x016F, 0x016F, 0x0171, 0x0171, + 0x0173, 0x0173, 0x0175, 0x0175, 0x0177, 0x0177, 0x017A, 0x017A, + 0x017C, 0x017C, 0x017E, 0x0180, 0x0183, 0x0183, 0x0185, 0x0185, + 0x0188, 0x0188, 0x018C, 0x018C, 0x0192, 0x0192, 0x0195, 0x0195, + 0x0199, 0x019B, 0x019E, 0x019E, 0x01A1, 0x01A1, 0x01A3, 0x01A3, + 0x01A5, 0x01A5, 0x01A8, 0x01A8, 0x01AD, 0x01AD, 0x01B0, 0x01B0, + 0x01B4, 0x01B4, 0x01B6, 0x01B6, 0x01B9, 0x01B9, 0x01BD, 0x01BD, + 0x01BF, 0x01BF, 0x01C5, 0x01C6, 0x01C8, 0x01C9, 0x01CB, 0x01CC, + 0x01CE, 0x01CE, 0x01D0, 0x01D0, 0x01D2, 0x01D2, 0x01D4, 0x01D4, + 0x01D6, 0x01D6, 0x01D8, 0x01D8, 0x01DA, 0x01DA, 0x01DC, 0x01DD, + 0x01DF, 0x01DF, 0x01E1, 0x01E1, 0x01E3, 0x01E3, 0x01E5, 0x01E5, + 0x01E7, 0x01E7, 0x01E9, 0x01E9, 0x01EB, 0x01EB, 0x01ED, 0x01ED, + 0x01EF, 0x01F0, 0x01F2, 0x01F3, 0x01F5, 0x01F5, 0x01F9, 0x01F9, + 0x01FB, 0x01FB, 0x01FD, 0x01FD, 0x01FF, 0x01FF, 0x0201, 0x0201, + 0x0203, 0x0203, 0x0205, 0x0205, 0x0207, 0x0207, 0x0209, 0x0209, + 0x020B, 0x020B, 0x020D, 0x020D, 0x020F, 0x020F, 0x0211, 0x0211, + 0x0213, 0x0213, 0x0215, 0x0215, 0x0217, 0x0217, 0x0219, 0x0219, + 0x021B, 0x021B, 0x021D, 0x021D, 0x021F, 0x021F, 0x0223, 0x0223, + 0x0225, 0x0225, 0x0227, 0x0227, 0x0229, 0x0229, 0x022B, 0x022B, + 0x022D, 0x022D, 0x022F, 0x022F, 0x0231, 0x0231, 0x0233, 0x0233, + 0x023C, 0x023C, 0x023F, 0x0240, 0x0242, 0x0242, 0x0247, 0x0247, + 0x0249, 0x0249, 0x024B, 0x024B, 0x024D, 0x024D, 0x024F, 0x0254, + 0x0256, 0x0257, 0x0259, 0x0259, 0x025B, 0x025C, 0x0260, 0x0261, + 0x0263, 0x0266, 0x0268, 0x026C, 0x026F, 0x026F, 0x0271, 0x0272, + 0x0275, 0x0275, 0x027D, 0x027D, 0x0280, 0x0280, 0x0282, 0x0283, + 0x0287, 0x028C, 0x0292, 0x0292, 0x029D, 0x029E, 0x0345, 0x0345, + 0x0371, 0x0371, 0x0373, 0x0373, 0x0377, 0x0377, 0x037B, 0x037D, + 0x0390, 0x0390, 0x03AC, 0x03CE, 0x03D0, 0x03D1, 0x03D5, 0x03D7, + 0x03D9, 0x03D9, 0x03DB, 0x03DB, 0x03DD, 0x03DD, 0x03DF, 0x03DF, + 0x03E1, 0x03E1, 0x03E3, 0x03E3, 0x03E5, 0x03E5, 0x03E7, 0x03E7, + 0x03E9, 0x03E9, 0x03EB, 0x03EB, 0x03ED, 0x03ED, 0x03EF, 0x03F3, + 0x03F5, 0x03F5, 0x03F8, 0x03F8, 0x03FB, 0x03FB, 0x0430, 0x045F, + 0x0461, 0x0461, 0x0463, 0x0463, 0x0465, 0x0465, 0x0467, 0x0467, + 0x0469, 0x0469, 0x046B, 0x046B, 0x046D, 0x046D, 0x046F, 0x046F, + 0x0471, 0x0471, 0x0473, 0x0473, 0x0475, 0x0475, 0x0477, 0x0477, + 0x0479, 0x0479, 0x047B, 0x047B, 0x047D, 0x047D, 0x047F, 0x047F, + 0x0481, 0x0481, 0x048B, 0x048B, 0x048D, 0x048D, 0x048F, 0x048F, + 0x0491, 0x0491, 0x0493, 0x0493, 0x0495, 0x0495, 0x0497, 0x0497, + 0x0499, 0x0499, 0x049B, 0x049B, 0x049D, 0x049D, 0x049F, 0x049F, + 0x04A1, 0x04A1, 0x04A3, 0x04A3, 0x04A5, 0x04A5, 0x04A7, 0x04A7, + 0x04A9, 0x04A9, 0x04AB, 0x04AB, 0x04AD, 0x04AD, 0x04AF, 0x04AF, + 0x04B1, 0x04B1, 0x04B3, 0x04B3, 0x04B5, 0x04B5, 0x04B7, 0x04B7, + 0x04B9, 0x04B9, 0x04BB, 0x04BB, 0x04BD, 0x04BD, 0x04BF, 0x04BF, + 0x04C2, 0x04C2, 0x04C4, 0x04C4, 0x04C6, 0x04C6, 0x04C8, 0x04C8, + 0x04CA, 0x04CA, 0x04CC, 0x04CC, 0x04CE, 0x04CF, 0x04D1, 0x04D1, + 0x04D3, 0x04D3, 0x04D5, 0x04D5, 0x04D7, 0x04D7, 0x04D9, 0x04D9, + 0x04DB, 0x04DB, 0x04DD, 0x04DD, 0x04DF, 0x04DF, 0x04E1, 0x04E1, + 0x04E3, 0x04E3, 0x04E5, 0x04E5, 0x04E7, 0x04E7, 0x04E9, 0x04E9, + 0x04EB, 0x04EB, 0x04ED, 0x04ED, 0x04EF, 0x04EF, 0x04F1, 0x04F1, + 0x04F3, 0x04F3, 0x04F5, 0x04F5, 0x04F7, 0x04F7, 0x04F9, 0x04F9, + 0x04FB, 0x04FB, 0x04FD, 0x04FD, 0x04FF, 0x04FF, 0x0501, 0x0501, + 0x0503, 0x0503, 0x0505, 0x0505, 0x0507, 0x0507, 0x0509, 0x0509, + 0x050B, 0x050B, 0x050D, 0x050D, 0x050F, 0x050F, 0x0511, 0x0511, + 0x0513, 0x0513, 0x0515, 0x0515, 0x0517, 0x0517, 0x0519, 0x0519, + 0x051B, 0x051B, 0x051D, 0x051D, 0x051F, 0x051F, 0x0521, 0x0521, + 0x0523, 0x0523, 0x0525, 0x0525, 0x0527, 0x0527, 0x0529, 0x0529, + 0x052B, 0x052B, 0x052D, 0x052D, 0x052F, 0x052F, 0x0561, 0x0587, + 0x10D0, 0x10FA, 0x10FD, 0x10FF, 0x13F8, 0x13FD, 0x1C80, 0x1C88, + 0x1C8A, 0x1C8A, 0x1D79, 0x1D79, 0x1D7D, 0x1D7D, 0x1D8E, 0x1D8E, + 0x1E01, 0x1E01, 0x1E03, 0x1E03, 0x1E05, 0x1E05, 0x1E07, 0x1E07, + 0x1E09, 0x1E09, 0x1E0B, 0x1E0B, 0x1E0D, 0x1E0D, 0x1E0F, 0x1E0F, + 0x1E11, 0x1E11, 0x1E13, 0x1E13, 0x1E15, 0x1E15, 0x1E17, 0x1E17, + 0x1E19, 0x1E19, 0x1E1B, 0x1E1B, 0x1E1D, 0x1E1D, 0x1E1F, 0x1E1F, + 0x1E21, 0x1E21, 0x1E23, 0x1E23, 0x1E25, 0x1E25, 0x1E27, 0x1E27, + 0x1E29, 0x1E29, 0x1E2B, 0x1E2B, 0x1E2D, 0x1E2D, 0x1E2F, 0x1E2F, + 0x1E31, 0x1E31, 0x1E33, 0x1E33, 0x1E35, 0x1E35, 0x1E37, 0x1E37, + 0x1E39, 0x1E39, 0x1E3B, 0x1E3B, 0x1E3D, 0x1E3D, 0x1E3F, 0x1E3F, + 0x1E41, 0x1E41, 0x1E43, 0x1E43, 0x1E45, 0x1E45, 0x1E47, 0x1E47, + 0x1E49, 0x1E49, 0x1E4B, 0x1E4B, 0x1E4D, 0x1E4D, 0x1E4F, 0x1E4F, + 0x1E51, 0x1E51, 0x1E53, 0x1E53, 0x1E55, 0x1E55, 0x1E57, 0x1E57, + 0x1E59, 0x1E59, 0x1E5B, 0x1E5B, 0x1E5D, 0x1E5D, 0x1E5F, 0x1E5F, + 0x1E61, 0x1E61, 0x1E63, 0x1E63, 0x1E65, 0x1E65, 0x1E67, 0x1E67, + 0x1E69, 0x1E69, 0x1E6B, 0x1E6B, 0x1E6D, 0x1E6D, 0x1E6F, 0x1E6F, + 0x1E71, 0x1E71, 0x1E73, 0x1E73, 0x1E75, 0x1E75, 0x1E77, 0x1E77, + 0x1E79, 0x1E79, 0x1E7B, 0x1E7B, 0x1E7D, 0x1E7D, 0x1E7F, 0x1E7F, + 0x1E81, 0x1E81, 0x1E83, 0x1E83, 0x1E85, 0x1E85, 0x1E87, 0x1E87, + 0x1E89, 0x1E89, 0x1E8B, 0x1E8B, 0x1E8D, 0x1E8D, 0x1E8F, 0x1E8F, + 0x1E91, 0x1E91, 0x1E93, 0x1E93, 0x1E95, 0x1E9B, 0x1EA1, 0x1EA1, + 0x1EA3, 0x1EA3, 0x1EA5, 0x1EA5, 0x1EA7, 0x1EA7, 0x1EA9, 0x1EA9, + 0x1EAB, 0x1EAB, 0x1EAD, 0x1EAD, 0x1EAF, 0x1EAF, 0x1EB1, 0x1EB1, + 0x1EB3, 0x1EB3, 0x1EB5, 0x1EB5, 0x1EB7, 0x1EB7, 0x1EB9, 0x1EB9, + 0x1EBB, 0x1EBB, 0x1EBD, 0x1EBD, 0x1EBF, 0x1EBF, 0x1EC1, 0x1EC1, + 0x1EC3, 0x1EC3, 0x1EC5, 0x1EC5, 0x1EC7, 0x1EC7, 0x1EC9, 0x1EC9, + 0x1ECB, 0x1ECB, 0x1ECD, 0x1ECD, 0x1ECF, 0x1ECF, 0x1ED1, 0x1ED1, + 0x1ED3, 0x1ED3, 0x1ED5, 0x1ED5, 0x1ED7, 0x1ED7, 0x1ED9, 0x1ED9, + 0x1EDB, 0x1EDB, 0x1EDD, 0x1EDD, 0x1EDF, 0x1EDF, 0x1EE1, 0x1EE1, + 0x1EE3, 0x1EE3, 0x1EE5, 0x1EE5, 0x1EE7, 0x1EE7, 0x1EE9, 0x1EE9, + 0x1EEB, 0x1EEB, 0x1EED, 0x1EED, 0x1EEF, 0x1EEF, 0x1EF1, 0x1EF1, + 0x1EF3, 0x1EF3, 0x1EF5, 0x1EF5, 0x1EF7, 0x1EF7, 0x1EF9, 0x1EF9, + 0x1EFB, 0x1EFB, 0x1EFD, 0x1EFD, 0x1EFF, 0x1F07, 0x1F10, 0x1F15, + 0x1F20, 0x1F27, 0x1F30, 0x1F37, 0x1F40, 0x1F45, 0x1F50, 0x1F57, + 0x1F60, 0x1F67, 0x1F70, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, 0x1FB7, + 0x1FBC, 0x1FBC, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FC7, + 0x1FCC, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FD7, 0x1FE0, 0x1FE7, + 0x1FF2, 0x1FF4, 0x1FF6, 0x1FF7, 0x1FFC, 0x1FFC, 0x214E, 0x214E, + 0x2170, 0x217F, 0x2184, 0x2184, 0x24D0, 0x24E9, 0x2C30, 0x2C5F, + 0x2C61, 0x2C61, 0x2C65, 0x2C66, 0x2C68, 0x2C68, 0x2C6A, 0x2C6A, + 0x2C6C, 0x2C6C, 0x2C73, 0x2C73, 0x2C76, 0x2C76, 0x2C81, 0x2C81, + 0x2C83, 0x2C83, 0x2C85, 0x2C85, 0x2C87, 0x2C87, 0x2C89, 0x2C89, + 0x2C8B, 0x2C8B, 0x2C8D, 0x2C8D, 0x2C8F, 0x2C8F, 0x2C91, 0x2C91, + 0x2C93, 0x2C93, 0x2C95, 0x2C95, 0x2C97, 0x2C97, 0x2C99, 0x2C99, + 0x2C9B, 0x2C9B, 0x2C9D, 0x2C9D, 0x2C9F, 0x2C9F, 0x2CA1, 0x2CA1, + 0x2CA3, 0x2CA3, 0x2CA5, 0x2CA5, 0x2CA7, 0x2CA7, 0x2CA9, 0x2CA9, + 0x2CAB, 0x2CAB, 0x2CAD, 0x2CAD, 0x2CAF, 0x2CAF, 0x2CB1, 0x2CB1, + 0x2CB3, 0x2CB3, 0x2CB5, 0x2CB5, 0x2CB7, 0x2CB7, 0x2CB9, 0x2CB9, + 0x2CBB, 0x2CBB, 0x2CBD, 0x2CBD, 0x2CBF, 0x2CBF, 0x2CC1, 0x2CC1, + 0x2CC3, 0x2CC3, 0x2CC5, 0x2CC5, 0x2CC7, 0x2CC7, 0x2CC9, 0x2CC9, + 0x2CCB, 0x2CCB, 0x2CCD, 0x2CCD, 0x2CCF, 0x2CCF, 0x2CD1, 0x2CD1, + 0x2CD3, 0x2CD3, 0x2CD5, 0x2CD5, 0x2CD7, 0x2CD7, 0x2CD9, 0x2CD9, + 0x2CDB, 0x2CDB, 0x2CDD, 0x2CDD, 0x2CDF, 0x2CDF, 0x2CE1, 0x2CE1, + 0x2CE3, 0x2CE3, 0x2CEC, 0x2CEC, 0x2CEE, 0x2CEE, 0x2CF3, 0x2CF3, + 0x2D00, 0x2D25, 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, 0xA641, 0xA641, + 0xA643, 0xA643, 0xA645, 0xA645, 0xA647, 0xA647, 0xA649, 0xA649, + 0xA64B, 0xA64B, 0xA64D, 0xA64D, 0xA64F, 0xA64F, 0xA651, 0xA651, + 0xA653, 0xA653, 0xA655, 0xA655, 0xA657, 0xA657, 0xA659, 0xA659, + 0xA65B, 0xA65B, 0xA65D, 0xA65D, 0xA65F, 0xA65F, 0xA661, 0xA661, + 0xA663, 0xA663, 0xA665, 0xA665, 0xA667, 0xA667, 0xA669, 0xA669, + 0xA66B, 0xA66B, 0xA66D, 0xA66D, 0xA681, 0xA681, 0xA683, 0xA683, + 0xA685, 0xA685, 0xA687, 0xA687, 0xA689, 0xA689, 0xA68B, 0xA68B, + 0xA68D, 0xA68D, 0xA68F, 0xA68F, 0xA691, 0xA691, 0xA693, 0xA693, + 0xA695, 0xA695, 0xA697, 0xA697, 0xA699, 0xA699, 0xA69B, 0xA69B, + 0xA723, 0xA723, 0xA725, 0xA725, 0xA727, 0xA727, 0xA729, 0xA729, + 0xA72B, 0xA72B, 0xA72D, 0xA72D, 0xA72F, 0xA72F, 0xA733, 0xA733, + 0xA735, 0xA735, 0xA737, 0xA737, 0xA739, 0xA739, 0xA73B, 0xA73B, + 0xA73D, 0xA73D, 0xA73F, 0xA73F, 0xA741, 0xA741, 0xA743, 0xA743, + 0xA745, 0xA745, 0xA747, 0xA747, 0xA749, 0xA749, 0xA74B, 0xA74B, + 0xA74D, 0xA74D, 0xA74F, 0xA74F, 0xA751, 0xA751, 0xA753, 0xA753, + 0xA755, 0xA755, 0xA757, 0xA757, 0xA759, 0xA759, 0xA75B, 0xA75B, + 0xA75D, 0xA75D, 0xA75F, 0xA75F, 0xA761, 0xA761, 0xA763, 0xA763, + 0xA765, 0xA765, 0xA767, 0xA767, 0xA769, 0xA769, 0xA76B, 0xA76B, + 0xA76D, 0xA76D, 0xA76F, 0xA76F, 0xA77A, 0xA77A, 0xA77C, 0xA77C, + 0xA77F, 0xA77F, 0xA781, 0xA781, 0xA783, 0xA783, 0xA785, 0xA785, + 0xA787, 0xA787, 0xA78C, 0xA78C, 0xA791, 0xA791, 0xA793, 0xA794, + 0xA797, 0xA797, 0xA799, 0xA799, 0xA79B, 0xA79B, 0xA79D, 0xA79D, + 0xA79F, 0xA79F, 0xA7A1, 0xA7A1, 0xA7A3, 0xA7A3, 0xA7A5, 0xA7A5, + 0xA7A7, 0xA7A7, 0xA7A9, 0xA7A9, 0xA7B5, 0xA7B5, 0xA7B7, 0xA7B7, + 0xA7B9, 0xA7B9, 0xA7BB, 0xA7BB, 0xA7BD, 0xA7BD, 0xA7BF, 0xA7BF, + 0xA7C1, 0xA7C1, 0xA7C3, 0xA7C3, 0xA7C8, 0xA7C8, 0xA7CA, 0xA7CA, + 0xA7CD, 0xA7CD, 0xA7CF, 0xA7CF, 0xA7D1, 0xA7D1, 0xA7D3, 0xA7D3, + 0xA7D5, 0xA7D5, 0xA7D7, 0xA7D7, 0xA7D9, 0xA7D9, 0xA7DB, 0xA7DB, + 0xA7F6, 0xA7F6, 0xAB53, 0xAB53, 0xAB70, 0xABBF, 0xFB00, 0xFB06, + 0xFB13, 0xFB17, 0xFF41, 0xFF5A, 0x10428, 0x1044F, 0x104D8, 0x104FB, + 0x10597, 0x105A1, 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, + 0x10CC0, 0x10CF2, 0x10D70, 0x10D85, 0x118C0, 0x118DF, 0x16E60, 0x16E7F, + 0x16EBB, 0x16ED3, 0x1E922, 0x1E943, + // #58 (9144+24): bp=Dash + 0x002D, 0x002D, 0x058A, 0x058A, 0x05BE, 0x05BE, 0x1400, 0x1400, + 0x1806, 0x1806, 0x2010, 0x2015, 0x2053, 0x2053, 0x207B, 0x207B, + 0x208B, 0x208B, 0x2212, 0x2212, 0x2E17, 0x2E17, 0x2E1A, 0x2E1A, + 0x2E3A, 0x2E3B, 0x2E40, 0x2E40, 0x2E5D, 0x2E5D, 0x301C, 0x301C, + 0x3030, 0x3030, 0x30A0, 0x30A0, 0xFE31, 0xFE32, 0xFE58, 0xFE58, + 0xFE63, 0xFE63, 0xFF0D, 0xFF0D, 0x10D6E, 0x10D6E, 0x10EAD, 0x10EAD, + // #59 (9168+17): bp=Default_Ignorable_Code_Point:DI + 0x00AD, 0x00AD, 0x034F, 0x034F, 0x061C, 0x061C, 0x115F, 0x1160, + 0x17B4, 0x17B5, 0x180B, 0x180F, 0x200B, 0x200F, 0x202A, 0x202E, + 0x2060, 0x206F, 0x3164, 0x3164, 0xFE00, 0xFE0F, 0xFEFF, 0xFEFF, + 0xFFA0, 0xFFA0, 0xFFF0, 0xFFF8, 0x1BCA0, 0x1BCA3, 0x1D173, 0x1D17A, + 0xE0000, 0xE0FFF, + // #60 (9185+8): bp=Deprecated:Dep + 0x0149, 0x0149, 0x0673, 0x0673, 0x0F77, 0x0F77, 0x0F79, 0x0F79, + 0x17A3, 0x17A4, 0x206A, 0x206F, 0x2329, 0x232A, 0xE0001, 0xE0001, + // #61 (9193+220): bp=Diacritic:Dia + 0x005E, 0x005E, 0x0060, 0x0060, 0x00A8, 0x00A8, 0x00AF, 0x00AF, + 0x00B4, 0x00B4, 0x00B7, 0x00B8, 0x02B0, 0x034E, 0x0350, 0x0357, + 0x035D, 0x0362, 0x0374, 0x0375, 0x037A, 0x037A, 0x0384, 0x0385, + 0x0483, 0x0487, 0x0559, 0x0559, 0x0591, 0x05BD, 0x05BF, 0x05BF, + 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x064B, 0x0652, + 0x0657, 0x0658, 0x06DF, 0x06E0, 0x06E5, 0x06E6, 0x06EA, 0x06EC, + 0x0730, 0x074A, 0x07A6, 0x07B0, 0x07EB, 0x07F5, 0x0818, 0x0819, + 0x0898, 0x089F, 0x08C9, 0x08D2, 0x08E3, 0x08FE, 0x093C, 0x093C, + 0x094D, 0x094D, 0x0951, 0x0954, 0x0971, 0x0971, 0x09BC, 0x09BC, + 0x09CD, 0x09CD, 0x0A3C, 0x0A3C, 0x0A4D, 0x0A4D, 0x0ABC, 0x0ABC, + 0x0ACD, 0x0ACD, 0x0AFD, 0x0AFF, 0x0B3C, 0x0B3C, 0x0B4D, 0x0B4D, + 0x0B55, 0x0B55, 0x0BCD, 0x0BCD, 0x0C3C, 0x0C3C, 0x0C4D, 0x0C4D, + 0x0CBC, 0x0CBC, 0x0CCD, 0x0CCD, 0x0D3B, 0x0D3C, 0x0D4D, 0x0D4D, + 0x0DCA, 0x0DCA, 0x0E3A, 0x0E3A, 0x0E47, 0x0E4C, 0x0E4E, 0x0E4E, + 0x0EBA, 0x0EBA, 0x0EC8, 0x0ECC, 0x0F18, 0x0F19, 0x0F35, 0x0F35, + 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F3E, 0x0F3F, 0x0F82, 0x0F84, + 0x0F86, 0x0F87, 0x0FC6, 0x0FC6, 0x1037, 0x1037, 0x1039, 0x103A, + 0x1063, 0x1064, 0x1069, 0x106D, 0x1087, 0x108D, 0x108F, 0x108F, + 0x109A, 0x109B, 0x135D, 0x135F, 0x1714, 0x1715, 0x1734, 0x1734, + 0x17C9, 0x17D3, 0x17DD, 0x17DD, 0x1939, 0x193B, 0x1A60, 0x1A60, + 0x1A75, 0x1A7C, 0x1A7F, 0x1A7F, 0x1AB0, 0x1ABE, 0x1AC1, 0x1ACB, + 0x1ACF, 0x1ADD, 0x1AE0, 0x1AEB, 0x1B34, 0x1B34, 0x1B44, 0x1B44, + 0x1B6B, 0x1B73, 0x1BAA, 0x1BAB, 0x1BE6, 0x1BE6, 0x1BF2, 0x1BF3, + 0x1C36, 0x1C37, 0x1C78, 0x1C7D, 0x1CD0, 0x1CE8, 0x1CED, 0x1CED, + 0x1CF4, 0x1CF4, 0x1CF7, 0x1CF9, 0x1D2C, 0x1D6A, 0x1D9B, 0x1DBE, + 0x1DC4, 0x1DCF, 0x1DF5, 0x1DFF, 0x1FBD, 0x1FBD, 0x1FBF, 0x1FC1, + 0x1FCD, 0x1FCF, 0x1FDD, 0x1FDF, 0x1FED, 0x1FEF, 0x1FFD, 0x1FFE, + 0x2CEF, 0x2CF1, 0x2E2F, 0x2E2F, 0x302A, 0x302F, 0x3099, 0x309C, + 0x30FC, 0x30FC, 0xA66F, 0xA66F, 0xA67C, 0xA67D, 0xA67F, 0xA67F, + 0xA69C, 0xA69D, 0xA6F0, 0xA6F1, 0xA700, 0xA721, 0xA788, 0xA78A, + 0xA7F1, 0xA7F1, 0xA7F8, 0xA7F9, 0xA806, 0xA806, 0xA82C, 0xA82C, + 0xA8C4, 0xA8C4, 0xA8E0, 0xA8F1, 0xA92B, 0xA92E, 0xA953, 0xA953, + 0xA9B3, 0xA9B3, 0xA9C0, 0xA9C0, 0xA9E5, 0xA9E5, 0xAA7B, 0xAA7D, + 0xAABF, 0xAAC2, 0xAAF6, 0xAAF6, 0xAB5B, 0xAB5F, 0xAB69, 0xAB6B, + 0xABEC, 0xABED, 0xFB1E, 0xFB1E, 0xFE20, 0xFE2F, 0xFF3E, 0xFF3E, + 0xFF40, 0xFF40, 0xFF70, 0xFF70, 0xFF9E, 0xFF9F, 0xFFE3, 0xFFE3, + 0x102E0, 0x102E0, 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, + 0x10A38, 0x10A3A, 0x10A3F, 0x10A3F, 0x10AE5, 0x10AE6, 0x10D22, 0x10D27, + 0x10D4E, 0x10D4E, 0x10D69, 0x10D6D, 0x10EFA, 0x10EFA, 0x10EFD, 0x10EFF, + 0x10F46, 0x10F50, 0x10F82, 0x10F85, 0x11046, 0x11046, 0x11070, 0x11070, + 0x110B9, 0x110BA, 0x11133, 0x11134, 0x11173, 0x11173, 0x111C0, 0x111C0, + 0x111CA, 0x111CC, 0x11235, 0x11236, 0x112E9, 0x112EA, 0x1133B, 0x1133C, + 0x1134D, 0x1134D, 0x11366, 0x1136C, 0x11370, 0x11374, 0x113CE, 0x113D0, + 0x113D2, 0x113D3, 0x113E1, 0x113E2, 0x11442, 0x11442, 0x11446, 0x11446, + 0x114C2, 0x114C3, 0x115BF, 0x115C0, 0x1163F, 0x1163F, 0x116B6, 0x116B7, + 0x1172B, 0x1172B, 0x11839, 0x1183A, 0x1193D, 0x1193E, 0x11943, 0x11943, + 0x119E0, 0x119E0, 0x11A34, 0x11A34, 0x11A47, 0x11A47, 0x11A99, 0x11A99, + 0x11C3F, 0x11C3F, 0x11D42, 0x11D42, 0x11D44, 0x11D45, 0x11D97, 0x11D97, + 0x11DD9, 0x11DD9, 0x11F41, 0x11F42, 0x11F5A, 0x11F5A, 0x13447, 0x13455, + 0x1612F, 0x1612F, 0x16AF0, 0x16AF4, 0x16B30, 0x16B36, 0x16D6B, 0x16D6C, + 0x16F8F, 0x16F9F, 0x16FF0, 0x16FF1, 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, + 0x1AFFD, 0x1AFFE, 0x1CF00, 0x1CF2D, 0x1CF30, 0x1CF46, 0x1D167, 0x1D169, + 0x1D16D, 0x1D172, 0x1D17B, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, + 0x1E030, 0x1E06D, 0x1E130, 0x1E136, 0x1E2AE, 0x1E2AE, 0x1E2EC, 0x1E2EF, + 0x1E5EE, 0x1E5EF, 0x1E8D0, 0x1E8D6, 0x1E944, 0x1E946, 0x1E948, 0x1E94A, + // #62 (9413+151): bp=Emoji + 0x0023, 0x0023, 0x002A, 0x002A, 0x0030, 0x0039, 0x00A9, 0x00A9, + 0x00AE, 0x00AE, 0x203C, 0x203C, 0x2049, 0x2049, 0x2122, 0x2122, + 0x2139, 0x2139, 0x2194, 0x2199, 0x21A9, 0x21AA, 0x231A, 0x231B, + 0x2328, 0x2328, 0x23CF, 0x23CF, 0x23E9, 0x23F3, 0x23F8, 0x23FA, + 0x24C2, 0x24C2, 0x25AA, 0x25AB, 0x25B6, 0x25B6, 0x25C0, 0x25C0, + 0x25FB, 0x25FE, 0x2600, 0x2604, 0x260E, 0x260E, 0x2611, 0x2611, + 0x2614, 0x2615, 0x2618, 0x2618, 0x261D, 0x261D, 0x2620, 0x2620, + 0x2622, 0x2623, 0x2626, 0x2626, 0x262A, 0x262A, 0x262E, 0x262F, + 0x2638, 0x263A, 0x2640, 0x2640, 0x2642, 0x2642, 0x2648, 0x2653, + 0x265F, 0x2660, 0x2663, 0x2663, 0x2665, 0x2666, 0x2668, 0x2668, + 0x267B, 0x267B, 0x267E, 0x267F, 0x2692, 0x2697, 0x2699, 0x2699, + 0x269B, 0x269C, 0x26A0, 0x26A1, 0x26A7, 0x26A7, 0x26AA, 0x26AB, + 0x26B0, 0x26B1, 0x26BD, 0x26BE, 0x26C4, 0x26C5, 0x26C8, 0x26C8, + 0x26CE, 0x26CF, 0x26D1, 0x26D1, 0x26D3, 0x26D4, 0x26E9, 0x26EA, + 0x26F0, 0x26F5, 0x26F7, 0x26FA, 0x26FD, 0x26FD, 0x2702, 0x2702, + 0x2705, 0x2705, 0x2708, 0x270D, 0x270F, 0x270F, 0x2712, 0x2712, + 0x2714, 0x2714, 0x2716, 0x2716, 0x271D, 0x271D, 0x2721, 0x2721, + 0x2728, 0x2728, 0x2733, 0x2734, 0x2744, 0x2744, 0x2747, 0x2747, + 0x274C, 0x274C, 0x274E, 0x274E, 0x2753, 0x2755, 0x2757, 0x2757, + 0x2763, 0x2764, 0x2795, 0x2797, 0x27A1, 0x27A1, 0x27B0, 0x27B0, + 0x27BF, 0x27BF, 0x2934, 0x2935, 0x2B05, 0x2B07, 0x2B1B, 0x2B1C, + 0x2B50, 0x2B50, 0x2B55, 0x2B55, 0x3030, 0x3030, 0x303D, 0x303D, + 0x3297, 0x3297, 0x3299, 0x3299, 0x1F004, 0x1F004, 0x1F0CF, 0x1F0CF, + 0x1F170, 0x1F171, 0x1F17E, 0x1F17F, 0x1F18E, 0x1F18E, 0x1F191, 0x1F19A, + 0x1F1E6, 0x1F1FF, 0x1F201, 0x1F202, 0x1F21A, 0x1F21A, 0x1F22F, 0x1F22F, + 0x1F232, 0x1F23A, 0x1F250, 0x1F251, 0x1F300, 0x1F321, 0x1F324, 0x1F393, + 0x1F396, 0x1F397, 0x1F399, 0x1F39B, 0x1F39E, 0x1F3F0, 0x1F3F3, 0x1F3F5, + 0x1F3F7, 0x1F4FD, 0x1F4FF, 0x1F53D, 0x1F549, 0x1F54E, 0x1F550, 0x1F567, + 0x1F56F, 0x1F570, 0x1F573, 0x1F57A, 0x1F587, 0x1F587, 0x1F58A, 0x1F58D, + 0x1F590, 0x1F590, 0x1F595, 0x1F596, 0x1F5A4, 0x1F5A5, 0x1F5A8, 0x1F5A8, + 0x1F5B1, 0x1F5B2, 0x1F5BC, 0x1F5BC, 0x1F5C2, 0x1F5C4, 0x1F5D1, 0x1F5D3, + 0x1F5DC, 0x1F5DE, 0x1F5E1, 0x1F5E1, 0x1F5E3, 0x1F5E3, 0x1F5E8, 0x1F5E8, + 0x1F5EF, 0x1F5EF, 0x1F5F3, 0x1F5F3, 0x1F5FA, 0x1F64F, 0x1F680, 0x1F6C5, + 0x1F6CB, 0x1F6D2, 0x1F6D5, 0x1F6D8, 0x1F6DC, 0x1F6E5, 0x1F6E9, 0x1F6E9, + 0x1F6EB, 0x1F6EC, 0x1F6F0, 0x1F6F0, 0x1F6F3, 0x1F6FC, 0x1F7E0, 0x1F7EB, + 0x1F7F0, 0x1F7F0, 0x1F90C, 0x1F93A, 0x1F93C, 0x1F945, 0x1F947, 0x1F9FF, + 0x1FA70, 0x1FA7C, 0x1FA80, 0x1FA8A, 0x1FA8E, 0x1FAC6, 0x1FAC8, 0x1FAC8, + 0x1FACD, 0x1FADC, 0x1FADF, 0x1FAEA, 0x1FAEF, 0x1FAF8, + // #63 (9564+10): bp=Emoji_Component:EComp + 0x0023, 0x0023, 0x002A, 0x002A, 0x0030, 0x0039, 0x200D, 0x200D, + 0x20E3, 0x20E3, 0xFE0F, 0xFE0F, 0x1F1E6, 0x1F1FF, 0x1F3FB, 0x1F3FF, + 0x1F9B0, 0x1F9B3, 0xE0020, 0xE007F, + // #64 (9574+1): bp=Emoji_Modifier:EMod + 0x1F3FB, 0x1F3FF, + // #65 (9575+40): bp=Emoji_Modifier_Base:EBase + 0x261D, 0x261D, 0x26F9, 0x26F9, 0x270A, 0x270D, 0x1F385, 0x1F385, + 0x1F3C2, 0x1F3C4, 0x1F3C7, 0x1F3C7, 0x1F3CA, 0x1F3CC, 0x1F442, 0x1F443, + 0x1F446, 0x1F450, 0x1F466, 0x1F478, 0x1F47C, 0x1F47C, 0x1F481, 0x1F483, + 0x1F485, 0x1F487, 0x1F48F, 0x1F48F, 0x1F491, 0x1F491, 0x1F4AA, 0x1F4AA, + 0x1F574, 0x1F575, 0x1F57A, 0x1F57A, 0x1F590, 0x1F590, 0x1F595, 0x1F596, + 0x1F645, 0x1F647, 0x1F64B, 0x1F64F, 0x1F6A3, 0x1F6A3, 0x1F6B4, 0x1F6B6, + 0x1F6C0, 0x1F6C0, 0x1F6CC, 0x1F6CC, 0x1F90C, 0x1F90C, 0x1F90F, 0x1F90F, + 0x1F918, 0x1F91F, 0x1F926, 0x1F926, 0x1F930, 0x1F939, 0x1F93C, 0x1F93E, + 0x1F977, 0x1F977, 0x1F9B5, 0x1F9B6, 0x1F9B8, 0x1F9B9, 0x1F9BB, 0x1F9BB, + 0x1F9CD, 0x1F9CF, 0x1F9D1, 0x1F9DD, 0x1FAC3, 0x1FAC5, 0x1FAF0, 0x1FAF8, + // #66 (9615+81): bp=Emoji_Presentation:EPres + 0x231A, 0x231B, 0x23E9, 0x23EC, 0x23F0, 0x23F0, 0x23F3, 0x23F3, + 0x25FD, 0x25FE, 0x2614, 0x2615, 0x2648, 0x2653, 0x267F, 0x267F, + 0x2693, 0x2693, 0x26A1, 0x26A1, 0x26AA, 0x26AB, 0x26BD, 0x26BE, + 0x26C4, 0x26C5, 0x26CE, 0x26CE, 0x26D4, 0x26D4, 0x26EA, 0x26EA, + 0x26F2, 0x26F3, 0x26F5, 0x26F5, 0x26FA, 0x26FA, 0x26FD, 0x26FD, + 0x2705, 0x2705, 0x270A, 0x270B, 0x2728, 0x2728, 0x274C, 0x274C, + 0x274E, 0x274E, 0x2753, 0x2755, 0x2757, 0x2757, 0x2795, 0x2797, + 0x27B0, 0x27B0, 0x27BF, 0x27BF, 0x2B1B, 0x2B1C, 0x2B50, 0x2B50, + 0x2B55, 0x2B55, 0x1F004, 0x1F004, 0x1F0CF, 0x1F0CF, 0x1F18E, 0x1F18E, + 0x1F191, 0x1F19A, 0x1F1E6, 0x1F1FF, 0x1F201, 0x1F201, 0x1F21A, 0x1F21A, + 0x1F22F, 0x1F22F, 0x1F232, 0x1F236, 0x1F238, 0x1F23A, 0x1F250, 0x1F251, + 0x1F300, 0x1F320, 0x1F32D, 0x1F335, 0x1F337, 0x1F37C, 0x1F37E, 0x1F393, + 0x1F3A0, 0x1F3CA, 0x1F3CF, 0x1F3D3, 0x1F3E0, 0x1F3F0, 0x1F3F4, 0x1F3F4, + 0x1F3F8, 0x1F43E, 0x1F440, 0x1F440, 0x1F442, 0x1F4FC, 0x1F4FF, 0x1F53D, + 0x1F54B, 0x1F54E, 0x1F550, 0x1F567, 0x1F57A, 0x1F57A, 0x1F595, 0x1F596, + 0x1F5A4, 0x1F5A4, 0x1F5FB, 0x1F64F, 0x1F680, 0x1F6C5, 0x1F6CC, 0x1F6CC, + 0x1F6D0, 0x1F6D2, 0x1F6D5, 0x1F6D8, 0x1F6DC, 0x1F6DF, 0x1F6EB, 0x1F6EC, + 0x1F6F4, 0x1F6FC, 0x1F7E0, 0x1F7EB, 0x1F7F0, 0x1F7F0, 0x1F90C, 0x1F93A, + 0x1F93C, 0x1F945, 0x1F947, 0x1F9FF, 0x1FA70, 0x1FA7C, 0x1FA80, 0x1FA8A, + 0x1FA8E, 0x1FAC6, 0x1FAC8, 0x1FAC8, 0x1FACD, 0x1FADC, 0x1FADF, 0x1FAEA, + 0x1FAEF, 0x1FAF8, + // #67 (9696+156): bp=Extended_Pictographic:ExtPict + 0x00A9, 0x00A9, 0x00AE, 0x00AE, 0x203C, 0x203C, 0x2049, 0x2049, + 0x2122, 0x2122, 0x2139, 0x2139, 0x2194, 0x2199, 0x21A9, 0x21AA, + 0x231A, 0x231B, 0x2328, 0x2328, 0x23CF, 0x23CF, 0x23E9, 0x23F3, + 0x23F8, 0x23FA, 0x24C2, 0x24C2, 0x25AA, 0x25AB, 0x25B6, 0x25B6, + 0x25C0, 0x25C0, 0x25FB, 0x25FE, 0x2600, 0x2604, 0x260E, 0x260E, + 0x2611, 0x2611, 0x2614, 0x2615, 0x2618, 0x2618, 0x261D, 0x261D, + 0x2620, 0x2620, 0x2622, 0x2623, 0x2626, 0x2626, 0x262A, 0x262A, + 0x262E, 0x262F, 0x2638, 0x263A, 0x2640, 0x2640, 0x2642, 0x2642, + 0x2648, 0x2653, 0x265F, 0x2660, 0x2663, 0x2663, 0x2665, 0x2666, + 0x2668, 0x2668, 0x267B, 0x267B, 0x267E, 0x267F, 0x2692, 0x2697, + 0x2699, 0x2699, 0x269B, 0x269C, 0x26A0, 0x26A1, 0x26A7, 0x26A7, + 0x26AA, 0x26AB, 0x26B0, 0x26B1, 0x26BD, 0x26BE, 0x26C4, 0x26C5, + 0x26C8, 0x26C8, 0x26CE, 0x26CF, 0x26D1, 0x26D1, 0x26D3, 0x26D4, + 0x26E9, 0x26EA, 0x26F0, 0x26F5, 0x26F7, 0x26FA, 0x26FD, 0x26FD, + 0x2702, 0x2702, 0x2705, 0x2705, 0x2708, 0x270D, 0x270F, 0x270F, + 0x2712, 0x2712, 0x2714, 0x2714, 0x2716, 0x2716, 0x271D, 0x271D, + 0x2721, 0x2721, 0x2728, 0x2728, 0x2733, 0x2734, 0x2744, 0x2744, + 0x2747, 0x2747, 0x274C, 0x274C, 0x274E, 0x274E, 0x2753, 0x2755, + 0x2757, 0x2757, 0x2763, 0x2764, 0x2795, 0x2797, 0x27A1, 0x27A1, + 0x27B0, 0x27B0, 0x27BF, 0x27BF, 0x2934, 0x2935, 0x2B05, 0x2B07, + 0x2B1B, 0x2B1C, 0x2B50, 0x2B50, 0x2B55, 0x2B55, 0x3030, 0x3030, + 0x303D, 0x303D, 0x3297, 0x3297, 0x3299, 0x3299, 0x1F004, 0x1F004, + 0x1F02C, 0x1F02F, 0x1F094, 0x1F09F, 0x1F0AF, 0x1F0B0, 0x1F0C0, 0x1F0C0, + 0x1F0CF, 0x1F0D0, 0x1F0F6, 0x1F0FF, 0x1F170, 0x1F171, 0x1F17E, 0x1F17F, + 0x1F18E, 0x1F18E, 0x1F191, 0x1F19A, 0x1F1AE, 0x1F1E5, 0x1F201, 0x1F20F, + 0x1F21A, 0x1F21A, 0x1F22F, 0x1F22F, 0x1F232, 0x1F23A, 0x1F23C, 0x1F23F, + 0x1F249, 0x1F25F, 0x1F266, 0x1F321, 0x1F324, 0x1F393, 0x1F396, 0x1F397, + 0x1F399, 0x1F39B, 0x1F39E, 0x1F3F0, 0x1F3F3, 0x1F3F5, 0x1F3F7, 0x1F3FA, + 0x1F400, 0x1F4FD, 0x1F4FF, 0x1F53D, 0x1F549, 0x1F54E, 0x1F550, 0x1F567, + 0x1F56F, 0x1F570, 0x1F573, 0x1F57A, 0x1F587, 0x1F587, 0x1F58A, 0x1F58D, + 0x1F590, 0x1F590, 0x1F595, 0x1F596, 0x1F5A4, 0x1F5A5, 0x1F5A8, 0x1F5A8, + 0x1F5B1, 0x1F5B2, 0x1F5BC, 0x1F5BC, 0x1F5C2, 0x1F5C4, 0x1F5D1, 0x1F5D3, + 0x1F5DC, 0x1F5DE, 0x1F5E1, 0x1F5E1, 0x1F5E3, 0x1F5E3, 0x1F5E8, 0x1F5E8, + 0x1F5EF, 0x1F5EF, 0x1F5F3, 0x1F5F3, 0x1F5FA, 0x1F64F, 0x1F680, 0x1F6C5, + 0x1F6CB, 0x1F6D2, 0x1F6D5, 0x1F6E5, 0x1F6E9, 0x1F6E9, 0x1F6EB, 0x1F6F0, + 0x1F6F3, 0x1F6FF, 0x1F7DA, 0x1F7FF, 0x1F80C, 0x1F80F, 0x1F848, 0x1F84F, + 0x1F85A, 0x1F85F, 0x1F888, 0x1F88F, 0x1F8AE, 0x1F8AF, 0x1F8BC, 0x1F8BF, + 0x1F8C2, 0x1F8CF, 0x1F8D9, 0x1F8FF, 0x1F90C, 0x1F93A, 0x1F93C, 0x1F945, + 0x1F947, 0x1F9FF, 0x1FA58, 0x1FA5F, 0x1FA6E, 0x1FAFF, 0x1FC00, 0x1FFFD, + // #68 (9852+43): bp=Extender:Ext + 0x00B7, 0x00B7, 0x02D0, 0x02D1, 0x0640, 0x0640, 0x07FA, 0x07FA, + 0x0A71, 0x0A71, 0x0AFB, 0x0AFB, 0x0B55, 0x0B55, 0x0E46, 0x0E46, + 0x0EC6, 0x0EC6, 0x180A, 0x180A, 0x1843, 0x1843, 0x1AA7, 0x1AA7, + 0x1C36, 0x1C36, 0x1C7B, 0x1C7B, 0x3005, 0x3005, 0x3031, 0x3035, + 0x309D, 0x309E, 0x30FC, 0x30FE, 0xA015, 0xA015, 0xA60C, 0xA60C, + 0xA9CF, 0xA9CF, 0xA9E6, 0xA9E6, 0xAA70, 0xAA70, 0xAADD, 0xAADD, + 0xAAF3, 0xAAF4, 0xFF70, 0xFF70, 0x10781, 0x10782, 0x10D4E, 0x10D4E, + 0x10D6A, 0x10D6A, 0x10D6F, 0x10D6F, 0x11237, 0x11237, 0x1135D, 0x1135D, + 0x113D2, 0x113D3, 0x115C6, 0x115C8, 0x11A98, 0x11A98, 0x11DD9, 0x11DD9, + 0x16B42, 0x16B43, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE3, 0x16FF2, 0x16FF3, + 0x1E13C, 0x1E13D, 0x1E5EF, 0x1E5EF, 0x1E944, 0x1E946, + // #69 (9895+904): bp=Grapheme_Base:Gr_Base + 0x0020, 0x007E, 0x00A0, 0x00AC, 0x00AE, 0x02FF, 0x0370, 0x0377, + 0x037A, 0x037F, 0x0384, 0x038A, 0x038C, 0x038C, 0x038E, 0x03A1, + 0x03A3, 0x0482, 0x048A, 0x052F, 0x0531, 0x0556, 0x0559, 0x058A, + 0x058D, 0x058F, 0x05BE, 0x05BE, 0x05C0, 0x05C0, 0x05C3, 0x05C3, + 0x05C6, 0x05C6, 0x05D0, 0x05EA, 0x05EF, 0x05F4, 0x0606, 0x060F, + 0x061B, 0x061B, 0x061D, 0x064A, 0x0660, 0x066F, 0x0671, 0x06D5, + 0x06DE, 0x06DE, 0x06E5, 0x06E6, 0x06E9, 0x06E9, 0x06EE, 0x070D, + 0x0710, 0x0710, 0x0712, 0x072F, 0x074D, 0x07A5, 0x07B1, 0x07B1, + 0x07C0, 0x07EA, 0x07F4, 0x07FA, 0x07FE, 0x0815, 0x081A, 0x081A, + 0x0824, 0x0824, 0x0828, 0x0828, 0x0830, 0x083E, 0x0840, 0x0858, + 0x085E, 0x085E, 0x0860, 0x086A, 0x0870, 0x088F, 0x08A0, 0x08C9, + 0x0903, 0x0939, 0x093B, 0x093B, 0x093D, 0x0940, 0x0949, 0x094C, + 0x094E, 0x0950, 0x0958, 0x0961, 0x0964, 0x0980, 0x0982, 0x0983, + 0x0985, 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, 0x09AA, 0x09B0, + 0x09B2, 0x09B2, 0x09B6, 0x09B9, 0x09BD, 0x09BD, 0x09BF, 0x09C0, + 0x09C7, 0x09C8, 0x09CB, 0x09CC, 0x09CE, 0x09CE, 0x09DC, 0x09DD, + 0x09DF, 0x09E1, 0x09E6, 0x09FD, 0x0A03, 0x0A03, 0x0A05, 0x0A0A, + 0x0A0F, 0x0A10, 0x0A13, 0x0A28, 0x0A2A, 0x0A30, 0x0A32, 0x0A33, + 0x0A35, 0x0A36, 0x0A38, 0x0A39, 0x0A3E, 0x0A40, 0x0A59, 0x0A5C, + 0x0A5E, 0x0A5E, 0x0A66, 0x0A6F, 0x0A72, 0x0A74, 0x0A76, 0x0A76, + 0x0A83, 0x0A83, 0x0A85, 0x0A8D, 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, + 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, 0x0AB5, 0x0AB9, 0x0ABD, 0x0AC0, + 0x0AC9, 0x0AC9, 0x0ACB, 0x0ACC, 0x0AD0, 0x0AD0, 0x0AE0, 0x0AE1, + 0x0AE6, 0x0AF1, 0x0AF9, 0x0AF9, 0x0B02, 0x0B03, 0x0B05, 0x0B0C, + 0x0B0F, 0x0B10, 0x0B13, 0x0B28, 0x0B2A, 0x0B30, 0x0B32, 0x0B33, + 0x0B35, 0x0B39, 0x0B3D, 0x0B3D, 0x0B40, 0x0B40, 0x0B47, 0x0B48, + 0x0B4B, 0x0B4C, 0x0B5C, 0x0B5D, 0x0B5F, 0x0B61, 0x0B66, 0x0B77, + 0x0B83, 0x0B83, 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, 0x0B92, 0x0B95, + 0x0B99, 0x0B9A, 0x0B9C, 0x0B9C, 0x0B9E, 0x0B9F, 0x0BA3, 0x0BA4, + 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB9, 0x0BBF, 0x0BBF, 0x0BC1, 0x0BC2, + 0x0BC6, 0x0BC8, 0x0BCA, 0x0BCC, 0x0BD0, 0x0BD0, 0x0BE6, 0x0BFA, + 0x0C01, 0x0C03, 0x0C05, 0x0C0C, 0x0C0E, 0x0C10, 0x0C12, 0x0C28, + 0x0C2A, 0x0C39, 0x0C3D, 0x0C3D, 0x0C41, 0x0C44, 0x0C58, 0x0C5A, + 0x0C5C, 0x0C5D, 0x0C60, 0x0C61, 0x0C66, 0x0C6F, 0x0C77, 0x0C80, + 0x0C82, 0x0C8C, 0x0C8E, 0x0C90, 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, + 0x0CB5, 0x0CB9, 0x0CBD, 0x0CBE, 0x0CC1, 0x0CC1, 0x0CC3, 0x0CC4, + 0x0CDC, 0x0CDE, 0x0CE0, 0x0CE1, 0x0CE6, 0x0CEF, 0x0CF1, 0x0CF3, + 0x0D02, 0x0D0C, 0x0D0E, 0x0D10, 0x0D12, 0x0D3A, 0x0D3D, 0x0D3D, + 0x0D3F, 0x0D40, 0x0D46, 0x0D48, 0x0D4A, 0x0D4C, 0x0D4E, 0x0D4F, + 0x0D54, 0x0D56, 0x0D58, 0x0D61, 0x0D66, 0x0D7F, 0x0D82, 0x0D83, + 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, 0x0DB3, 0x0DBB, 0x0DBD, 0x0DBD, + 0x0DC0, 0x0DC6, 0x0DD0, 0x0DD1, 0x0DD8, 0x0DDE, 0x0DE6, 0x0DEF, + 0x0DF2, 0x0DF4, 0x0E01, 0x0E30, 0x0E32, 0x0E33, 0x0E3F, 0x0E46, + 0x0E4F, 0x0E5B, 0x0E81, 0x0E82, 0x0E84, 0x0E84, 0x0E86, 0x0E8A, + 0x0E8C, 0x0EA3, 0x0EA5, 0x0EA5, 0x0EA7, 0x0EB0, 0x0EB2, 0x0EB3, + 0x0EBD, 0x0EBD, 0x0EC0, 0x0EC4, 0x0EC6, 0x0EC6, 0x0ED0, 0x0ED9, + 0x0EDC, 0x0EDF, 0x0F00, 0x0F17, 0x0F1A, 0x0F34, 0x0F36, 0x0F36, + 0x0F38, 0x0F38, 0x0F3A, 0x0F47, 0x0F49, 0x0F6C, 0x0F7F, 0x0F7F, + 0x0F85, 0x0F85, 0x0F88, 0x0F8C, 0x0FBE, 0x0FC5, 0x0FC7, 0x0FCC, + 0x0FCE, 0x0FDA, 0x1000, 0x102C, 0x1031, 0x1031, 0x1038, 0x1038, + 0x103B, 0x103C, 0x103F, 0x1057, 0x105A, 0x105D, 0x1061, 0x1070, + 0x1075, 0x1081, 0x1083, 0x1084, 0x1087, 0x108C, 0x108E, 0x109C, + 0x109E, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x1248, + 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, 0x125A, 0x125D, + 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, 0x12B2, 0x12B5, + 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, 0x12C8, 0x12D6, + 0x12D8, 0x1310, 0x1312, 0x1315, 0x1318, 0x135A, 0x1360, 0x137C, + 0x1380, 0x1399, 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0x1400, 0x169C, + 0x16A0, 0x16F8, 0x1700, 0x1711, 0x171F, 0x1731, 0x1735, 0x1736, + 0x1740, 0x1751, 0x1760, 0x176C, 0x176E, 0x1770, 0x1780, 0x17B3, + 0x17B6, 0x17B6, 0x17BE, 0x17C5, 0x17C7, 0x17C8, 0x17D4, 0x17DC, + 0x17E0, 0x17E9, 0x17F0, 0x17F9, 0x1800, 0x180A, 0x1810, 0x1819, + 0x1820, 0x1878, 0x1880, 0x1884, 0x1887, 0x18A8, 0x18AA, 0x18AA, + 0x18B0, 0x18F5, 0x1900, 0x191E, 0x1923, 0x1926, 0x1929, 0x192B, + 0x1930, 0x1931, 0x1933, 0x1938, 0x1940, 0x1940, 0x1944, 0x196D, + 0x1970, 0x1974, 0x1980, 0x19AB, 0x19B0, 0x19C9, 0x19D0, 0x19DA, + 0x19DE, 0x1A16, 0x1A19, 0x1A1A, 0x1A1E, 0x1A55, 0x1A57, 0x1A57, + 0x1A61, 0x1A61, 0x1A63, 0x1A64, 0x1A6D, 0x1A72, 0x1A80, 0x1A89, + 0x1A90, 0x1A99, 0x1AA0, 0x1AAD, 0x1B04, 0x1B33, 0x1B3E, 0x1B41, + 0x1B45, 0x1B4C, 0x1B4E, 0x1B6A, 0x1B74, 0x1B7F, 0x1B82, 0x1BA1, + 0x1BA6, 0x1BA7, 0x1BAE, 0x1BE5, 0x1BE7, 0x1BE7, 0x1BEA, 0x1BEC, + 0x1BEE, 0x1BEE, 0x1BFC, 0x1C2B, 0x1C34, 0x1C35, 0x1C3B, 0x1C49, + 0x1C4D, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CC7, 0x1CD3, 0x1CD3, + 0x1CE1, 0x1CE1, 0x1CE9, 0x1CEC, 0x1CEE, 0x1CF3, 0x1CF5, 0x1CF7, + 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, 0x1E00, 0x1F15, 0x1F18, 0x1F1D, + 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, + 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, + 0x1FB6, 0x1FC4, 0x1FC6, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FDD, 0x1FEF, + 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFE, 0x2000, 0x200A, 0x2010, 0x2027, + 0x202F, 0x205F, 0x2070, 0x2071, 0x2074, 0x208E, 0x2090, 0x209C, + 0x20A0, 0x20C1, 0x2100, 0x218B, 0x2190, 0x2429, 0x2440, 0x244A, + 0x2460, 0x2B73, 0x2B76, 0x2CEE, 0x2CF2, 0x2CF3, 0x2CF9, 0x2D25, + 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, 0x2D30, 0x2D67, 0x2D6F, 0x2D70, + 0x2D80, 0x2D96, 0x2DA0, 0x2DA6, 0x2DA8, 0x2DAE, 0x2DB0, 0x2DB6, + 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, + 0x2DD8, 0x2DDE, 0x2E00, 0x2E5D, 0x2E80, 0x2E99, 0x2E9B, 0x2EF3, + 0x2F00, 0x2FD5, 0x2FF0, 0x3029, 0x3030, 0x303F, 0x3041, 0x3096, + 0x309B, 0x30FF, 0x3105, 0x312F, 0x3131, 0x318E, 0x3190, 0x31E5, + 0x31EF, 0x321E, 0x3220, 0xA48C, 0xA490, 0xA4C6, 0xA4D0, 0xA62B, + 0xA640, 0xA66E, 0xA673, 0xA673, 0xA67E, 0xA69D, 0xA6A0, 0xA6EF, + 0xA6F2, 0xA6F7, 0xA700, 0xA7DC, 0xA7F1, 0xA801, 0xA803, 0xA805, + 0xA807, 0xA80A, 0xA80C, 0xA824, 0xA827, 0xA82B, 0xA830, 0xA839, + 0xA840, 0xA877, 0xA880, 0xA8C3, 0xA8CE, 0xA8D9, 0xA8F2, 0xA8FE, + 0xA900, 0xA925, 0xA92E, 0xA946, 0xA952, 0xA952, 0xA95F, 0xA97C, + 0xA983, 0xA9B2, 0xA9B4, 0xA9B5, 0xA9BA, 0xA9BB, 0xA9BE, 0xA9BF, + 0xA9C1, 0xA9CD, 0xA9CF, 0xA9D9, 0xA9DE, 0xA9E4, 0xA9E6, 0xA9FE, + 0xAA00, 0xAA28, 0xAA2F, 0xAA30, 0xAA33, 0xAA34, 0xAA40, 0xAA42, + 0xAA44, 0xAA4B, 0xAA4D, 0xAA4D, 0xAA50, 0xAA59, 0xAA5C, 0xAA7B, + 0xAA7D, 0xAAAF, 0xAAB1, 0xAAB1, 0xAAB5, 0xAAB6, 0xAAB9, 0xAABD, + 0xAAC0, 0xAAC0, 0xAAC2, 0xAAC2, 0xAADB, 0xAAEB, 0xAAEE, 0xAAF5, + 0xAB01, 0xAB06, 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, + 0xAB28, 0xAB2E, 0xAB30, 0xAB6B, 0xAB70, 0xABE4, 0xABE6, 0xABE7, + 0xABE9, 0xABEC, 0xABF0, 0xABF9, 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, + 0xD7CB, 0xD7FB, 0xF900, 0xFA6D, 0xFA70, 0xFAD9, 0xFB00, 0xFB06, + 0xFB13, 0xFB17, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB36, 0xFB38, 0xFB3C, + 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFDCF, + 0xFDF0, 0xFDFF, 0xFE10, 0xFE19, 0xFE30, 0xFE52, 0xFE54, 0xFE66, + 0xFE68, 0xFE6B, 0xFE70, 0xFE74, 0xFE76, 0xFEFC, 0xFF01, 0xFF9D, + 0xFFA0, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, + 0xFFDA, 0xFFDC, 0xFFE0, 0xFFE6, 0xFFE8, 0xFFEE, 0xFFFC, 0xFFFD, + 0x10000, 0x1000B, 0x1000D, 0x10026, 0x10028, 0x1003A, 0x1003C, 0x1003D, + 0x1003F, 0x1004D, 0x10050, 0x1005D, 0x10080, 0x100FA, 0x10100, 0x10102, + 0x10107, 0x10133, 0x10137, 0x1018E, 0x10190, 0x1019C, 0x101A0, 0x101A0, + 0x101D0, 0x101FC, 0x10280, 0x1029C, 0x102A0, 0x102D0, 0x102E1, 0x102FB, + 0x10300, 0x10323, 0x1032D, 0x1034A, 0x10350, 0x10375, 0x10380, 0x1039D, + 0x1039F, 0x103C3, 0x103C8, 0x103D5, 0x10400, 0x1049D, 0x104A0, 0x104A9, + 0x104B0, 0x104D3, 0x104D8, 0x104FB, 0x10500, 0x10527, 0x10530, 0x10563, + 0x1056F, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, + 0x10597, 0x105A1, 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, + 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, + 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10800, 0x10805, + 0x10808, 0x10808, 0x1080A, 0x10835, 0x10837, 0x10838, 0x1083C, 0x1083C, + 0x1083F, 0x10855, 0x10857, 0x1089E, 0x108A7, 0x108AF, 0x108E0, 0x108F2, + 0x108F4, 0x108F5, 0x108FB, 0x1091B, 0x1091F, 0x10939, 0x1093F, 0x10959, + 0x10980, 0x109B7, 0x109BC, 0x109CF, 0x109D2, 0x10A00, 0x10A10, 0x10A13, + 0x10A15, 0x10A17, 0x10A19, 0x10A35, 0x10A40, 0x10A48, 0x10A50, 0x10A58, + 0x10A60, 0x10A9F, 0x10AC0, 0x10AE4, 0x10AEB, 0x10AF6, 0x10B00, 0x10B35, + 0x10B39, 0x10B55, 0x10B58, 0x10B72, 0x10B78, 0x10B91, 0x10B99, 0x10B9C, + 0x10BA9, 0x10BAF, 0x10C00, 0x10C48, 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, + 0x10CFA, 0x10D23, 0x10D30, 0x10D39, 0x10D40, 0x10D65, 0x10D6E, 0x10D85, + 0x10D8E, 0x10D8F, 0x10E60, 0x10E7E, 0x10E80, 0x10EA9, 0x10EAD, 0x10EAD, + 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC7, 0x10ED0, 0x10ED8, 0x10F00, 0x10F27, + 0x10F30, 0x10F45, 0x10F51, 0x10F59, 0x10F70, 0x10F81, 0x10F86, 0x10F89, + 0x10FB0, 0x10FCB, 0x10FE0, 0x10FF6, 0x11000, 0x11000, 0x11002, 0x11037, + 0x11047, 0x1104D, 0x11052, 0x1106F, 0x11071, 0x11072, 0x11075, 0x11075, + 0x11082, 0x110B2, 0x110B7, 0x110B8, 0x110BB, 0x110BC, 0x110BE, 0x110C1, + 0x110D0, 0x110E8, 0x110F0, 0x110F9, 0x11103, 0x11126, 0x1112C, 0x1112C, + 0x11136, 0x11147, 0x11150, 0x11172, 0x11174, 0x11176, 0x11182, 0x111B5, + 0x111BF, 0x111BF, 0x111C1, 0x111C8, 0x111CD, 0x111CE, 0x111D0, 0x111DF, + 0x111E1, 0x111F4, 0x11200, 0x11211, 0x11213, 0x1122E, 0x11232, 0x11233, + 0x11238, 0x1123D, 0x1123F, 0x11240, 0x11280, 0x11286, 0x11288, 0x11288, + 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A9, 0x112B0, 0x112DE, + 0x112E0, 0x112E2, 0x112F0, 0x112F9, 0x11302, 0x11303, 0x11305, 0x1130C, + 0x1130F, 0x11310, 0x11313, 0x11328, 0x1132A, 0x11330, 0x11332, 0x11333, + 0x11335, 0x11339, 0x1133D, 0x1133D, 0x1133F, 0x1133F, 0x11341, 0x11344, + 0x11347, 0x11348, 0x1134B, 0x1134C, 0x11350, 0x11350, 0x1135D, 0x11363, + 0x11380, 0x11389, 0x1138B, 0x1138B, 0x1138E, 0x1138E, 0x11390, 0x113B5, + 0x113B7, 0x113B7, 0x113B9, 0x113BA, 0x113CA, 0x113CA, 0x113CC, 0x113CD, + 0x113D1, 0x113D1, 0x113D3, 0x113D5, 0x113D7, 0x113D8, 0x11400, 0x11437, + 0x11440, 0x11441, 0x11445, 0x11445, 0x11447, 0x1145B, 0x1145D, 0x1145D, + 0x1145F, 0x11461, 0x11480, 0x114AF, 0x114B1, 0x114B2, 0x114B9, 0x114B9, + 0x114BB, 0x114BC, 0x114BE, 0x114BE, 0x114C1, 0x114C1, 0x114C4, 0x114C7, + 0x114D0, 0x114D9, 0x11580, 0x115AE, 0x115B0, 0x115B1, 0x115B8, 0x115BB, + 0x115BE, 0x115BE, 0x115C1, 0x115DB, 0x11600, 0x11632, 0x1163B, 0x1163C, + 0x1163E, 0x1163E, 0x11641, 0x11644, 0x11650, 0x11659, 0x11660, 0x1166C, + 0x11680, 0x116AA, 0x116AC, 0x116AC, 0x116AE, 0x116AF, 0x116B8, 0x116B9, + 0x116C0, 0x116C9, 0x116D0, 0x116E3, 0x11700, 0x1171A, 0x1171E, 0x1171E, + 0x11720, 0x11721, 0x11726, 0x11726, 0x11730, 0x11746, 0x11800, 0x1182E, + 0x11838, 0x11838, 0x1183B, 0x1183B, 0x118A0, 0x118F2, 0x118FF, 0x11906, + 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, 0x11916, 0x11918, 0x1192F, + 0x11931, 0x11935, 0x11937, 0x11938, 0x1193F, 0x11942, 0x11944, 0x11946, + 0x11950, 0x11959, 0x119A0, 0x119A7, 0x119AA, 0x119D3, 0x119DC, 0x119DF, + 0x119E1, 0x119E4, 0x11A00, 0x11A00, 0x11A0B, 0x11A32, 0x11A39, 0x11A3A, + 0x11A3F, 0x11A46, 0x11A50, 0x11A50, 0x11A57, 0x11A58, 0x11A5C, 0x11A89, + 0x11A97, 0x11A97, 0x11A9A, 0x11AA2, 0x11AB0, 0x11AF8, 0x11B00, 0x11B09, + 0x11B61, 0x11B61, 0x11B65, 0x11B65, 0x11B67, 0x11B67, 0x11BC0, 0x11BE1, + 0x11BF0, 0x11BF9, 0x11C00, 0x11C08, 0x11C0A, 0x11C2F, 0x11C3E, 0x11C3E, + 0x11C40, 0x11C45, 0x11C50, 0x11C6C, 0x11C70, 0x11C8F, 0x11CA9, 0x11CA9, + 0x11CB1, 0x11CB1, 0x11CB4, 0x11CB4, 0x11D00, 0x11D06, 0x11D08, 0x11D09, + 0x11D0B, 0x11D30, 0x11D46, 0x11D46, 0x11D50, 0x11D59, 0x11D60, 0x11D65, + 0x11D67, 0x11D68, 0x11D6A, 0x11D8E, 0x11D93, 0x11D94, 0x11D96, 0x11D96, + 0x11D98, 0x11D98, 0x11DA0, 0x11DA9, 0x11DB0, 0x11DDB, 0x11DE0, 0x11DE9, + 0x11EE0, 0x11EF2, 0x11EF5, 0x11EF8, 0x11F02, 0x11F10, 0x11F12, 0x11F35, + 0x11F3E, 0x11F3F, 0x11F43, 0x11F59, 0x11FB0, 0x11FB0, 0x11FC0, 0x11FF1, + 0x11FFF, 0x12399, 0x12400, 0x1246E, 0x12470, 0x12474, 0x12480, 0x12543, + 0x12F90, 0x12FF2, 0x13000, 0x1342F, 0x13441, 0x13446, 0x13460, 0x143FA, + 0x14400, 0x14646, 0x16100, 0x1611D, 0x1612A, 0x1612C, 0x16130, 0x16139, + 0x16800, 0x16A38, 0x16A40, 0x16A5E, 0x16A60, 0x16A69, 0x16A6E, 0x16ABE, + 0x16AC0, 0x16AC9, 0x16AD0, 0x16AED, 0x16AF5, 0x16AF5, 0x16B00, 0x16B2F, + 0x16B37, 0x16B45, 0x16B50, 0x16B59, 0x16B5B, 0x16B61, 0x16B63, 0x16B77, + 0x16B7D, 0x16B8F, 0x16D40, 0x16D79, 0x16E40, 0x16E9A, 0x16EA0, 0x16EB8, + 0x16EBB, 0x16ED3, 0x16F00, 0x16F4A, 0x16F50, 0x16F87, 0x16F93, 0x16F9F, + 0x16FE0, 0x16FE3, 0x16FF2, 0x16FF6, 0x17000, 0x18CD5, 0x18CFF, 0x18D1E, + 0x18D80, 0x18DF2, 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, + 0x1B000, 0x1B122, 0x1B132, 0x1B132, 0x1B150, 0x1B152, 0x1B155, 0x1B155, + 0x1B164, 0x1B167, 0x1B170, 0x1B2FB, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, + 0x1BC80, 0x1BC88, 0x1BC90, 0x1BC99, 0x1BC9C, 0x1BC9C, 0x1BC9F, 0x1BC9F, + 0x1CC00, 0x1CCFC, 0x1CD00, 0x1CEB3, 0x1CEBA, 0x1CED0, 0x1CEE0, 0x1CEF0, + 0x1CF50, 0x1CFC3, 0x1D000, 0x1D0F5, 0x1D100, 0x1D126, 0x1D129, 0x1D164, + 0x1D16A, 0x1D16C, 0x1D183, 0x1D184, 0x1D18C, 0x1D1A9, 0x1D1AE, 0x1D1EA, + 0x1D200, 0x1D241, 0x1D245, 0x1D245, 0x1D2C0, 0x1D2D3, 0x1D2E0, 0x1D2F3, + 0x1D300, 0x1D356, 0x1D360, 0x1D378, 0x1D400, 0x1D454, 0x1D456, 0x1D49C, + 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, + 0x1D4AE, 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, + 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, + 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, + 0x1D552, 0x1D6A5, 0x1D6A8, 0x1D7CB, 0x1D7CE, 0x1D9FF, 0x1DA37, 0x1DA3A, + 0x1DA6D, 0x1DA74, 0x1DA76, 0x1DA83, 0x1DA85, 0x1DA8B, 0x1DF00, 0x1DF1E, + 0x1DF25, 0x1DF2A, 0x1E030, 0x1E06D, 0x1E100, 0x1E12C, 0x1E137, 0x1E13D, + 0x1E140, 0x1E149, 0x1E14E, 0x1E14F, 0x1E290, 0x1E2AD, 0x1E2C0, 0x1E2EB, + 0x1E2F0, 0x1E2F9, 0x1E2FF, 0x1E2FF, 0x1E4D0, 0x1E4EB, 0x1E4F0, 0x1E4F9, + 0x1E5D0, 0x1E5ED, 0x1E5F0, 0x1E5FA, 0x1E5FF, 0x1E5FF, 0x1E6C0, 0x1E6DE, + 0x1E6E0, 0x1E6E2, 0x1E6E4, 0x1E6E5, 0x1E6E7, 0x1E6ED, 0x1E6F0, 0x1E6F4, + 0x1E6FE, 0x1E6FF, 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, + 0x1E7F0, 0x1E7FE, 0x1E800, 0x1E8C4, 0x1E8C7, 0x1E8CF, 0x1E900, 0x1E943, + 0x1E94B, 0x1E94B, 0x1E950, 0x1E959, 0x1E95E, 0x1E95F, 0x1EC71, 0x1ECB4, + 0x1ED01, 0x1ED3D, 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, 0x1EE21, 0x1EE22, + 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, + 0x1EE39, 0x1EE39, 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, + 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, 0x1EE4F, 0x1EE51, 0x1EE52, + 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, + 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, + 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, 0x1EE74, 0x1EE77, 0x1EE79, 0x1EE7C, + 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, + 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, 0x1EEF0, 0x1EEF1, 0x1F000, 0x1F02B, + 0x1F030, 0x1F093, 0x1F0A0, 0x1F0AE, 0x1F0B1, 0x1F0BF, 0x1F0C1, 0x1F0CF, + 0x1F0D1, 0x1F0F5, 0x1F100, 0x1F1AD, 0x1F1E6, 0x1F202, 0x1F210, 0x1F23B, + 0x1F240, 0x1F248, 0x1F250, 0x1F251, 0x1F260, 0x1F265, 0x1F300, 0x1F6D8, + 0x1F6DC, 0x1F6EC, 0x1F6F0, 0x1F6FC, 0x1F700, 0x1F7D9, 0x1F7E0, 0x1F7EB, + 0x1F7F0, 0x1F7F0, 0x1F800, 0x1F80B, 0x1F810, 0x1F847, 0x1F850, 0x1F859, + 0x1F860, 0x1F887, 0x1F890, 0x1F8AD, 0x1F8B0, 0x1F8BB, 0x1F8C0, 0x1F8C1, + 0x1F8D0, 0x1F8D8, 0x1F900, 0x1FA57, 0x1FA60, 0x1FA6D, 0x1FA70, 0x1FA7C, + 0x1FA80, 0x1FA8A, 0x1FA8E, 0x1FAC6, 0x1FAC8, 0x1FAC8, 0x1FACD, 0x1FADC, + 0x1FADF, 0x1FAEA, 0x1FAEF, 0x1FAF8, 0x1FB00, 0x1FB92, 0x1FB94, 0x1FBFA, + 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, 0x2CEB0, 0x2EBE0, + 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, 0x31350, 0x33479, + // #70 (10799+383): bp=Grapheme_Extend:Gr_Ext + 0x0300, 0x036F, 0x0483, 0x0489, 0x0591, 0x05BD, 0x05BF, 0x05BF, + 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x0610, 0x061A, + 0x064B, 0x065F, 0x0670, 0x0670, 0x06D6, 0x06DC, 0x06DF, 0x06E4, + 0x06E7, 0x06E8, 0x06EA, 0x06ED, 0x0711, 0x0711, 0x0730, 0x074A, + 0x07A6, 0x07B0, 0x07EB, 0x07F3, 0x07FD, 0x07FD, 0x0816, 0x0819, + 0x081B, 0x0823, 0x0825, 0x0827, 0x0829, 0x082D, 0x0859, 0x085B, + 0x0897, 0x089F, 0x08CA, 0x08E1, 0x08E3, 0x0902, 0x093A, 0x093A, + 0x093C, 0x093C, 0x0941, 0x0948, 0x094D, 0x094D, 0x0951, 0x0957, + 0x0962, 0x0963, 0x0981, 0x0981, 0x09BC, 0x09BC, 0x09BE, 0x09BE, + 0x09C1, 0x09C4, 0x09CD, 0x09CD, 0x09D7, 0x09D7, 0x09E2, 0x09E3, + 0x09FE, 0x09FE, 0x0A01, 0x0A02, 0x0A3C, 0x0A3C, 0x0A41, 0x0A42, + 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, 0x0A51, 0x0A51, 0x0A70, 0x0A71, + 0x0A75, 0x0A75, 0x0A81, 0x0A82, 0x0ABC, 0x0ABC, 0x0AC1, 0x0AC5, + 0x0AC7, 0x0AC8, 0x0ACD, 0x0ACD, 0x0AE2, 0x0AE3, 0x0AFA, 0x0AFF, + 0x0B01, 0x0B01, 0x0B3C, 0x0B3C, 0x0B3E, 0x0B3F, 0x0B41, 0x0B44, + 0x0B4D, 0x0B4D, 0x0B55, 0x0B57, 0x0B62, 0x0B63, 0x0B82, 0x0B82, + 0x0BBE, 0x0BBE, 0x0BC0, 0x0BC0, 0x0BCD, 0x0BCD, 0x0BD7, 0x0BD7, + 0x0C00, 0x0C00, 0x0C04, 0x0C04, 0x0C3C, 0x0C3C, 0x0C3E, 0x0C40, + 0x0C46, 0x0C48, 0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0C62, 0x0C63, + 0x0C81, 0x0C81, 0x0CBC, 0x0CBC, 0x0CBF, 0x0CC0, 0x0CC2, 0x0CC2, + 0x0CC6, 0x0CC8, 0x0CCA, 0x0CCD, 0x0CD5, 0x0CD6, 0x0CE2, 0x0CE3, + 0x0D00, 0x0D01, 0x0D3B, 0x0D3C, 0x0D3E, 0x0D3E, 0x0D41, 0x0D44, + 0x0D4D, 0x0D4D, 0x0D57, 0x0D57, 0x0D62, 0x0D63, 0x0D81, 0x0D81, + 0x0DCA, 0x0DCA, 0x0DCF, 0x0DCF, 0x0DD2, 0x0DD4, 0x0DD6, 0x0DD6, + 0x0DDF, 0x0DDF, 0x0E31, 0x0E31, 0x0E34, 0x0E3A, 0x0E47, 0x0E4E, + 0x0EB1, 0x0EB1, 0x0EB4, 0x0EBC, 0x0EC8, 0x0ECE, 0x0F18, 0x0F19, + 0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F71, 0x0F7E, + 0x0F80, 0x0F84, 0x0F86, 0x0F87, 0x0F8D, 0x0F97, 0x0F99, 0x0FBC, + 0x0FC6, 0x0FC6, 0x102D, 0x1030, 0x1032, 0x1037, 0x1039, 0x103A, + 0x103D, 0x103E, 0x1058, 0x1059, 0x105E, 0x1060, 0x1071, 0x1074, + 0x1082, 0x1082, 0x1085, 0x1086, 0x108D, 0x108D, 0x109D, 0x109D, + 0x135D, 0x135F, 0x1712, 0x1715, 0x1732, 0x1734, 0x1752, 0x1753, + 0x1772, 0x1773, 0x17B4, 0x17B5, 0x17B7, 0x17BD, 0x17C6, 0x17C6, + 0x17C9, 0x17D3, 0x17DD, 0x17DD, 0x180B, 0x180D, 0x180F, 0x180F, + 0x1885, 0x1886, 0x18A9, 0x18A9, 0x1920, 0x1922, 0x1927, 0x1928, + 0x1932, 0x1932, 0x1939, 0x193B, 0x1A17, 0x1A18, 0x1A1B, 0x1A1B, + 0x1A56, 0x1A56, 0x1A58, 0x1A5E, 0x1A60, 0x1A60, 0x1A62, 0x1A62, + 0x1A65, 0x1A6C, 0x1A73, 0x1A7C, 0x1A7F, 0x1A7F, 0x1AB0, 0x1ADD, + 0x1AE0, 0x1AEB, 0x1B00, 0x1B03, 0x1B34, 0x1B3D, 0x1B42, 0x1B44, + 0x1B6B, 0x1B73, 0x1B80, 0x1B81, 0x1BA2, 0x1BA5, 0x1BA8, 0x1BAD, + 0x1BE6, 0x1BE6, 0x1BE8, 0x1BE9, 0x1BED, 0x1BED, 0x1BEF, 0x1BF3, + 0x1C2C, 0x1C33, 0x1C36, 0x1C37, 0x1CD0, 0x1CD2, 0x1CD4, 0x1CE0, + 0x1CE2, 0x1CE8, 0x1CED, 0x1CED, 0x1CF4, 0x1CF4, 0x1CF8, 0x1CF9, + 0x1DC0, 0x1DFF, 0x200C, 0x200C, 0x20D0, 0x20F0, 0x2CEF, 0x2CF1, + 0x2D7F, 0x2D7F, 0x2DE0, 0x2DFF, 0x302A, 0x302F, 0x3099, 0x309A, + 0xA66F, 0xA672, 0xA674, 0xA67D, 0xA69E, 0xA69F, 0xA6F0, 0xA6F1, + 0xA802, 0xA802, 0xA806, 0xA806, 0xA80B, 0xA80B, 0xA825, 0xA826, + 0xA82C, 0xA82C, 0xA8C4, 0xA8C5, 0xA8E0, 0xA8F1, 0xA8FF, 0xA8FF, + 0xA926, 0xA92D, 0xA947, 0xA951, 0xA953, 0xA953, 0xA980, 0xA982, + 0xA9B3, 0xA9B3, 0xA9B6, 0xA9B9, 0xA9BC, 0xA9BD, 0xA9C0, 0xA9C0, + 0xA9E5, 0xA9E5, 0xAA29, 0xAA2E, 0xAA31, 0xAA32, 0xAA35, 0xAA36, + 0xAA43, 0xAA43, 0xAA4C, 0xAA4C, 0xAA7C, 0xAA7C, 0xAAB0, 0xAAB0, + 0xAAB2, 0xAAB4, 0xAAB7, 0xAAB8, 0xAABE, 0xAABF, 0xAAC1, 0xAAC1, + 0xAAEC, 0xAAED, 0xAAF6, 0xAAF6, 0xABE5, 0xABE5, 0xABE8, 0xABE8, + 0xABED, 0xABED, 0xFB1E, 0xFB1E, 0xFE00, 0xFE0F, 0xFE20, 0xFE2F, + 0xFF9E, 0xFF9F, 0x101FD, 0x101FD, 0x102E0, 0x102E0, 0x10376, 0x1037A, + 0x10A01, 0x10A03, 0x10A05, 0x10A06, 0x10A0C, 0x10A0F, 0x10A38, 0x10A3A, + 0x10A3F, 0x10A3F, 0x10AE5, 0x10AE6, 0x10D24, 0x10D27, 0x10D69, 0x10D6D, + 0x10EAB, 0x10EAC, 0x10EFA, 0x10EFF, 0x10F46, 0x10F50, 0x10F82, 0x10F85, + 0x11001, 0x11001, 0x11038, 0x11046, 0x11070, 0x11070, 0x11073, 0x11074, + 0x1107F, 0x11081, 0x110B3, 0x110B6, 0x110B9, 0x110BA, 0x110C2, 0x110C2, + 0x11100, 0x11102, 0x11127, 0x1112B, 0x1112D, 0x11134, 0x11173, 0x11173, + 0x11180, 0x11181, 0x111B6, 0x111BE, 0x111C0, 0x111C0, 0x111C9, 0x111CC, + 0x111CF, 0x111CF, 0x1122F, 0x11231, 0x11234, 0x11237, 0x1123E, 0x1123E, + 0x11241, 0x11241, 0x112DF, 0x112DF, 0x112E3, 0x112EA, 0x11300, 0x11301, + 0x1133B, 0x1133C, 0x1133E, 0x1133E, 0x11340, 0x11340, 0x1134D, 0x1134D, + 0x11357, 0x11357, 0x11366, 0x1136C, 0x11370, 0x11374, 0x113B8, 0x113B8, + 0x113BB, 0x113C0, 0x113C2, 0x113C2, 0x113C5, 0x113C5, 0x113C7, 0x113C9, + 0x113CE, 0x113D0, 0x113D2, 0x113D2, 0x113E1, 0x113E2, 0x11438, 0x1143F, + 0x11442, 0x11444, 0x11446, 0x11446, 0x1145E, 0x1145E, 0x114B0, 0x114B0, + 0x114B3, 0x114B8, 0x114BA, 0x114BA, 0x114BD, 0x114BD, 0x114BF, 0x114C0, + 0x114C2, 0x114C3, 0x115AF, 0x115AF, 0x115B2, 0x115B5, 0x115BC, 0x115BD, + 0x115BF, 0x115C0, 0x115DC, 0x115DD, 0x11633, 0x1163A, 0x1163D, 0x1163D, + 0x1163F, 0x11640, 0x116AB, 0x116AB, 0x116AD, 0x116AD, 0x116B0, 0x116B7, + 0x1171D, 0x1171D, 0x1171F, 0x1171F, 0x11722, 0x11725, 0x11727, 0x1172B, + 0x1182F, 0x11837, 0x11839, 0x1183A, 0x11930, 0x11930, 0x1193B, 0x1193E, + 0x11943, 0x11943, 0x119D4, 0x119D7, 0x119DA, 0x119DB, 0x119E0, 0x119E0, + 0x11A01, 0x11A0A, 0x11A33, 0x11A38, 0x11A3B, 0x11A3E, 0x11A47, 0x11A47, + 0x11A51, 0x11A56, 0x11A59, 0x11A5B, 0x11A8A, 0x11A96, 0x11A98, 0x11A99, + 0x11B60, 0x11B60, 0x11B62, 0x11B64, 0x11B66, 0x11B66, 0x11C30, 0x11C36, + 0x11C38, 0x11C3D, 0x11C3F, 0x11C3F, 0x11C92, 0x11CA7, 0x11CAA, 0x11CB0, + 0x11CB2, 0x11CB3, 0x11CB5, 0x11CB6, 0x11D31, 0x11D36, 0x11D3A, 0x11D3A, + 0x11D3C, 0x11D3D, 0x11D3F, 0x11D45, 0x11D47, 0x11D47, 0x11D90, 0x11D91, + 0x11D95, 0x11D95, 0x11D97, 0x11D97, 0x11EF3, 0x11EF4, 0x11F00, 0x11F01, + 0x11F36, 0x11F3A, 0x11F40, 0x11F42, 0x11F5A, 0x11F5A, 0x13440, 0x13440, + 0x13447, 0x13455, 0x1611E, 0x16129, 0x1612D, 0x1612F, 0x16AF0, 0x16AF4, + 0x16B30, 0x16B36, 0x16F4F, 0x16F4F, 0x16F8F, 0x16F92, 0x16FE4, 0x16FE4, + 0x16FF0, 0x16FF1, 0x1BC9D, 0x1BC9E, 0x1CF00, 0x1CF2D, 0x1CF30, 0x1CF46, + 0x1D165, 0x1D169, 0x1D16D, 0x1D172, 0x1D17B, 0x1D182, 0x1D185, 0x1D18B, + 0x1D1AA, 0x1D1AD, 0x1D242, 0x1D244, 0x1DA00, 0x1DA36, 0x1DA3B, 0x1DA6C, + 0x1DA75, 0x1DA75, 0x1DA84, 0x1DA84, 0x1DA9B, 0x1DA9F, 0x1DAA1, 0x1DAAF, + 0x1E000, 0x1E006, 0x1E008, 0x1E018, 0x1E01B, 0x1E021, 0x1E023, 0x1E024, + 0x1E026, 0x1E02A, 0x1E08F, 0x1E08F, 0x1E130, 0x1E136, 0x1E2AE, 0x1E2AE, + 0x1E2EC, 0x1E2EF, 0x1E4EC, 0x1E4EF, 0x1E5EE, 0x1E5EF, 0x1E6E3, 0x1E6E3, + 0x1E6E6, 0x1E6E6, 0x1E6EE, 0x1E6EF, 0x1E6F5, 0x1E6F5, 0x1E8D0, 0x1E8D6, + 0x1E944, 0x1E94A, 0xE0020, 0xE007F, 0xE0100, 0xE01EF, + // #71 (11182+6): bp=Hex_Digit:Hex + 0x0030, 0x0039, 0x0041, 0x0046, 0x0061, 0x0066, 0xFF10, 0xFF19, + 0xFF21, 0xFF26, 0xFF41, 0xFF46, + // #72 (11188+3): bp=IDS_Binary_Operator:IDSB + 0x2FF0, 0x2FF1, 0x2FF4, 0x2FFD, 0x31EF, 0x31EF, + // #73 (11191+1): bp=IDS_Trinary_Operator:IDST + 0x2FF2, 0x2FF3, + // #74 (11192+799): bp=ID_Continue:IDC + 0x0030, 0x0039, 0x0041, 0x005A, 0x005F, 0x005F, 0x0061, 0x007A, + 0x00AA, 0x00AA, 0x00B5, 0x00B5, 0x00B7, 0x00B7, 0x00BA, 0x00BA, + 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x02C1, 0x02C6, 0x02D1, + 0x02E0, 0x02E4, 0x02EC, 0x02EC, 0x02EE, 0x02EE, 0x0300, 0x0374, + 0x0376, 0x0377, 0x037A, 0x037D, 0x037F, 0x037F, 0x0386, 0x038A, + 0x038C, 0x038C, 0x038E, 0x03A1, 0x03A3, 0x03F5, 0x03F7, 0x0481, + 0x0483, 0x0487, 0x048A, 0x052F, 0x0531, 0x0556, 0x0559, 0x0559, + 0x0560, 0x0588, 0x0591, 0x05BD, 0x05BF, 0x05BF, 0x05C1, 0x05C2, + 0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x05D0, 0x05EA, 0x05EF, 0x05F2, + 0x0610, 0x061A, 0x0620, 0x0669, 0x066E, 0x06D3, 0x06D5, 0x06DC, + 0x06DF, 0x06E8, 0x06EA, 0x06FC, 0x06FF, 0x06FF, 0x0710, 0x074A, + 0x074D, 0x07B1, 0x07C0, 0x07F5, 0x07FA, 0x07FA, 0x07FD, 0x07FD, + 0x0800, 0x082D, 0x0840, 0x085B, 0x0860, 0x086A, 0x0870, 0x0887, + 0x0889, 0x088F, 0x0897, 0x08E1, 0x08E3, 0x0963, 0x0966, 0x096F, + 0x0971, 0x0983, 0x0985, 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, + 0x09AA, 0x09B0, 0x09B2, 0x09B2, 0x09B6, 0x09B9, 0x09BC, 0x09C4, + 0x09C7, 0x09C8, 0x09CB, 0x09CE, 0x09D7, 0x09D7, 0x09DC, 0x09DD, + 0x09DF, 0x09E3, 0x09E6, 0x09F1, 0x09FC, 0x09FC, 0x09FE, 0x09FE, + 0x0A01, 0x0A03, 0x0A05, 0x0A0A, 0x0A0F, 0x0A10, 0x0A13, 0x0A28, + 0x0A2A, 0x0A30, 0x0A32, 0x0A33, 0x0A35, 0x0A36, 0x0A38, 0x0A39, + 0x0A3C, 0x0A3C, 0x0A3E, 0x0A42, 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, + 0x0A51, 0x0A51, 0x0A59, 0x0A5C, 0x0A5E, 0x0A5E, 0x0A66, 0x0A75, + 0x0A81, 0x0A83, 0x0A85, 0x0A8D, 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, + 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, 0x0AB5, 0x0AB9, 0x0ABC, 0x0AC5, + 0x0AC7, 0x0AC9, 0x0ACB, 0x0ACD, 0x0AD0, 0x0AD0, 0x0AE0, 0x0AE3, + 0x0AE6, 0x0AEF, 0x0AF9, 0x0AFF, 0x0B01, 0x0B03, 0x0B05, 0x0B0C, + 0x0B0F, 0x0B10, 0x0B13, 0x0B28, 0x0B2A, 0x0B30, 0x0B32, 0x0B33, + 0x0B35, 0x0B39, 0x0B3C, 0x0B44, 0x0B47, 0x0B48, 0x0B4B, 0x0B4D, + 0x0B55, 0x0B57, 0x0B5C, 0x0B5D, 0x0B5F, 0x0B63, 0x0B66, 0x0B6F, + 0x0B71, 0x0B71, 0x0B82, 0x0B83, 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, + 0x0B92, 0x0B95, 0x0B99, 0x0B9A, 0x0B9C, 0x0B9C, 0x0B9E, 0x0B9F, + 0x0BA3, 0x0BA4, 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB9, 0x0BBE, 0x0BC2, + 0x0BC6, 0x0BC8, 0x0BCA, 0x0BCD, 0x0BD0, 0x0BD0, 0x0BD7, 0x0BD7, + 0x0BE6, 0x0BEF, 0x0C00, 0x0C0C, 0x0C0E, 0x0C10, 0x0C12, 0x0C28, + 0x0C2A, 0x0C39, 0x0C3C, 0x0C44, 0x0C46, 0x0C48, 0x0C4A, 0x0C4D, + 0x0C55, 0x0C56, 0x0C58, 0x0C5A, 0x0C5C, 0x0C5D, 0x0C60, 0x0C63, + 0x0C66, 0x0C6F, 0x0C80, 0x0C83, 0x0C85, 0x0C8C, 0x0C8E, 0x0C90, + 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, 0x0CB5, 0x0CB9, 0x0CBC, 0x0CC4, + 0x0CC6, 0x0CC8, 0x0CCA, 0x0CCD, 0x0CD5, 0x0CD6, 0x0CDC, 0x0CDE, + 0x0CE0, 0x0CE3, 0x0CE6, 0x0CEF, 0x0CF1, 0x0CF3, 0x0D00, 0x0D0C, + 0x0D0E, 0x0D10, 0x0D12, 0x0D44, 0x0D46, 0x0D48, 0x0D4A, 0x0D4E, + 0x0D54, 0x0D57, 0x0D5F, 0x0D63, 0x0D66, 0x0D6F, 0x0D7A, 0x0D7F, + 0x0D81, 0x0D83, 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, 0x0DB3, 0x0DBB, + 0x0DBD, 0x0DBD, 0x0DC0, 0x0DC6, 0x0DCA, 0x0DCA, 0x0DCF, 0x0DD4, + 0x0DD6, 0x0DD6, 0x0DD8, 0x0DDF, 0x0DE6, 0x0DEF, 0x0DF2, 0x0DF3, + 0x0E01, 0x0E3A, 0x0E40, 0x0E4E, 0x0E50, 0x0E59, 0x0E81, 0x0E82, + 0x0E84, 0x0E84, 0x0E86, 0x0E8A, 0x0E8C, 0x0EA3, 0x0EA5, 0x0EA5, + 0x0EA7, 0x0EBD, 0x0EC0, 0x0EC4, 0x0EC6, 0x0EC6, 0x0EC8, 0x0ECE, + 0x0ED0, 0x0ED9, 0x0EDC, 0x0EDF, 0x0F00, 0x0F00, 0x0F18, 0x0F19, + 0x0F20, 0x0F29, 0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, + 0x0F3E, 0x0F47, 0x0F49, 0x0F6C, 0x0F71, 0x0F84, 0x0F86, 0x0F97, + 0x0F99, 0x0FBC, 0x0FC6, 0x0FC6, 0x1000, 0x1049, 0x1050, 0x109D, + 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, + 0x10FC, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, + 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, + 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, + 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, 0x1318, 0x135A, + 0x135D, 0x135F, 0x1369, 0x1371, 0x1380, 0x138F, 0x13A0, 0x13F5, + 0x13F8, 0x13FD, 0x1401, 0x166C, 0x166F, 0x167F, 0x1681, 0x169A, + 0x16A0, 0x16EA, 0x16EE, 0x16F8, 0x1700, 0x1715, 0x171F, 0x1734, + 0x1740, 0x1753, 0x1760, 0x176C, 0x176E, 0x1770, 0x1772, 0x1773, + 0x1780, 0x17D3, 0x17D7, 0x17D7, 0x17DC, 0x17DD, 0x17E0, 0x17E9, + 0x180B, 0x180D, 0x180F, 0x1819, 0x1820, 0x1878, 0x1880, 0x18AA, + 0x18B0, 0x18F5, 0x1900, 0x191E, 0x1920, 0x192B, 0x1930, 0x193B, + 0x1946, 0x196D, 0x1970, 0x1974, 0x1980, 0x19AB, 0x19B0, 0x19C9, + 0x19D0, 0x19DA, 0x1A00, 0x1A1B, 0x1A20, 0x1A5E, 0x1A60, 0x1A7C, + 0x1A7F, 0x1A89, 0x1A90, 0x1A99, 0x1AA7, 0x1AA7, 0x1AB0, 0x1ABD, + 0x1ABF, 0x1ADD, 0x1AE0, 0x1AEB, 0x1B00, 0x1B4C, 0x1B50, 0x1B59, + 0x1B6B, 0x1B73, 0x1B80, 0x1BF3, 0x1C00, 0x1C37, 0x1C40, 0x1C49, + 0x1C4D, 0x1C7D, 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, + 0x1CD0, 0x1CD2, 0x1CD4, 0x1CFA, 0x1D00, 0x1F15, 0x1F18, 0x1F1D, + 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, + 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, + 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, + 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, + 0x1FF6, 0x1FFC, 0x200C, 0x200D, 0x203F, 0x2040, 0x2054, 0x2054, + 0x2071, 0x2071, 0x207F, 0x207F, 0x2090, 0x209C, 0x20D0, 0x20DC, + 0x20E1, 0x20E1, 0x20E5, 0x20F0, 0x2102, 0x2102, 0x2107, 0x2107, + 0x210A, 0x2113, 0x2115, 0x2115, 0x2118, 0x211D, 0x2124, 0x2124, + 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x2139, 0x213C, 0x213F, + 0x2145, 0x2149, 0x214E, 0x214E, 0x2160, 0x2188, 0x2C00, 0x2CE4, + 0x2CEB, 0x2CF3, 0x2D00, 0x2D25, 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, + 0x2D30, 0x2D67, 0x2D6F, 0x2D6F, 0x2D7F, 0x2D96, 0x2DA0, 0x2DA6, + 0x2DA8, 0x2DAE, 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, + 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, 0x2DE0, 0x2DFF, + 0x3005, 0x3007, 0x3021, 0x302F, 0x3031, 0x3035, 0x3038, 0x303C, + 0x3041, 0x3096, 0x3099, 0x309F, 0x30A1, 0x30FF, 0x3105, 0x312F, + 0x3131, 0x318E, 0x31A0, 0x31BF, 0x31F0, 0x31FF, 0x3400, 0x4DBF, + 0x4E00, 0xA48C, 0xA4D0, 0xA4FD, 0xA500, 0xA60C, 0xA610, 0xA62B, + 0xA640, 0xA66F, 0xA674, 0xA67D, 0xA67F, 0xA6F1, 0xA717, 0xA71F, + 0xA722, 0xA788, 0xA78B, 0xA7DC, 0xA7F1, 0xA827, 0xA82C, 0xA82C, + 0xA840, 0xA873, 0xA880, 0xA8C5, 0xA8D0, 0xA8D9, 0xA8E0, 0xA8F7, + 0xA8FB, 0xA8FB, 0xA8FD, 0xA92D, 0xA930, 0xA953, 0xA960, 0xA97C, + 0xA980, 0xA9C0, 0xA9CF, 0xA9D9, 0xA9E0, 0xA9FE, 0xAA00, 0xAA36, + 0xAA40, 0xAA4D, 0xAA50, 0xAA59, 0xAA60, 0xAA76, 0xAA7A, 0xAAC2, + 0xAADB, 0xAADD, 0xAAE0, 0xAAEF, 0xAAF2, 0xAAF6, 0xAB01, 0xAB06, + 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, + 0xAB30, 0xAB5A, 0xAB5C, 0xAB69, 0xAB70, 0xABEA, 0xABEC, 0xABED, + 0xABF0, 0xABF9, 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, 0xD7CB, 0xD7FB, + 0xF900, 0xFA6D, 0xFA70, 0xFAD9, 0xFB00, 0xFB06, 0xFB13, 0xFB17, + 0xFB1D, 0xFB28, 0xFB2A, 0xFB36, 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, + 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFBB1, 0xFBD3, 0xFD3D, + 0xFD50, 0xFD8F, 0xFD92, 0xFDC7, 0xFDF0, 0xFDFB, 0xFE00, 0xFE0F, + 0xFE20, 0xFE2F, 0xFE33, 0xFE34, 0xFE4D, 0xFE4F, 0xFE70, 0xFE74, + 0xFE76, 0xFEFC, 0xFF10, 0xFF19, 0xFF21, 0xFF3A, 0xFF3F, 0xFF3F, + 0xFF41, 0xFF5A, 0xFF65, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, + 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0x10000, 0x1000B, 0x1000D, 0x10026, + 0x10028, 0x1003A, 0x1003C, 0x1003D, 0x1003F, 0x1004D, 0x10050, 0x1005D, + 0x10080, 0x100FA, 0x10140, 0x10174, 0x101FD, 0x101FD, 0x10280, 0x1029C, + 0x102A0, 0x102D0, 0x102E0, 0x102E0, 0x10300, 0x1031F, 0x1032D, 0x1034A, + 0x10350, 0x1037A, 0x10380, 0x1039D, 0x103A0, 0x103C3, 0x103C8, 0x103CF, + 0x103D1, 0x103D5, 0x10400, 0x1049D, 0x104A0, 0x104A9, 0x104B0, 0x104D3, + 0x104D8, 0x104FB, 0x10500, 0x10527, 0x10530, 0x10563, 0x10570, 0x1057A, + 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, 0x10597, 0x105A1, + 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x105C0, 0x105F3, + 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, 0x10780, 0x10785, + 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10800, 0x10805, 0x10808, 0x10808, + 0x1080A, 0x10835, 0x10837, 0x10838, 0x1083C, 0x1083C, 0x1083F, 0x10855, + 0x10860, 0x10876, 0x10880, 0x1089E, 0x108E0, 0x108F2, 0x108F4, 0x108F5, + 0x10900, 0x10915, 0x10920, 0x10939, 0x10940, 0x10959, 0x10980, 0x109B7, + 0x109BE, 0x109BF, 0x10A00, 0x10A03, 0x10A05, 0x10A06, 0x10A0C, 0x10A13, + 0x10A15, 0x10A17, 0x10A19, 0x10A35, 0x10A38, 0x10A3A, 0x10A3F, 0x10A3F, + 0x10A60, 0x10A7C, 0x10A80, 0x10A9C, 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE6, + 0x10B00, 0x10B35, 0x10B40, 0x10B55, 0x10B60, 0x10B72, 0x10B80, 0x10B91, + 0x10C00, 0x10C48, 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, 0x10D00, 0x10D27, + 0x10D30, 0x10D39, 0x10D40, 0x10D65, 0x10D69, 0x10D6D, 0x10D6F, 0x10D85, + 0x10E80, 0x10EA9, 0x10EAB, 0x10EAC, 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC7, + 0x10EFA, 0x10F1C, 0x10F27, 0x10F27, 0x10F30, 0x10F50, 0x10F70, 0x10F85, + 0x10FB0, 0x10FC4, 0x10FE0, 0x10FF6, 0x11000, 0x11046, 0x11066, 0x11075, + 0x1107F, 0x110BA, 0x110C2, 0x110C2, 0x110D0, 0x110E8, 0x110F0, 0x110F9, + 0x11100, 0x11134, 0x11136, 0x1113F, 0x11144, 0x11147, 0x11150, 0x11173, + 0x11176, 0x11176, 0x11180, 0x111C4, 0x111C9, 0x111CC, 0x111CE, 0x111DA, + 0x111DC, 0x111DC, 0x11200, 0x11211, 0x11213, 0x11237, 0x1123E, 0x11241, + 0x11280, 0x11286, 0x11288, 0x11288, 0x1128A, 0x1128D, 0x1128F, 0x1129D, + 0x1129F, 0x112A8, 0x112B0, 0x112EA, 0x112F0, 0x112F9, 0x11300, 0x11303, + 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, 0x1132A, 0x11330, + 0x11332, 0x11333, 0x11335, 0x11339, 0x1133B, 0x11344, 0x11347, 0x11348, + 0x1134B, 0x1134D, 0x11350, 0x11350, 0x11357, 0x11357, 0x1135D, 0x11363, + 0x11366, 0x1136C, 0x11370, 0x11374, 0x11380, 0x11389, 0x1138B, 0x1138B, + 0x1138E, 0x1138E, 0x11390, 0x113B5, 0x113B7, 0x113C0, 0x113C2, 0x113C2, + 0x113C5, 0x113C5, 0x113C7, 0x113CA, 0x113CC, 0x113D3, 0x113E1, 0x113E2, + 0x11400, 0x1144A, 0x11450, 0x11459, 0x1145E, 0x11461, 0x11480, 0x114C5, + 0x114C7, 0x114C7, 0x114D0, 0x114D9, 0x11580, 0x115B5, 0x115B8, 0x115C0, + 0x115D8, 0x115DD, 0x11600, 0x11640, 0x11644, 0x11644, 0x11650, 0x11659, + 0x11680, 0x116B8, 0x116C0, 0x116C9, 0x116D0, 0x116E3, 0x11700, 0x1171A, + 0x1171D, 0x1172B, 0x11730, 0x11739, 0x11740, 0x11746, 0x11800, 0x1183A, + 0x118A0, 0x118E9, 0x118FF, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, + 0x11915, 0x11916, 0x11918, 0x11935, 0x11937, 0x11938, 0x1193B, 0x11943, + 0x11950, 0x11959, 0x119A0, 0x119A7, 0x119AA, 0x119D7, 0x119DA, 0x119E1, + 0x119E3, 0x119E4, 0x11A00, 0x11A3E, 0x11A47, 0x11A47, 0x11A50, 0x11A99, + 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, 0x11B60, 0x11B67, 0x11BC0, 0x11BE0, + 0x11BF0, 0x11BF9, 0x11C00, 0x11C08, 0x11C0A, 0x11C36, 0x11C38, 0x11C40, + 0x11C50, 0x11C59, 0x11C72, 0x11C8F, 0x11C92, 0x11CA7, 0x11CA9, 0x11CB6, + 0x11D00, 0x11D06, 0x11D08, 0x11D09, 0x11D0B, 0x11D36, 0x11D3A, 0x11D3A, + 0x11D3C, 0x11D3D, 0x11D3F, 0x11D47, 0x11D50, 0x11D59, 0x11D60, 0x11D65, + 0x11D67, 0x11D68, 0x11D6A, 0x11D8E, 0x11D90, 0x11D91, 0x11D93, 0x11D98, + 0x11DA0, 0x11DA9, 0x11DB0, 0x11DDB, 0x11DE0, 0x11DE9, 0x11EE0, 0x11EF6, + 0x11F00, 0x11F10, 0x11F12, 0x11F3A, 0x11F3E, 0x11F42, 0x11F50, 0x11F5A, + 0x11FB0, 0x11FB0, 0x12000, 0x12399, 0x12400, 0x1246E, 0x12480, 0x12543, + 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13440, 0x13455, 0x13460, 0x143FA, + 0x14400, 0x14646, 0x16100, 0x16139, 0x16800, 0x16A38, 0x16A40, 0x16A5E, + 0x16A60, 0x16A69, 0x16A70, 0x16ABE, 0x16AC0, 0x16AC9, 0x16AD0, 0x16AED, + 0x16AF0, 0x16AF4, 0x16B00, 0x16B36, 0x16B40, 0x16B43, 0x16B50, 0x16B59, + 0x16B63, 0x16B77, 0x16B7D, 0x16B8F, 0x16D40, 0x16D6C, 0x16D70, 0x16D79, + 0x16E40, 0x16E7F, 0x16EA0, 0x16EB8, 0x16EBB, 0x16ED3, 0x16F00, 0x16F4A, + 0x16F4F, 0x16F87, 0x16F8F, 0x16F9F, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE4, + 0x16FF0, 0x16FF6, 0x17000, 0x18CD5, 0x18CFF, 0x18D1E, 0x18D80, 0x18DF2, + 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B122, + 0x1B132, 0x1B132, 0x1B150, 0x1B152, 0x1B155, 0x1B155, 0x1B164, 0x1B167, + 0x1B170, 0x1B2FB, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, 0x1BC88, + 0x1BC90, 0x1BC99, 0x1BC9D, 0x1BC9E, 0x1CCF0, 0x1CCF9, 0x1CF00, 0x1CF2D, + 0x1CF30, 0x1CF46, 0x1D165, 0x1D169, 0x1D16D, 0x1D172, 0x1D17B, 0x1D182, + 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, 0x1D242, 0x1D244, 0x1D400, 0x1D454, + 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, + 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, + 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, + 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, + 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, 0x1D6A8, 0x1D6C0, 0x1D6C2, 0x1D6DA, + 0x1D6DC, 0x1D6FA, 0x1D6FC, 0x1D714, 0x1D716, 0x1D734, 0x1D736, 0x1D74E, + 0x1D750, 0x1D76E, 0x1D770, 0x1D788, 0x1D78A, 0x1D7A8, 0x1D7AA, 0x1D7C2, + 0x1D7C4, 0x1D7CB, 0x1D7CE, 0x1D7FF, 0x1DA00, 0x1DA36, 0x1DA3B, 0x1DA6C, + 0x1DA75, 0x1DA75, 0x1DA84, 0x1DA84, 0x1DA9B, 0x1DA9F, 0x1DAA1, 0x1DAAF, + 0x1DF00, 0x1DF1E, 0x1DF25, 0x1DF2A, 0x1E000, 0x1E006, 0x1E008, 0x1E018, + 0x1E01B, 0x1E021, 0x1E023, 0x1E024, 0x1E026, 0x1E02A, 0x1E030, 0x1E06D, + 0x1E08F, 0x1E08F, 0x1E100, 0x1E12C, 0x1E130, 0x1E13D, 0x1E140, 0x1E149, + 0x1E14E, 0x1E14E, 0x1E290, 0x1E2AE, 0x1E2C0, 0x1E2F9, 0x1E4D0, 0x1E4F9, + 0x1E5D0, 0x1E5FA, 0x1E6C0, 0x1E6DE, 0x1E6E0, 0x1E6F5, 0x1E6FE, 0x1E6FF, + 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, 0x1E7FE, + 0x1E800, 0x1E8C4, 0x1E8D0, 0x1E8D6, 0x1E900, 0x1E94B, 0x1E950, 0x1E959, + 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, + 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, + 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, + 0x1EE4B, 0x1EE4B, 0x1EE4D, 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, + 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, + 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, + 0x1EE6C, 0x1EE72, 0x1EE74, 0x1EE77, 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, + 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, + 0x1EEAB, 0x1EEBB, 0x1FBF0, 0x1FBF9, 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, + 0x2B820, 0x2CEAD, 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, + 0x30000, 0x3134A, 0x31350, 0x33479, 0xE0100, 0xE01EF, + // #75 (11991+684): bp=ID_Start:IDS + 0x0041, 0x005A, 0x0061, 0x007A, 0x00AA, 0x00AA, 0x00B5, 0x00B5, + 0x00BA, 0x00BA, 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x02C1, + 0x02C6, 0x02D1, 0x02E0, 0x02E4, 0x02EC, 0x02EC, 0x02EE, 0x02EE, + 0x0370, 0x0374, 0x0376, 0x0377, 0x037A, 0x037D, 0x037F, 0x037F, + 0x0386, 0x0386, 0x0388, 0x038A, 0x038C, 0x038C, 0x038E, 0x03A1, + 0x03A3, 0x03F5, 0x03F7, 0x0481, 0x048A, 0x052F, 0x0531, 0x0556, + 0x0559, 0x0559, 0x0560, 0x0588, 0x05D0, 0x05EA, 0x05EF, 0x05F2, + 0x0620, 0x064A, 0x066E, 0x066F, 0x0671, 0x06D3, 0x06D5, 0x06D5, + 0x06E5, 0x06E6, 0x06EE, 0x06EF, 0x06FA, 0x06FC, 0x06FF, 0x06FF, + 0x0710, 0x0710, 0x0712, 0x072F, 0x074D, 0x07A5, 0x07B1, 0x07B1, + 0x07CA, 0x07EA, 0x07F4, 0x07F5, 0x07FA, 0x07FA, 0x0800, 0x0815, + 0x081A, 0x081A, 0x0824, 0x0824, 0x0828, 0x0828, 0x0840, 0x0858, + 0x0860, 0x086A, 0x0870, 0x0887, 0x0889, 0x088F, 0x08A0, 0x08C9, + 0x0904, 0x0939, 0x093D, 0x093D, 0x0950, 0x0950, 0x0958, 0x0961, + 0x0971, 0x0980, 0x0985, 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, + 0x09AA, 0x09B0, 0x09B2, 0x09B2, 0x09B6, 0x09B9, 0x09BD, 0x09BD, + 0x09CE, 0x09CE, 0x09DC, 0x09DD, 0x09DF, 0x09E1, 0x09F0, 0x09F1, + 0x09FC, 0x09FC, 0x0A05, 0x0A0A, 0x0A0F, 0x0A10, 0x0A13, 0x0A28, + 0x0A2A, 0x0A30, 0x0A32, 0x0A33, 0x0A35, 0x0A36, 0x0A38, 0x0A39, + 0x0A59, 0x0A5C, 0x0A5E, 0x0A5E, 0x0A72, 0x0A74, 0x0A85, 0x0A8D, + 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, + 0x0AB5, 0x0AB9, 0x0ABD, 0x0ABD, 0x0AD0, 0x0AD0, 0x0AE0, 0x0AE1, + 0x0AF9, 0x0AF9, 0x0B05, 0x0B0C, 0x0B0F, 0x0B10, 0x0B13, 0x0B28, + 0x0B2A, 0x0B30, 0x0B32, 0x0B33, 0x0B35, 0x0B39, 0x0B3D, 0x0B3D, + 0x0B5C, 0x0B5D, 0x0B5F, 0x0B61, 0x0B71, 0x0B71, 0x0B83, 0x0B83, + 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, 0x0B92, 0x0B95, 0x0B99, 0x0B9A, + 0x0B9C, 0x0B9C, 0x0B9E, 0x0B9F, 0x0BA3, 0x0BA4, 0x0BA8, 0x0BAA, + 0x0BAE, 0x0BB9, 0x0BD0, 0x0BD0, 0x0C05, 0x0C0C, 0x0C0E, 0x0C10, + 0x0C12, 0x0C28, 0x0C2A, 0x0C39, 0x0C3D, 0x0C3D, 0x0C58, 0x0C5A, + 0x0C5C, 0x0C5D, 0x0C60, 0x0C61, 0x0C80, 0x0C80, 0x0C85, 0x0C8C, + 0x0C8E, 0x0C90, 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, 0x0CB5, 0x0CB9, + 0x0CBD, 0x0CBD, 0x0CDC, 0x0CDE, 0x0CE0, 0x0CE1, 0x0CF1, 0x0CF2, + 0x0D04, 0x0D0C, 0x0D0E, 0x0D10, 0x0D12, 0x0D3A, 0x0D3D, 0x0D3D, + 0x0D4E, 0x0D4E, 0x0D54, 0x0D56, 0x0D5F, 0x0D61, 0x0D7A, 0x0D7F, + 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, 0x0DB3, 0x0DBB, 0x0DBD, 0x0DBD, + 0x0DC0, 0x0DC6, 0x0E01, 0x0E30, 0x0E32, 0x0E33, 0x0E40, 0x0E46, + 0x0E81, 0x0E82, 0x0E84, 0x0E84, 0x0E86, 0x0E8A, 0x0E8C, 0x0EA3, + 0x0EA5, 0x0EA5, 0x0EA7, 0x0EB0, 0x0EB2, 0x0EB3, 0x0EBD, 0x0EBD, + 0x0EC0, 0x0EC4, 0x0EC6, 0x0EC6, 0x0EDC, 0x0EDF, 0x0F00, 0x0F00, + 0x0F40, 0x0F47, 0x0F49, 0x0F6C, 0x0F88, 0x0F8C, 0x1000, 0x102A, + 0x103F, 0x103F, 0x1050, 0x1055, 0x105A, 0x105D, 0x1061, 0x1061, + 0x1065, 0x1066, 0x106E, 0x1070, 0x1075, 0x1081, 0x108E, 0x108E, + 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, + 0x10FC, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, + 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, + 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, + 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, 0x1318, 0x135A, + 0x1380, 0x138F, 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0x1401, 0x166C, + 0x166F, 0x167F, 0x1681, 0x169A, 0x16A0, 0x16EA, 0x16EE, 0x16F8, + 0x1700, 0x1711, 0x171F, 0x1731, 0x1740, 0x1751, 0x1760, 0x176C, + 0x176E, 0x1770, 0x1780, 0x17B3, 0x17D7, 0x17D7, 0x17DC, 0x17DC, + 0x1820, 0x1878, 0x1880, 0x18A8, 0x18AA, 0x18AA, 0x18B0, 0x18F5, + 0x1900, 0x191E, 0x1950, 0x196D, 0x1970, 0x1974, 0x1980, 0x19AB, + 0x19B0, 0x19C9, 0x1A00, 0x1A16, 0x1A20, 0x1A54, 0x1AA7, 0x1AA7, + 0x1B05, 0x1B33, 0x1B45, 0x1B4C, 0x1B83, 0x1BA0, 0x1BAE, 0x1BAF, + 0x1BBA, 0x1BE5, 0x1C00, 0x1C23, 0x1C4D, 0x1C4F, 0x1C5A, 0x1C7D, + 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1CE9, 0x1CEC, + 0x1CEE, 0x1CF3, 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, + 0x1E00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, 0x1F48, 0x1F4D, + 0x1F50, 0x1F57, 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, + 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, + 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, + 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFC, 0x2071, 0x2071, + 0x207F, 0x207F, 0x2090, 0x209C, 0x2102, 0x2102, 0x2107, 0x2107, + 0x210A, 0x2113, 0x2115, 0x2115, 0x2118, 0x211D, 0x2124, 0x2124, + 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x2139, 0x213C, 0x213F, + 0x2145, 0x2149, 0x214E, 0x214E, 0x2160, 0x2188, 0x2C00, 0x2CE4, + 0x2CEB, 0x2CEE, 0x2CF2, 0x2CF3, 0x2D00, 0x2D25, 0x2D27, 0x2D27, + 0x2D2D, 0x2D2D, 0x2D30, 0x2D67, 0x2D6F, 0x2D6F, 0x2D80, 0x2D96, + 0x2DA0, 0x2DA6, 0x2DA8, 0x2DAE, 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, + 0x2DC0, 0x2DC6, 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, + 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303C, + 0x3041, 0x3096, 0x309B, 0x309F, 0x30A1, 0x30FA, 0x30FC, 0x30FF, + 0x3105, 0x312F, 0x3131, 0x318E, 0x31A0, 0x31BF, 0x31F0, 0x31FF, + 0x3400, 0x4DBF, 0x4E00, 0xA48C, 0xA4D0, 0xA4FD, 0xA500, 0xA60C, + 0xA610, 0xA61F, 0xA62A, 0xA62B, 0xA640, 0xA66E, 0xA67F, 0xA69D, + 0xA6A0, 0xA6EF, 0xA717, 0xA71F, 0xA722, 0xA788, 0xA78B, 0xA7DC, + 0xA7F1, 0xA801, 0xA803, 0xA805, 0xA807, 0xA80A, 0xA80C, 0xA822, + 0xA840, 0xA873, 0xA882, 0xA8B3, 0xA8F2, 0xA8F7, 0xA8FB, 0xA8FB, + 0xA8FD, 0xA8FE, 0xA90A, 0xA925, 0xA930, 0xA946, 0xA960, 0xA97C, + 0xA984, 0xA9B2, 0xA9CF, 0xA9CF, 0xA9E0, 0xA9E4, 0xA9E6, 0xA9EF, + 0xA9FA, 0xA9FE, 0xAA00, 0xAA28, 0xAA40, 0xAA42, 0xAA44, 0xAA4B, + 0xAA60, 0xAA76, 0xAA7A, 0xAA7A, 0xAA7E, 0xAAAF, 0xAAB1, 0xAAB1, + 0xAAB5, 0xAAB6, 0xAAB9, 0xAABD, 0xAAC0, 0xAAC0, 0xAAC2, 0xAAC2, + 0xAADB, 0xAADD, 0xAAE0, 0xAAEA, 0xAAF2, 0xAAF4, 0xAB01, 0xAB06, + 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, + 0xAB30, 0xAB5A, 0xAB5C, 0xAB69, 0xAB70, 0xABE2, 0xAC00, 0xD7A3, + 0xD7B0, 0xD7C6, 0xD7CB, 0xD7FB, 0xF900, 0xFA6D, 0xFA70, 0xFAD9, + 0xFB00, 0xFB06, 0xFB13, 0xFB17, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB28, + 0xFB2A, 0xFB36, 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, + 0xFB43, 0xFB44, 0xFB46, 0xFBB1, 0xFBD3, 0xFD3D, 0xFD50, 0xFD8F, + 0xFD92, 0xFDC7, 0xFDF0, 0xFDFB, 0xFE70, 0xFE74, 0xFE76, 0xFEFC, + 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, 0xFF66, 0xFFBE, 0xFFC2, 0xFFC7, + 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0x10000, 0x1000B, + 0x1000D, 0x10026, 0x10028, 0x1003A, 0x1003C, 0x1003D, 0x1003F, 0x1004D, + 0x10050, 0x1005D, 0x10080, 0x100FA, 0x10140, 0x10174, 0x10280, 0x1029C, + 0x102A0, 0x102D0, 0x10300, 0x1031F, 0x1032D, 0x1034A, 0x10350, 0x10375, + 0x10380, 0x1039D, 0x103A0, 0x103C3, 0x103C8, 0x103CF, 0x103D1, 0x103D5, + 0x10400, 0x1049D, 0x104B0, 0x104D3, 0x104D8, 0x104FB, 0x10500, 0x10527, + 0x10530, 0x10563, 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, + 0x10594, 0x10595, 0x10597, 0x105A1, 0x105A3, 0x105B1, 0x105B3, 0x105B9, + 0x105BB, 0x105BC, 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, + 0x10760, 0x10767, 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, + 0x10800, 0x10805, 0x10808, 0x10808, 0x1080A, 0x10835, 0x10837, 0x10838, + 0x1083C, 0x1083C, 0x1083F, 0x10855, 0x10860, 0x10876, 0x10880, 0x1089E, + 0x108E0, 0x108F2, 0x108F4, 0x108F5, 0x10900, 0x10915, 0x10920, 0x10939, + 0x10940, 0x10959, 0x10980, 0x109B7, 0x109BE, 0x109BF, 0x10A00, 0x10A00, + 0x10A10, 0x10A13, 0x10A15, 0x10A17, 0x10A19, 0x10A35, 0x10A60, 0x10A7C, + 0x10A80, 0x10A9C, 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE4, 0x10B00, 0x10B35, + 0x10B40, 0x10B55, 0x10B60, 0x10B72, 0x10B80, 0x10B91, 0x10C00, 0x10C48, + 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, 0x10D00, 0x10D23, 0x10D4A, 0x10D65, + 0x10D6F, 0x10D85, 0x10E80, 0x10EA9, 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC7, + 0x10F00, 0x10F1C, 0x10F27, 0x10F27, 0x10F30, 0x10F45, 0x10F70, 0x10F81, + 0x10FB0, 0x10FC4, 0x10FE0, 0x10FF6, 0x11003, 0x11037, 0x11071, 0x11072, + 0x11075, 0x11075, 0x11083, 0x110AF, 0x110D0, 0x110E8, 0x11103, 0x11126, + 0x11144, 0x11144, 0x11147, 0x11147, 0x11150, 0x11172, 0x11176, 0x11176, + 0x11183, 0x111B2, 0x111C1, 0x111C4, 0x111DA, 0x111DA, 0x111DC, 0x111DC, + 0x11200, 0x11211, 0x11213, 0x1122B, 0x1123F, 0x11240, 0x11280, 0x11286, + 0x11288, 0x11288, 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A8, + 0x112B0, 0x112DE, 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, + 0x1132A, 0x11330, 0x11332, 0x11333, 0x11335, 0x11339, 0x1133D, 0x1133D, + 0x11350, 0x11350, 0x1135D, 0x11361, 0x11380, 0x11389, 0x1138B, 0x1138B, + 0x1138E, 0x1138E, 0x11390, 0x113B5, 0x113B7, 0x113B7, 0x113D1, 0x113D1, + 0x113D3, 0x113D3, 0x11400, 0x11434, 0x11447, 0x1144A, 0x1145F, 0x11461, + 0x11480, 0x114AF, 0x114C4, 0x114C5, 0x114C7, 0x114C7, 0x11580, 0x115AE, + 0x115D8, 0x115DB, 0x11600, 0x1162F, 0x11644, 0x11644, 0x11680, 0x116AA, + 0x116B8, 0x116B8, 0x11700, 0x1171A, 0x11740, 0x11746, 0x11800, 0x1182B, + 0x118A0, 0x118DF, 0x118FF, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, + 0x11915, 0x11916, 0x11918, 0x1192F, 0x1193F, 0x1193F, 0x11941, 0x11941, + 0x119A0, 0x119A7, 0x119AA, 0x119D0, 0x119E1, 0x119E1, 0x119E3, 0x119E3, + 0x11A00, 0x11A00, 0x11A0B, 0x11A32, 0x11A3A, 0x11A3A, 0x11A50, 0x11A50, + 0x11A5C, 0x11A89, 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, 0x11BC0, 0x11BE0, + 0x11C00, 0x11C08, 0x11C0A, 0x11C2E, 0x11C40, 0x11C40, 0x11C72, 0x11C8F, + 0x11D00, 0x11D06, 0x11D08, 0x11D09, 0x11D0B, 0x11D30, 0x11D46, 0x11D46, + 0x11D60, 0x11D65, 0x11D67, 0x11D68, 0x11D6A, 0x11D89, 0x11D98, 0x11D98, + 0x11DB0, 0x11DDB, 0x11EE0, 0x11EF2, 0x11F02, 0x11F02, 0x11F04, 0x11F10, + 0x11F12, 0x11F33, 0x11FB0, 0x11FB0, 0x12000, 0x12399, 0x12400, 0x1246E, + 0x12480, 0x12543, 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13441, 0x13446, + 0x13460, 0x143FA, 0x14400, 0x14646, 0x16100, 0x1611D, 0x16800, 0x16A38, + 0x16A40, 0x16A5E, 0x16A70, 0x16ABE, 0x16AD0, 0x16AED, 0x16B00, 0x16B2F, + 0x16B40, 0x16B43, 0x16B63, 0x16B77, 0x16B7D, 0x16B8F, 0x16D40, 0x16D6C, + 0x16E40, 0x16E7F, 0x16EA0, 0x16EB8, 0x16EBB, 0x16ED3, 0x16F00, 0x16F4A, + 0x16F50, 0x16F50, 0x16F93, 0x16F9F, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE3, + 0x16FF2, 0x16FF6, 0x17000, 0x18CD5, 0x18CFF, 0x18D1E, 0x18D80, 0x18DF2, + 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B122, + 0x1B132, 0x1B132, 0x1B150, 0x1B152, 0x1B155, 0x1B155, 0x1B164, 0x1B167, + 0x1B170, 0x1B2FB, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, 0x1BC88, + 0x1BC90, 0x1BC99, 0x1D400, 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, + 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, + 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, + 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, + 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, + 0x1D6A8, 0x1D6C0, 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6FA, 0x1D6FC, 0x1D714, + 0x1D716, 0x1D734, 0x1D736, 0x1D74E, 0x1D750, 0x1D76E, 0x1D770, 0x1D788, + 0x1D78A, 0x1D7A8, 0x1D7AA, 0x1D7C2, 0x1D7C4, 0x1D7CB, 0x1DF00, 0x1DF1E, + 0x1DF25, 0x1DF2A, 0x1E030, 0x1E06D, 0x1E100, 0x1E12C, 0x1E137, 0x1E13D, + 0x1E14E, 0x1E14E, 0x1E290, 0x1E2AD, 0x1E2C0, 0x1E2EB, 0x1E4D0, 0x1E4EB, + 0x1E5D0, 0x1E5ED, 0x1E5F0, 0x1E5F0, 0x1E6C0, 0x1E6DE, 0x1E6E0, 0x1E6E2, + 0x1E6E4, 0x1E6E5, 0x1E6E7, 0x1E6ED, 0x1E6F0, 0x1E6F4, 0x1E6FE, 0x1E6FF, + 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, 0x1E7FE, + 0x1E800, 0x1E8C4, 0x1E900, 0x1E943, 0x1E94B, 0x1E94B, 0x1EE00, 0x1EE03, + 0x1EE05, 0x1EE1F, 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, + 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, 0x1EE3B, 0x1EE3B, + 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, + 0x1EE4D, 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, + 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, + 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, + 0x1EE74, 0x1EE77, 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, + 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, + 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, 0x2CEB0, 0x2EBE0, + 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, 0x31350, 0x33479, + // #76 (12675+21): bp=Ideographic:Ideo + 0x3006, 0x3007, 0x3021, 0x3029, 0x3038, 0x303A, 0x3400, 0x4DBF, + 0x4E00, 0x9FFF, 0xF900, 0xFA6D, 0xFA70, 0xFAD9, 0x16FE4, 0x16FE4, + 0x16FF2, 0x16FF6, 0x17000, 0x18CD5, 0x18CFF, 0x18D1E, 0x18D80, 0x18DF2, + 0x1B170, 0x1B2FB, 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, + 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, + 0x31350, 0x33479, + // #77 (12696+1): bp=Join_Control:Join_C + 0x200C, 0x200D, + // #78 (12697+7): bp=Logical_Order_Exception:LOE + 0x0E40, 0x0E44, 0x0EC0, 0x0EC4, 0x19B5, 0x19B7, 0x19BA, 0x19BA, + 0xAAB5, 0xAAB6, 0xAAB9, 0xAAB9, 0xAABB, 0xAABC, + // #79 (12704+677): bp=Lowercase:Lower + 0x0061, 0x007A, 0x00AA, 0x00AA, 0x00B5, 0x00B5, 0x00BA, 0x00BA, + 0x00DF, 0x00F6, 0x00F8, 0x00FF, 0x0101, 0x0101, 0x0103, 0x0103, + 0x0105, 0x0105, 0x0107, 0x0107, 0x0109, 0x0109, 0x010B, 0x010B, + 0x010D, 0x010D, 0x010F, 0x010F, 0x0111, 0x0111, 0x0113, 0x0113, + 0x0115, 0x0115, 0x0117, 0x0117, 0x0119, 0x0119, 0x011B, 0x011B, + 0x011D, 0x011D, 0x011F, 0x011F, 0x0121, 0x0121, 0x0123, 0x0123, + 0x0125, 0x0125, 0x0127, 0x0127, 0x0129, 0x0129, 0x012B, 0x012B, + 0x012D, 0x012D, 0x012F, 0x012F, 0x0131, 0x0131, 0x0133, 0x0133, + 0x0135, 0x0135, 0x0137, 0x0138, 0x013A, 0x013A, 0x013C, 0x013C, + 0x013E, 0x013E, 0x0140, 0x0140, 0x0142, 0x0142, 0x0144, 0x0144, + 0x0146, 0x0146, 0x0148, 0x0149, 0x014B, 0x014B, 0x014D, 0x014D, + 0x014F, 0x014F, 0x0151, 0x0151, 0x0153, 0x0153, 0x0155, 0x0155, + 0x0157, 0x0157, 0x0159, 0x0159, 0x015B, 0x015B, 0x015D, 0x015D, + 0x015F, 0x015F, 0x0161, 0x0161, 0x0163, 0x0163, 0x0165, 0x0165, + 0x0167, 0x0167, 0x0169, 0x0169, 0x016B, 0x016B, 0x016D, 0x016D, + 0x016F, 0x016F, 0x0171, 0x0171, 0x0173, 0x0173, 0x0175, 0x0175, + 0x0177, 0x0177, 0x017A, 0x017A, 0x017C, 0x017C, 0x017E, 0x0180, + 0x0183, 0x0183, 0x0185, 0x0185, 0x0188, 0x0188, 0x018C, 0x018D, + 0x0192, 0x0192, 0x0195, 0x0195, 0x0199, 0x019B, 0x019E, 0x019E, + 0x01A1, 0x01A1, 0x01A3, 0x01A3, 0x01A5, 0x01A5, 0x01A8, 0x01A8, + 0x01AA, 0x01AB, 0x01AD, 0x01AD, 0x01B0, 0x01B0, 0x01B4, 0x01B4, + 0x01B6, 0x01B6, 0x01B9, 0x01BA, 0x01BD, 0x01BF, 0x01C6, 0x01C6, + 0x01C9, 0x01C9, 0x01CC, 0x01CC, 0x01CE, 0x01CE, 0x01D0, 0x01D0, + 0x01D2, 0x01D2, 0x01D4, 0x01D4, 0x01D6, 0x01D6, 0x01D8, 0x01D8, + 0x01DA, 0x01DA, 0x01DC, 0x01DD, 0x01DF, 0x01DF, 0x01E1, 0x01E1, + 0x01E3, 0x01E3, 0x01E5, 0x01E5, 0x01E7, 0x01E7, 0x01E9, 0x01E9, + 0x01EB, 0x01EB, 0x01ED, 0x01ED, 0x01EF, 0x01F0, 0x01F3, 0x01F3, + 0x01F5, 0x01F5, 0x01F9, 0x01F9, 0x01FB, 0x01FB, 0x01FD, 0x01FD, + 0x01FF, 0x01FF, 0x0201, 0x0201, 0x0203, 0x0203, 0x0205, 0x0205, + 0x0207, 0x0207, 0x0209, 0x0209, 0x020B, 0x020B, 0x020D, 0x020D, + 0x020F, 0x020F, 0x0211, 0x0211, 0x0213, 0x0213, 0x0215, 0x0215, + 0x0217, 0x0217, 0x0219, 0x0219, 0x021B, 0x021B, 0x021D, 0x021D, + 0x021F, 0x021F, 0x0221, 0x0221, 0x0223, 0x0223, 0x0225, 0x0225, + 0x0227, 0x0227, 0x0229, 0x0229, 0x022B, 0x022B, 0x022D, 0x022D, + 0x022F, 0x022F, 0x0231, 0x0231, 0x0233, 0x0239, 0x023C, 0x023C, + 0x023F, 0x0240, 0x0242, 0x0242, 0x0247, 0x0247, 0x0249, 0x0249, + 0x024B, 0x024B, 0x024D, 0x024D, 0x024F, 0x0293, 0x0296, 0x02B8, + 0x02C0, 0x02C1, 0x02E0, 0x02E4, 0x0345, 0x0345, 0x0371, 0x0371, + 0x0373, 0x0373, 0x0377, 0x0377, 0x037A, 0x037D, 0x0390, 0x0390, + 0x03AC, 0x03CE, 0x03D0, 0x03D1, 0x03D5, 0x03D7, 0x03D9, 0x03D9, + 0x03DB, 0x03DB, 0x03DD, 0x03DD, 0x03DF, 0x03DF, 0x03E1, 0x03E1, + 0x03E3, 0x03E3, 0x03E5, 0x03E5, 0x03E7, 0x03E7, 0x03E9, 0x03E9, + 0x03EB, 0x03EB, 0x03ED, 0x03ED, 0x03EF, 0x03F3, 0x03F5, 0x03F5, + 0x03F8, 0x03F8, 0x03FB, 0x03FC, 0x0430, 0x045F, 0x0461, 0x0461, + 0x0463, 0x0463, 0x0465, 0x0465, 0x0467, 0x0467, 0x0469, 0x0469, + 0x046B, 0x046B, 0x046D, 0x046D, 0x046F, 0x046F, 0x0471, 0x0471, + 0x0473, 0x0473, 0x0475, 0x0475, 0x0477, 0x0477, 0x0479, 0x0479, + 0x047B, 0x047B, 0x047D, 0x047D, 0x047F, 0x047F, 0x0481, 0x0481, + 0x048B, 0x048B, 0x048D, 0x048D, 0x048F, 0x048F, 0x0491, 0x0491, + 0x0493, 0x0493, 0x0495, 0x0495, 0x0497, 0x0497, 0x0499, 0x0499, + 0x049B, 0x049B, 0x049D, 0x049D, 0x049F, 0x049F, 0x04A1, 0x04A1, + 0x04A3, 0x04A3, 0x04A5, 0x04A5, 0x04A7, 0x04A7, 0x04A9, 0x04A9, + 0x04AB, 0x04AB, 0x04AD, 0x04AD, 0x04AF, 0x04AF, 0x04B1, 0x04B1, + 0x04B3, 0x04B3, 0x04B5, 0x04B5, 0x04B7, 0x04B7, 0x04B9, 0x04B9, + 0x04BB, 0x04BB, 0x04BD, 0x04BD, 0x04BF, 0x04BF, 0x04C2, 0x04C2, + 0x04C4, 0x04C4, 0x04C6, 0x04C6, 0x04C8, 0x04C8, 0x04CA, 0x04CA, + 0x04CC, 0x04CC, 0x04CE, 0x04CF, 0x04D1, 0x04D1, 0x04D3, 0x04D3, + 0x04D5, 0x04D5, 0x04D7, 0x04D7, 0x04D9, 0x04D9, 0x04DB, 0x04DB, + 0x04DD, 0x04DD, 0x04DF, 0x04DF, 0x04E1, 0x04E1, 0x04E3, 0x04E3, + 0x04E5, 0x04E5, 0x04E7, 0x04E7, 0x04E9, 0x04E9, 0x04EB, 0x04EB, + 0x04ED, 0x04ED, 0x04EF, 0x04EF, 0x04F1, 0x04F1, 0x04F3, 0x04F3, + 0x04F5, 0x04F5, 0x04F7, 0x04F7, 0x04F9, 0x04F9, 0x04FB, 0x04FB, + 0x04FD, 0x04FD, 0x04FF, 0x04FF, 0x0501, 0x0501, 0x0503, 0x0503, + 0x0505, 0x0505, 0x0507, 0x0507, 0x0509, 0x0509, 0x050B, 0x050B, + 0x050D, 0x050D, 0x050F, 0x050F, 0x0511, 0x0511, 0x0513, 0x0513, + 0x0515, 0x0515, 0x0517, 0x0517, 0x0519, 0x0519, 0x051B, 0x051B, + 0x051D, 0x051D, 0x051F, 0x051F, 0x0521, 0x0521, 0x0523, 0x0523, + 0x0525, 0x0525, 0x0527, 0x0527, 0x0529, 0x0529, 0x052B, 0x052B, + 0x052D, 0x052D, 0x052F, 0x052F, 0x0560, 0x0588, 0x10D0, 0x10FA, + 0x10FC, 0x10FF, 0x13F8, 0x13FD, 0x1C80, 0x1C88, 0x1C8A, 0x1C8A, + 0x1D00, 0x1DBF, 0x1E01, 0x1E01, 0x1E03, 0x1E03, 0x1E05, 0x1E05, + 0x1E07, 0x1E07, 0x1E09, 0x1E09, 0x1E0B, 0x1E0B, 0x1E0D, 0x1E0D, + 0x1E0F, 0x1E0F, 0x1E11, 0x1E11, 0x1E13, 0x1E13, 0x1E15, 0x1E15, + 0x1E17, 0x1E17, 0x1E19, 0x1E19, 0x1E1B, 0x1E1B, 0x1E1D, 0x1E1D, + 0x1E1F, 0x1E1F, 0x1E21, 0x1E21, 0x1E23, 0x1E23, 0x1E25, 0x1E25, + 0x1E27, 0x1E27, 0x1E29, 0x1E29, 0x1E2B, 0x1E2B, 0x1E2D, 0x1E2D, + 0x1E2F, 0x1E2F, 0x1E31, 0x1E31, 0x1E33, 0x1E33, 0x1E35, 0x1E35, + 0x1E37, 0x1E37, 0x1E39, 0x1E39, 0x1E3B, 0x1E3B, 0x1E3D, 0x1E3D, + 0x1E3F, 0x1E3F, 0x1E41, 0x1E41, 0x1E43, 0x1E43, 0x1E45, 0x1E45, + 0x1E47, 0x1E47, 0x1E49, 0x1E49, 0x1E4B, 0x1E4B, 0x1E4D, 0x1E4D, + 0x1E4F, 0x1E4F, 0x1E51, 0x1E51, 0x1E53, 0x1E53, 0x1E55, 0x1E55, + 0x1E57, 0x1E57, 0x1E59, 0x1E59, 0x1E5B, 0x1E5B, 0x1E5D, 0x1E5D, + 0x1E5F, 0x1E5F, 0x1E61, 0x1E61, 0x1E63, 0x1E63, 0x1E65, 0x1E65, + 0x1E67, 0x1E67, 0x1E69, 0x1E69, 0x1E6B, 0x1E6B, 0x1E6D, 0x1E6D, + 0x1E6F, 0x1E6F, 0x1E71, 0x1E71, 0x1E73, 0x1E73, 0x1E75, 0x1E75, + 0x1E77, 0x1E77, 0x1E79, 0x1E79, 0x1E7B, 0x1E7B, 0x1E7D, 0x1E7D, + 0x1E7F, 0x1E7F, 0x1E81, 0x1E81, 0x1E83, 0x1E83, 0x1E85, 0x1E85, + 0x1E87, 0x1E87, 0x1E89, 0x1E89, 0x1E8B, 0x1E8B, 0x1E8D, 0x1E8D, + 0x1E8F, 0x1E8F, 0x1E91, 0x1E91, 0x1E93, 0x1E93, 0x1E95, 0x1E9D, + 0x1E9F, 0x1E9F, 0x1EA1, 0x1EA1, 0x1EA3, 0x1EA3, 0x1EA5, 0x1EA5, + 0x1EA7, 0x1EA7, 0x1EA9, 0x1EA9, 0x1EAB, 0x1EAB, 0x1EAD, 0x1EAD, + 0x1EAF, 0x1EAF, 0x1EB1, 0x1EB1, 0x1EB3, 0x1EB3, 0x1EB5, 0x1EB5, + 0x1EB7, 0x1EB7, 0x1EB9, 0x1EB9, 0x1EBB, 0x1EBB, 0x1EBD, 0x1EBD, + 0x1EBF, 0x1EBF, 0x1EC1, 0x1EC1, 0x1EC3, 0x1EC3, 0x1EC5, 0x1EC5, + 0x1EC7, 0x1EC7, 0x1EC9, 0x1EC9, 0x1ECB, 0x1ECB, 0x1ECD, 0x1ECD, + 0x1ECF, 0x1ECF, 0x1ED1, 0x1ED1, 0x1ED3, 0x1ED3, 0x1ED5, 0x1ED5, + 0x1ED7, 0x1ED7, 0x1ED9, 0x1ED9, 0x1EDB, 0x1EDB, 0x1EDD, 0x1EDD, + 0x1EDF, 0x1EDF, 0x1EE1, 0x1EE1, 0x1EE3, 0x1EE3, 0x1EE5, 0x1EE5, + 0x1EE7, 0x1EE7, 0x1EE9, 0x1EE9, 0x1EEB, 0x1EEB, 0x1EED, 0x1EED, + 0x1EEF, 0x1EEF, 0x1EF1, 0x1EF1, 0x1EF3, 0x1EF3, 0x1EF5, 0x1EF5, + 0x1EF7, 0x1EF7, 0x1EF9, 0x1EF9, 0x1EFB, 0x1EFB, 0x1EFD, 0x1EFD, + 0x1EFF, 0x1F07, 0x1F10, 0x1F15, 0x1F20, 0x1F27, 0x1F30, 0x1F37, + 0x1F40, 0x1F45, 0x1F50, 0x1F57, 0x1F60, 0x1F67, 0x1F70, 0x1F7D, + 0x1F80, 0x1F87, 0x1F90, 0x1F97, 0x1FA0, 0x1FA7, 0x1FB0, 0x1FB4, + 0x1FB6, 0x1FB7, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FC7, + 0x1FD0, 0x1FD3, 0x1FD6, 0x1FD7, 0x1FE0, 0x1FE7, 0x1FF2, 0x1FF4, + 0x1FF6, 0x1FF7, 0x2071, 0x2071, 0x207F, 0x207F, 0x2090, 0x209C, + 0x210A, 0x210A, 0x210E, 0x210F, 0x2113, 0x2113, 0x212F, 0x212F, + 0x2134, 0x2134, 0x2139, 0x2139, 0x213C, 0x213D, 0x2146, 0x2149, + 0x214E, 0x214E, 0x2170, 0x217F, 0x2184, 0x2184, 0x24D0, 0x24E9, + 0x2C30, 0x2C5F, 0x2C61, 0x2C61, 0x2C65, 0x2C66, 0x2C68, 0x2C68, + 0x2C6A, 0x2C6A, 0x2C6C, 0x2C6C, 0x2C71, 0x2C71, 0x2C73, 0x2C74, + 0x2C76, 0x2C7D, 0x2C81, 0x2C81, 0x2C83, 0x2C83, 0x2C85, 0x2C85, + 0x2C87, 0x2C87, 0x2C89, 0x2C89, 0x2C8B, 0x2C8B, 0x2C8D, 0x2C8D, + 0x2C8F, 0x2C8F, 0x2C91, 0x2C91, 0x2C93, 0x2C93, 0x2C95, 0x2C95, + 0x2C97, 0x2C97, 0x2C99, 0x2C99, 0x2C9B, 0x2C9B, 0x2C9D, 0x2C9D, + 0x2C9F, 0x2C9F, 0x2CA1, 0x2CA1, 0x2CA3, 0x2CA3, 0x2CA5, 0x2CA5, + 0x2CA7, 0x2CA7, 0x2CA9, 0x2CA9, 0x2CAB, 0x2CAB, 0x2CAD, 0x2CAD, + 0x2CAF, 0x2CAF, 0x2CB1, 0x2CB1, 0x2CB3, 0x2CB3, 0x2CB5, 0x2CB5, + 0x2CB7, 0x2CB7, 0x2CB9, 0x2CB9, 0x2CBB, 0x2CBB, 0x2CBD, 0x2CBD, + 0x2CBF, 0x2CBF, 0x2CC1, 0x2CC1, 0x2CC3, 0x2CC3, 0x2CC5, 0x2CC5, + 0x2CC7, 0x2CC7, 0x2CC9, 0x2CC9, 0x2CCB, 0x2CCB, 0x2CCD, 0x2CCD, + 0x2CCF, 0x2CCF, 0x2CD1, 0x2CD1, 0x2CD3, 0x2CD3, 0x2CD5, 0x2CD5, + 0x2CD7, 0x2CD7, 0x2CD9, 0x2CD9, 0x2CDB, 0x2CDB, 0x2CDD, 0x2CDD, + 0x2CDF, 0x2CDF, 0x2CE1, 0x2CE1, 0x2CE3, 0x2CE4, 0x2CEC, 0x2CEC, + 0x2CEE, 0x2CEE, 0x2CF3, 0x2CF3, 0x2D00, 0x2D25, 0x2D27, 0x2D27, + 0x2D2D, 0x2D2D, 0xA641, 0xA641, 0xA643, 0xA643, 0xA645, 0xA645, + 0xA647, 0xA647, 0xA649, 0xA649, 0xA64B, 0xA64B, 0xA64D, 0xA64D, + 0xA64F, 0xA64F, 0xA651, 0xA651, 0xA653, 0xA653, 0xA655, 0xA655, + 0xA657, 0xA657, 0xA659, 0xA659, 0xA65B, 0xA65B, 0xA65D, 0xA65D, + 0xA65F, 0xA65F, 0xA661, 0xA661, 0xA663, 0xA663, 0xA665, 0xA665, + 0xA667, 0xA667, 0xA669, 0xA669, 0xA66B, 0xA66B, 0xA66D, 0xA66D, + 0xA681, 0xA681, 0xA683, 0xA683, 0xA685, 0xA685, 0xA687, 0xA687, + 0xA689, 0xA689, 0xA68B, 0xA68B, 0xA68D, 0xA68D, 0xA68F, 0xA68F, + 0xA691, 0xA691, 0xA693, 0xA693, 0xA695, 0xA695, 0xA697, 0xA697, + 0xA699, 0xA699, 0xA69B, 0xA69D, 0xA723, 0xA723, 0xA725, 0xA725, + 0xA727, 0xA727, 0xA729, 0xA729, 0xA72B, 0xA72B, 0xA72D, 0xA72D, + 0xA72F, 0xA731, 0xA733, 0xA733, 0xA735, 0xA735, 0xA737, 0xA737, + 0xA739, 0xA739, 0xA73B, 0xA73B, 0xA73D, 0xA73D, 0xA73F, 0xA73F, + 0xA741, 0xA741, 0xA743, 0xA743, 0xA745, 0xA745, 0xA747, 0xA747, + 0xA749, 0xA749, 0xA74B, 0xA74B, 0xA74D, 0xA74D, 0xA74F, 0xA74F, + 0xA751, 0xA751, 0xA753, 0xA753, 0xA755, 0xA755, 0xA757, 0xA757, + 0xA759, 0xA759, 0xA75B, 0xA75B, 0xA75D, 0xA75D, 0xA75F, 0xA75F, + 0xA761, 0xA761, 0xA763, 0xA763, 0xA765, 0xA765, 0xA767, 0xA767, + 0xA769, 0xA769, 0xA76B, 0xA76B, 0xA76D, 0xA76D, 0xA76F, 0xA778, + 0xA77A, 0xA77A, 0xA77C, 0xA77C, 0xA77F, 0xA77F, 0xA781, 0xA781, + 0xA783, 0xA783, 0xA785, 0xA785, 0xA787, 0xA787, 0xA78C, 0xA78C, + 0xA78E, 0xA78E, 0xA791, 0xA791, 0xA793, 0xA795, 0xA797, 0xA797, + 0xA799, 0xA799, 0xA79B, 0xA79B, 0xA79D, 0xA79D, 0xA79F, 0xA79F, + 0xA7A1, 0xA7A1, 0xA7A3, 0xA7A3, 0xA7A5, 0xA7A5, 0xA7A7, 0xA7A7, + 0xA7A9, 0xA7A9, 0xA7AF, 0xA7AF, 0xA7B5, 0xA7B5, 0xA7B7, 0xA7B7, + 0xA7B9, 0xA7B9, 0xA7BB, 0xA7BB, 0xA7BD, 0xA7BD, 0xA7BF, 0xA7BF, + 0xA7C1, 0xA7C1, 0xA7C3, 0xA7C3, 0xA7C8, 0xA7C8, 0xA7CA, 0xA7CA, + 0xA7CD, 0xA7CD, 0xA7CF, 0xA7CF, 0xA7D1, 0xA7D1, 0xA7D3, 0xA7D3, + 0xA7D5, 0xA7D5, 0xA7D7, 0xA7D7, 0xA7D9, 0xA7D9, 0xA7DB, 0xA7DB, + 0xA7F1, 0xA7F4, 0xA7F6, 0xA7F6, 0xA7F8, 0xA7FA, 0xAB30, 0xAB5A, + 0xAB5C, 0xAB69, 0xAB70, 0xABBF, 0xFB00, 0xFB06, 0xFB13, 0xFB17, + 0xFF41, 0xFF5A, 0x10428, 0x1044F, 0x104D8, 0x104FB, 0x10597, 0x105A1, + 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x10780, 0x10780, + 0x10783, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10CC0, 0x10CF2, + 0x10D70, 0x10D85, 0x118C0, 0x118DF, 0x16E60, 0x16E7F, 0x16EBB, 0x16ED3, + 0x1D41A, 0x1D433, 0x1D44E, 0x1D454, 0x1D456, 0x1D467, 0x1D482, 0x1D49B, + 0x1D4B6, 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D4CF, + 0x1D4EA, 0x1D503, 0x1D51E, 0x1D537, 0x1D552, 0x1D56B, 0x1D586, 0x1D59F, + 0x1D5BA, 0x1D5D3, 0x1D5EE, 0x1D607, 0x1D622, 0x1D63B, 0x1D656, 0x1D66F, + 0x1D68A, 0x1D6A5, 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6E1, 0x1D6FC, 0x1D714, + 0x1D716, 0x1D71B, 0x1D736, 0x1D74E, 0x1D750, 0x1D755, 0x1D770, 0x1D788, + 0x1D78A, 0x1D78F, 0x1D7AA, 0x1D7C2, 0x1D7C4, 0x1D7C9, 0x1D7CB, 0x1D7CB, + 0x1DF00, 0x1DF09, 0x1DF0B, 0x1DF1E, 0x1DF25, 0x1DF2A, 0x1E030, 0x1E06D, + 0x1E922, 0x1E943, + // #80 (13381+141): bp=Math + 0x002B, 0x002B, 0x003C, 0x003E, 0x005E, 0x005E, 0x007C, 0x007C, + 0x007E, 0x007E, 0x00AC, 0x00AC, 0x00B1, 0x00B1, 0x00D7, 0x00D7, + 0x00F7, 0x00F7, 0x03D0, 0x03D2, 0x03D5, 0x03D5, 0x03F0, 0x03F1, + 0x03F4, 0x03F6, 0x0606, 0x0608, 0x2016, 0x2016, 0x2032, 0x2034, + 0x2040, 0x2040, 0x2044, 0x2044, 0x2052, 0x2052, 0x2061, 0x2064, + 0x207A, 0x207E, 0x208A, 0x208E, 0x20D0, 0x20DC, 0x20E1, 0x20E1, + 0x20E5, 0x20E6, 0x20EB, 0x20EF, 0x2102, 0x2102, 0x2107, 0x2107, + 0x210A, 0x2113, 0x2115, 0x2115, 0x2118, 0x211D, 0x2124, 0x2124, + 0x2128, 0x2129, 0x212C, 0x212D, 0x212F, 0x2131, 0x2133, 0x2138, + 0x213C, 0x2149, 0x214B, 0x214B, 0x2190, 0x21A7, 0x21A9, 0x21AE, + 0x21B0, 0x21B1, 0x21B6, 0x21B7, 0x21BC, 0x21DB, 0x21DD, 0x21DD, + 0x21E4, 0x21E5, 0x21F4, 0x22FF, 0x2308, 0x230B, 0x2320, 0x2321, + 0x237C, 0x237C, 0x239B, 0x23B5, 0x23B7, 0x23B7, 0x23D0, 0x23D0, + 0x23DC, 0x23E2, 0x25A0, 0x25A1, 0x25AE, 0x25B7, 0x25BC, 0x25C1, + 0x25C6, 0x25C7, 0x25CA, 0x25CB, 0x25CF, 0x25D3, 0x25E2, 0x25E2, + 0x25E4, 0x25E4, 0x25E7, 0x25EC, 0x25F8, 0x25FF, 0x2605, 0x2606, + 0x2640, 0x2640, 0x2642, 0x2642, 0x2660, 0x2663, 0x266D, 0x266F, + 0x27C0, 0x27FF, 0x2900, 0x2AFF, 0x2B30, 0x2B44, 0x2B47, 0x2B4C, + 0xFB29, 0xFB29, 0xFE61, 0xFE66, 0xFE68, 0xFE68, 0xFF0B, 0xFF0B, + 0xFF1C, 0xFF1E, 0xFF3C, 0xFF3C, 0xFF3E, 0xFF3E, 0xFF5C, 0xFF5C, + 0xFF5E, 0xFF5E, 0xFFE2, 0xFFE2, 0xFFE9, 0xFFEC, 0x10D8E, 0x10D8F, + 0x1CEF0, 0x1CEF0, 0x1D400, 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, + 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, + 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, + 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, + 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, + 0x1D6A8, 0x1D7CB, 0x1D7CE, 0x1D7FF, 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, + 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, + 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, + 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, 0x1EE4F, + 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, + 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, + 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, 0x1EE74, 0x1EE77, + 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, + 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, 0x1EEF0, 0x1EEF1, + 0x1F8D0, 0x1F8D8, + // #81 (13522+18): bp=Noncharacter_Code_Point:NChar + 0xFDD0, 0xFDEF, 0xFFFE, 0xFFFF, 0x1FFFE, 0x1FFFF, 0x2FFFE, 0x2FFFF, + 0x3FFFE, 0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE, 0x5FFFF, 0x6FFFE, 0x6FFFF, + 0x7FFFE, 0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF, + 0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE, 0xDFFFF, 0xEFFFE, 0xEFFFF, + 0xFFFFE, 0xFFFFF, 0x10FFFE, 0x10FFFF, + // #82 (13540+28): bp=Pattern_Syntax:Pat_Syn + 0x0021, 0x002F, 0x003A, 0x0040, 0x005B, 0x005E, 0x0060, 0x0060, + 0x007B, 0x007E, 0x00A1, 0x00A7, 0x00A9, 0x00A9, 0x00AB, 0x00AC, + 0x00AE, 0x00AE, 0x00B0, 0x00B1, 0x00B6, 0x00B6, 0x00BB, 0x00BB, + 0x00BF, 0x00BF, 0x00D7, 0x00D7, 0x00F7, 0x00F7, 0x2010, 0x2027, + 0x2030, 0x203E, 0x2041, 0x2053, 0x2055, 0x205E, 0x2190, 0x245F, + 0x2500, 0x2775, 0x2794, 0x2BFF, 0x2E00, 0x2E7F, 0x3001, 0x3003, + 0x3008, 0x3020, 0x3030, 0x3030, 0xFD3E, 0xFD3F, 0xFE45, 0xFE46, + // #83 (13568+5): bp=Pattern_White_Space:Pat_WS + 0x0009, 0x000D, 0x0020, 0x0020, 0x0085, 0x0085, 0x200E, 0x200F, + 0x2028, 0x2029, + // #84 (13573+13): bp=Quotation_Mark:QMark + 0x0022, 0x0022, 0x0027, 0x0027, 0x00AB, 0x00AB, 0x00BB, 0x00BB, + 0x2018, 0x201F, 0x2039, 0x203A, 0x2E42, 0x2E42, 0x300C, 0x300F, + 0x301D, 0x301F, 0xFE41, 0xFE44, 0xFF02, 0xFF02, 0xFF07, 0xFF07, + 0xFF62, 0xFF63, + // #85 (13586+3): bp=Radical + 0x2E80, 0x2E99, 0x2E9B, 0x2EF3, 0x2F00, 0x2FD5, + // #86 (13589+1): bp=Regional_Indicator:RI + 0x1F1E6, 0x1F1FF, + // #87 (13590+88): bp=Sentence_Terminal:STerm + 0x0021, 0x0021, 0x002E, 0x002E, 0x003F, 0x003F, 0x0589, 0x0589, + 0x061D, 0x061F, 0x06D4, 0x06D4, 0x0700, 0x0702, 0x07F9, 0x07F9, + 0x0837, 0x0837, 0x0839, 0x0839, 0x083D, 0x083E, 0x0964, 0x0965, + 0x104A, 0x104B, 0x1362, 0x1362, 0x1367, 0x1368, 0x166E, 0x166E, + 0x1735, 0x1736, 0x17D4, 0x17D5, 0x1803, 0x1803, 0x1809, 0x1809, + 0x1944, 0x1945, 0x1AA8, 0x1AAB, 0x1B4E, 0x1B4F, 0x1B5A, 0x1B5B, + 0x1B5E, 0x1B5F, 0x1B7D, 0x1B7F, 0x1C3B, 0x1C3C, 0x1C7E, 0x1C7F, + 0x2024, 0x2024, 0x203C, 0x203D, 0x2047, 0x2049, 0x2CF9, 0x2CFB, + 0x2E2E, 0x2E2E, 0x2E3C, 0x2E3C, 0x2E53, 0x2E54, 0x3002, 0x3002, + 0xA4FF, 0xA4FF, 0xA60E, 0xA60F, 0xA6F3, 0xA6F3, 0xA6F7, 0xA6F7, + 0xA876, 0xA877, 0xA8CE, 0xA8CF, 0xA92F, 0xA92F, 0xA9C8, 0xA9C9, + 0xAA5D, 0xAA5F, 0xAAF0, 0xAAF1, 0xABEB, 0xABEB, 0xFE12, 0xFE12, + 0xFE15, 0xFE16, 0xFE52, 0xFE52, 0xFE56, 0xFE57, 0xFF01, 0xFF01, + 0xFF0E, 0xFF0E, 0xFF1F, 0xFF1F, 0xFF61, 0xFF61, 0x10A56, 0x10A57, + 0x10F55, 0x10F59, 0x10F86, 0x10F89, 0x11047, 0x11048, 0x110BE, 0x110C1, + 0x11141, 0x11143, 0x111C5, 0x111C6, 0x111CD, 0x111CD, 0x111DE, 0x111DF, + 0x11238, 0x11239, 0x1123B, 0x1123C, 0x112A9, 0x112A9, 0x113D4, 0x113D5, + 0x1144B, 0x1144C, 0x115C2, 0x115C3, 0x115C9, 0x115D7, 0x11641, 0x11642, + 0x1173C, 0x1173E, 0x11944, 0x11944, 0x11946, 0x11946, 0x11A42, 0x11A43, + 0x11A9B, 0x11A9C, 0x11C41, 0x11C42, 0x11EF7, 0x11EF8, 0x11F43, 0x11F44, + 0x16A6E, 0x16A6F, 0x16AF5, 0x16AF5, 0x16B37, 0x16B38, 0x16B44, 0x16B44, + 0x16D6E, 0x16D6F, 0x16E98, 0x16E98, 0x1BC9F, 0x1BC9F, 0x1DA88, 0x1DA88, + // #88 (13678+34): bp=Soft_Dotted:SD + 0x0069, 0x006A, 0x012F, 0x012F, 0x0249, 0x0249, 0x0268, 0x0268, + 0x029D, 0x029D, 0x02B2, 0x02B2, 0x03F3, 0x03F3, 0x0456, 0x0456, + 0x0458, 0x0458, 0x1D62, 0x1D62, 0x1D96, 0x1D96, 0x1DA4, 0x1DA4, + 0x1DA8, 0x1DA8, 0x1E2D, 0x1E2D, 0x1ECB, 0x1ECB, 0x2071, 0x2071, + 0x2148, 0x2149, 0x2C7C, 0x2C7C, 0x1D422, 0x1D423, 0x1D456, 0x1D457, + 0x1D48A, 0x1D48B, 0x1D4BE, 0x1D4BF, 0x1D4F2, 0x1D4F3, 0x1D526, 0x1D527, + 0x1D55A, 0x1D55B, 0x1D58E, 0x1D58F, 0x1D5C2, 0x1D5C3, 0x1D5F6, 0x1D5F7, + 0x1D62A, 0x1D62B, 0x1D65E, 0x1D65F, 0x1D692, 0x1D693, 0x1DF1A, 0x1DF1A, + 0x1E04C, 0x1E04D, 0x1E068, 0x1E068, + // #89 (13712+116): bp=Terminal_Punctuation:Term + 0x0021, 0x0021, 0x002C, 0x002C, 0x002E, 0x002E, 0x003A, 0x003B, + 0x003F, 0x003F, 0x037E, 0x037E, 0x0387, 0x0387, 0x0589, 0x0589, + 0x05C3, 0x05C3, 0x060C, 0x060C, 0x061B, 0x061B, 0x061D, 0x061F, + 0x06D4, 0x06D4, 0x0700, 0x070A, 0x070C, 0x070C, 0x07F8, 0x07F9, + 0x0830, 0x0835, 0x0837, 0x083E, 0x085E, 0x085E, 0x0964, 0x0965, + 0x0E5A, 0x0E5B, 0x0F08, 0x0F08, 0x0F0D, 0x0F12, 0x104A, 0x104B, + 0x1361, 0x1368, 0x166E, 0x166E, 0x16EB, 0x16ED, 0x1735, 0x1736, + 0x17D4, 0x17D6, 0x17DA, 0x17DA, 0x1802, 0x1805, 0x1808, 0x1809, + 0x1944, 0x1945, 0x1AA8, 0x1AAB, 0x1B4E, 0x1B4F, 0x1B5A, 0x1B5B, + 0x1B5D, 0x1B5F, 0x1B7D, 0x1B7F, 0x1C3B, 0x1C3F, 0x1C7E, 0x1C7F, + 0x2024, 0x2024, 0x203C, 0x203D, 0x2047, 0x2049, 0x2CF9, 0x2CFB, + 0x2E2E, 0x2E2E, 0x2E3C, 0x2E3C, 0x2E41, 0x2E41, 0x2E4C, 0x2E4C, + 0x2E4E, 0x2E4F, 0x2E53, 0x2E54, 0x3001, 0x3002, 0xA4FE, 0xA4FF, + 0xA60D, 0xA60F, 0xA6F3, 0xA6F7, 0xA876, 0xA877, 0xA8CE, 0xA8CF, + 0xA92F, 0xA92F, 0xA9C7, 0xA9C9, 0xAA5D, 0xAA5F, 0xAADF, 0xAADF, + 0xAAF0, 0xAAF1, 0xABEB, 0xABEB, 0xFE12, 0xFE12, 0xFE15, 0xFE16, + 0xFE50, 0xFE52, 0xFE54, 0xFE57, 0xFF01, 0xFF01, 0xFF0C, 0xFF0C, + 0xFF0E, 0xFF0E, 0xFF1A, 0xFF1B, 0xFF1F, 0xFF1F, 0xFF61, 0xFF61, + 0xFF64, 0xFF64, 0x1039F, 0x1039F, 0x103D0, 0x103D0, 0x10857, 0x10857, + 0x1091F, 0x1091F, 0x10A56, 0x10A57, 0x10AF0, 0x10AF5, 0x10B3A, 0x10B3F, + 0x10B99, 0x10B9C, 0x10F55, 0x10F59, 0x10F86, 0x10F89, 0x11047, 0x1104D, + 0x110BE, 0x110C1, 0x11141, 0x11143, 0x111C5, 0x111C6, 0x111CD, 0x111CD, + 0x111DE, 0x111DF, 0x11238, 0x1123C, 0x112A9, 0x112A9, 0x113D4, 0x113D5, + 0x1144B, 0x1144D, 0x1145A, 0x1145B, 0x115C2, 0x115C5, 0x115C9, 0x115D7, + 0x11641, 0x11642, 0x1173C, 0x1173E, 0x11944, 0x11944, 0x11946, 0x11946, + 0x11A42, 0x11A43, 0x11A9B, 0x11A9C, 0x11AA1, 0x11AA2, 0x11C41, 0x11C43, + 0x11C71, 0x11C71, 0x11EF7, 0x11EF8, 0x11F43, 0x11F44, 0x12470, 0x12474, + 0x16A6E, 0x16A6F, 0x16AF5, 0x16AF5, 0x16B37, 0x16B39, 0x16B44, 0x16B44, + 0x16D6E, 0x16D6F, 0x16E97, 0x16E98, 0x1BC9F, 0x1BC9F, 0x1DA87, 0x1DA8A, + // #90 (13828+16): bp=Unified_Ideograph:UIdeo + 0x3400, 0x4DBF, 0x4E00, 0x9FFF, 0xFA0E, 0xFA0F, 0xFA11, 0xFA11, + 0xFA13, 0xFA14, 0xFA1F, 0xFA1F, 0xFA21, 0xFA21, 0xFA23, 0xFA24, + 0xFA27, 0xFA29, 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, + 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x30000, 0x3134A, 0x31350, 0x33479, + // #91 (13844+660): bp=Uppercase:Upper + 0x0041, 0x005A, 0x00C0, 0x00D6, 0x00D8, 0x00DE, 0x0100, 0x0100, + 0x0102, 0x0102, 0x0104, 0x0104, 0x0106, 0x0106, 0x0108, 0x0108, + 0x010A, 0x010A, 0x010C, 0x010C, 0x010E, 0x010E, 0x0110, 0x0110, + 0x0112, 0x0112, 0x0114, 0x0114, 0x0116, 0x0116, 0x0118, 0x0118, + 0x011A, 0x011A, 0x011C, 0x011C, 0x011E, 0x011E, 0x0120, 0x0120, + 0x0122, 0x0122, 0x0124, 0x0124, 0x0126, 0x0126, 0x0128, 0x0128, + 0x012A, 0x012A, 0x012C, 0x012C, 0x012E, 0x012E, 0x0130, 0x0130, + 0x0132, 0x0132, 0x0134, 0x0134, 0x0136, 0x0136, 0x0139, 0x0139, + 0x013B, 0x013B, 0x013D, 0x013D, 0x013F, 0x013F, 0x0141, 0x0141, + 0x0143, 0x0143, 0x0145, 0x0145, 0x0147, 0x0147, 0x014A, 0x014A, + 0x014C, 0x014C, 0x014E, 0x014E, 0x0150, 0x0150, 0x0152, 0x0152, + 0x0154, 0x0154, 0x0156, 0x0156, 0x0158, 0x0158, 0x015A, 0x015A, + 0x015C, 0x015C, 0x015E, 0x015E, 0x0160, 0x0160, 0x0162, 0x0162, + 0x0164, 0x0164, 0x0166, 0x0166, 0x0168, 0x0168, 0x016A, 0x016A, + 0x016C, 0x016C, 0x016E, 0x016E, 0x0170, 0x0170, 0x0172, 0x0172, + 0x0174, 0x0174, 0x0176, 0x0176, 0x0178, 0x0179, 0x017B, 0x017B, + 0x017D, 0x017D, 0x0181, 0x0182, 0x0184, 0x0184, 0x0186, 0x0187, + 0x0189, 0x018B, 0x018E, 0x0191, 0x0193, 0x0194, 0x0196, 0x0198, + 0x019C, 0x019D, 0x019F, 0x01A0, 0x01A2, 0x01A2, 0x01A4, 0x01A4, + 0x01A6, 0x01A7, 0x01A9, 0x01A9, 0x01AC, 0x01AC, 0x01AE, 0x01AF, + 0x01B1, 0x01B3, 0x01B5, 0x01B5, 0x01B7, 0x01B8, 0x01BC, 0x01BC, + 0x01C4, 0x01C4, 0x01C7, 0x01C7, 0x01CA, 0x01CA, 0x01CD, 0x01CD, + 0x01CF, 0x01CF, 0x01D1, 0x01D1, 0x01D3, 0x01D3, 0x01D5, 0x01D5, + 0x01D7, 0x01D7, 0x01D9, 0x01D9, 0x01DB, 0x01DB, 0x01DE, 0x01DE, + 0x01E0, 0x01E0, 0x01E2, 0x01E2, 0x01E4, 0x01E4, 0x01E6, 0x01E6, + 0x01E8, 0x01E8, 0x01EA, 0x01EA, 0x01EC, 0x01EC, 0x01EE, 0x01EE, + 0x01F1, 0x01F1, 0x01F4, 0x01F4, 0x01F6, 0x01F8, 0x01FA, 0x01FA, + 0x01FC, 0x01FC, 0x01FE, 0x01FE, 0x0200, 0x0200, 0x0202, 0x0202, + 0x0204, 0x0204, 0x0206, 0x0206, 0x0208, 0x0208, 0x020A, 0x020A, + 0x020C, 0x020C, 0x020E, 0x020E, 0x0210, 0x0210, 0x0212, 0x0212, + 0x0214, 0x0214, 0x0216, 0x0216, 0x0218, 0x0218, 0x021A, 0x021A, + 0x021C, 0x021C, 0x021E, 0x021E, 0x0220, 0x0220, 0x0222, 0x0222, + 0x0224, 0x0224, 0x0226, 0x0226, 0x0228, 0x0228, 0x022A, 0x022A, + 0x022C, 0x022C, 0x022E, 0x022E, 0x0230, 0x0230, 0x0232, 0x0232, + 0x023A, 0x023B, 0x023D, 0x023E, 0x0241, 0x0241, 0x0243, 0x0246, + 0x0248, 0x0248, 0x024A, 0x024A, 0x024C, 0x024C, 0x024E, 0x024E, + 0x0370, 0x0370, 0x0372, 0x0372, 0x0376, 0x0376, 0x037F, 0x037F, + 0x0386, 0x0386, 0x0388, 0x038A, 0x038C, 0x038C, 0x038E, 0x038F, + 0x0391, 0x03A1, 0x03A3, 0x03AB, 0x03CF, 0x03CF, 0x03D2, 0x03D4, + 0x03D8, 0x03D8, 0x03DA, 0x03DA, 0x03DC, 0x03DC, 0x03DE, 0x03DE, + 0x03E0, 0x03E0, 0x03E2, 0x03E2, 0x03E4, 0x03E4, 0x03E6, 0x03E6, + 0x03E8, 0x03E8, 0x03EA, 0x03EA, 0x03EC, 0x03EC, 0x03EE, 0x03EE, + 0x03F4, 0x03F4, 0x03F7, 0x03F7, 0x03F9, 0x03FA, 0x03FD, 0x042F, + 0x0460, 0x0460, 0x0462, 0x0462, 0x0464, 0x0464, 0x0466, 0x0466, + 0x0468, 0x0468, 0x046A, 0x046A, 0x046C, 0x046C, 0x046E, 0x046E, + 0x0470, 0x0470, 0x0472, 0x0472, 0x0474, 0x0474, 0x0476, 0x0476, + 0x0478, 0x0478, 0x047A, 0x047A, 0x047C, 0x047C, 0x047E, 0x047E, + 0x0480, 0x0480, 0x048A, 0x048A, 0x048C, 0x048C, 0x048E, 0x048E, + 0x0490, 0x0490, 0x0492, 0x0492, 0x0494, 0x0494, 0x0496, 0x0496, + 0x0498, 0x0498, 0x049A, 0x049A, 0x049C, 0x049C, 0x049E, 0x049E, + 0x04A0, 0x04A0, 0x04A2, 0x04A2, 0x04A4, 0x04A4, 0x04A6, 0x04A6, + 0x04A8, 0x04A8, 0x04AA, 0x04AA, 0x04AC, 0x04AC, 0x04AE, 0x04AE, + 0x04B0, 0x04B0, 0x04B2, 0x04B2, 0x04B4, 0x04B4, 0x04B6, 0x04B6, + 0x04B8, 0x04B8, 0x04BA, 0x04BA, 0x04BC, 0x04BC, 0x04BE, 0x04BE, + 0x04C0, 0x04C1, 0x04C3, 0x04C3, 0x04C5, 0x04C5, 0x04C7, 0x04C7, + 0x04C9, 0x04C9, 0x04CB, 0x04CB, 0x04CD, 0x04CD, 0x04D0, 0x04D0, + 0x04D2, 0x04D2, 0x04D4, 0x04D4, 0x04D6, 0x04D6, 0x04D8, 0x04D8, + 0x04DA, 0x04DA, 0x04DC, 0x04DC, 0x04DE, 0x04DE, 0x04E0, 0x04E0, + 0x04E2, 0x04E2, 0x04E4, 0x04E4, 0x04E6, 0x04E6, 0x04E8, 0x04E8, + 0x04EA, 0x04EA, 0x04EC, 0x04EC, 0x04EE, 0x04EE, 0x04F0, 0x04F0, + 0x04F2, 0x04F2, 0x04F4, 0x04F4, 0x04F6, 0x04F6, 0x04F8, 0x04F8, + 0x04FA, 0x04FA, 0x04FC, 0x04FC, 0x04FE, 0x04FE, 0x0500, 0x0500, + 0x0502, 0x0502, 0x0504, 0x0504, 0x0506, 0x0506, 0x0508, 0x0508, + 0x050A, 0x050A, 0x050C, 0x050C, 0x050E, 0x050E, 0x0510, 0x0510, + 0x0512, 0x0512, 0x0514, 0x0514, 0x0516, 0x0516, 0x0518, 0x0518, + 0x051A, 0x051A, 0x051C, 0x051C, 0x051E, 0x051E, 0x0520, 0x0520, + 0x0522, 0x0522, 0x0524, 0x0524, 0x0526, 0x0526, 0x0528, 0x0528, + 0x052A, 0x052A, 0x052C, 0x052C, 0x052E, 0x052E, 0x0531, 0x0556, + 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x13A0, 0x13F5, + 0x1C89, 0x1C89, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1E00, 0x1E00, + 0x1E02, 0x1E02, 0x1E04, 0x1E04, 0x1E06, 0x1E06, 0x1E08, 0x1E08, + 0x1E0A, 0x1E0A, 0x1E0C, 0x1E0C, 0x1E0E, 0x1E0E, 0x1E10, 0x1E10, + 0x1E12, 0x1E12, 0x1E14, 0x1E14, 0x1E16, 0x1E16, 0x1E18, 0x1E18, + 0x1E1A, 0x1E1A, 0x1E1C, 0x1E1C, 0x1E1E, 0x1E1E, 0x1E20, 0x1E20, + 0x1E22, 0x1E22, 0x1E24, 0x1E24, 0x1E26, 0x1E26, 0x1E28, 0x1E28, + 0x1E2A, 0x1E2A, 0x1E2C, 0x1E2C, 0x1E2E, 0x1E2E, 0x1E30, 0x1E30, + 0x1E32, 0x1E32, 0x1E34, 0x1E34, 0x1E36, 0x1E36, 0x1E38, 0x1E38, + 0x1E3A, 0x1E3A, 0x1E3C, 0x1E3C, 0x1E3E, 0x1E3E, 0x1E40, 0x1E40, + 0x1E42, 0x1E42, 0x1E44, 0x1E44, 0x1E46, 0x1E46, 0x1E48, 0x1E48, + 0x1E4A, 0x1E4A, 0x1E4C, 0x1E4C, 0x1E4E, 0x1E4E, 0x1E50, 0x1E50, + 0x1E52, 0x1E52, 0x1E54, 0x1E54, 0x1E56, 0x1E56, 0x1E58, 0x1E58, + 0x1E5A, 0x1E5A, 0x1E5C, 0x1E5C, 0x1E5E, 0x1E5E, 0x1E60, 0x1E60, + 0x1E62, 0x1E62, 0x1E64, 0x1E64, 0x1E66, 0x1E66, 0x1E68, 0x1E68, + 0x1E6A, 0x1E6A, 0x1E6C, 0x1E6C, 0x1E6E, 0x1E6E, 0x1E70, 0x1E70, + 0x1E72, 0x1E72, 0x1E74, 0x1E74, 0x1E76, 0x1E76, 0x1E78, 0x1E78, + 0x1E7A, 0x1E7A, 0x1E7C, 0x1E7C, 0x1E7E, 0x1E7E, 0x1E80, 0x1E80, + 0x1E82, 0x1E82, 0x1E84, 0x1E84, 0x1E86, 0x1E86, 0x1E88, 0x1E88, + 0x1E8A, 0x1E8A, 0x1E8C, 0x1E8C, 0x1E8E, 0x1E8E, 0x1E90, 0x1E90, + 0x1E92, 0x1E92, 0x1E94, 0x1E94, 0x1E9E, 0x1E9E, 0x1EA0, 0x1EA0, + 0x1EA2, 0x1EA2, 0x1EA4, 0x1EA4, 0x1EA6, 0x1EA6, 0x1EA8, 0x1EA8, + 0x1EAA, 0x1EAA, 0x1EAC, 0x1EAC, 0x1EAE, 0x1EAE, 0x1EB0, 0x1EB0, + 0x1EB2, 0x1EB2, 0x1EB4, 0x1EB4, 0x1EB6, 0x1EB6, 0x1EB8, 0x1EB8, + 0x1EBA, 0x1EBA, 0x1EBC, 0x1EBC, 0x1EBE, 0x1EBE, 0x1EC0, 0x1EC0, + 0x1EC2, 0x1EC2, 0x1EC4, 0x1EC4, 0x1EC6, 0x1EC6, 0x1EC8, 0x1EC8, + 0x1ECA, 0x1ECA, 0x1ECC, 0x1ECC, 0x1ECE, 0x1ECE, 0x1ED0, 0x1ED0, + 0x1ED2, 0x1ED2, 0x1ED4, 0x1ED4, 0x1ED6, 0x1ED6, 0x1ED8, 0x1ED8, + 0x1EDA, 0x1EDA, 0x1EDC, 0x1EDC, 0x1EDE, 0x1EDE, 0x1EE0, 0x1EE0, + 0x1EE2, 0x1EE2, 0x1EE4, 0x1EE4, 0x1EE6, 0x1EE6, 0x1EE8, 0x1EE8, + 0x1EEA, 0x1EEA, 0x1EEC, 0x1EEC, 0x1EEE, 0x1EEE, 0x1EF0, 0x1EF0, + 0x1EF2, 0x1EF2, 0x1EF4, 0x1EF4, 0x1EF6, 0x1EF6, 0x1EF8, 0x1EF8, + 0x1EFA, 0x1EFA, 0x1EFC, 0x1EFC, 0x1EFE, 0x1EFE, 0x1F08, 0x1F0F, + 0x1F18, 0x1F1D, 0x1F28, 0x1F2F, 0x1F38, 0x1F3F, 0x1F48, 0x1F4D, + 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F5F, + 0x1F68, 0x1F6F, 0x1FB8, 0x1FBB, 0x1FC8, 0x1FCB, 0x1FD8, 0x1FDB, + 0x1FE8, 0x1FEC, 0x1FF8, 0x1FFB, 0x2102, 0x2102, 0x2107, 0x2107, + 0x210B, 0x210D, 0x2110, 0x2112, 0x2115, 0x2115, 0x2119, 0x211D, + 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x212D, + 0x2130, 0x2133, 0x213E, 0x213F, 0x2145, 0x2145, 0x2160, 0x216F, + 0x2183, 0x2183, 0x24B6, 0x24CF, 0x2C00, 0x2C2F, 0x2C60, 0x2C60, + 0x2C62, 0x2C64, 0x2C67, 0x2C67, 0x2C69, 0x2C69, 0x2C6B, 0x2C6B, + 0x2C6D, 0x2C70, 0x2C72, 0x2C72, 0x2C75, 0x2C75, 0x2C7E, 0x2C80, + 0x2C82, 0x2C82, 0x2C84, 0x2C84, 0x2C86, 0x2C86, 0x2C88, 0x2C88, + 0x2C8A, 0x2C8A, 0x2C8C, 0x2C8C, 0x2C8E, 0x2C8E, 0x2C90, 0x2C90, + 0x2C92, 0x2C92, 0x2C94, 0x2C94, 0x2C96, 0x2C96, 0x2C98, 0x2C98, + 0x2C9A, 0x2C9A, 0x2C9C, 0x2C9C, 0x2C9E, 0x2C9E, 0x2CA0, 0x2CA0, + 0x2CA2, 0x2CA2, 0x2CA4, 0x2CA4, 0x2CA6, 0x2CA6, 0x2CA8, 0x2CA8, + 0x2CAA, 0x2CAA, 0x2CAC, 0x2CAC, 0x2CAE, 0x2CAE, 0x2CB0, 0x2CB0, + 0x2CB2, 0x2CB2, 0x2CB4, 0x2CB4, 0x2CB6, 0x2CB6, 0x2CB8, 0x2CB8, + 0x2CBA, 0x2CBA, 0x2CBC, 0x2CBC, 0x2CBE, 0x2CBE, 0x2CC0, 0x2CC0, + 0x2CC2, 0x2CC2, 0x2CC4, 0x2CC4, 0x2CC6, 0x2CC6, 0x2CC8, 0x2CC8, + 0x2CCA, 0x2CCA, 0x2CCC, 0x2CCC, 0x2CCE, 0x2CCE, 0x2CD0, 0x2CD0, + 0x2CD2, 0x2CD2, 0x2CD4, 0x2CD4, 0x2CD6, 0x2CD6, 0x2CD8, 0x2CD8, + 0x2CDA, 0x2CDA, 0x2CDC, 0x2CDC, 0x2CDE, 0x2CDE, 0x2CE0, 0x2CE0, + 0x2CE2, 0x2CE2, 0x2CEB, 0x2CEB, 0x2CED, 0x2CED, 0x2CF2, 0x2CF2, + 0xA640, 0xA640, 0xA642, 0xA642, 0xA644, 0xA644, 0xA646, 0xA646, + 0xA648, 0xA648, 0xA64A, 0xA64A, 0xA64C, 0xA64C, 0xA64E, 0xA64E, + 0xA650, 0xA650, 0xA652, 0xA652, 0xA654, 0xA654, 0xA656, 0xA656, + 0xA658, 0xA658, 0xA65A, 0xA65A, 0xA65C, 0xA65C, 0xA65E, 0xA65E, + 0xA660, 0xA660, 0xA662, 0xA662, 0xA664, 0xA664, 0xA666, 0xA666, + 0xA668, 0xA668, 0xA66A, 0xA66A, 0xA66C, 0xA66C, 0xA680, 0xA680, + 0xA682, 0xA682, 0xA684, 0xA684, 0xA686, 0xA686, 0xA688, 0xA688, + 0xA68A, 0xA68A, 0xA68C, 0xA68C, 0xA68E, 0xA68E, 0xA690, 0xA690, + 0xA692, 0xA692, 0xA694, 0xA694, 0xA696, 0xA696, 0xA698, 0xA698, + 0xA69A, 0xA69A, 0xA722, 0xA722, 0xA724, 0xA724, 0xA726, 0xA726, + 0xA728, 0xA728, 0xA72A, 0xA72A, 0xA72C, 0xA72C, 0xA72E, 0xA72E, + 0xA732, 0xA732, 0xA734, 0xA734, 0xA736, 0xA736, 0xA738, 0xA738, + 0xA73A, 0xA73A, 0xA73C, 0xA73C, 0xA73E, 0xA73E, 0xA740, 0xA740, + 0xA742, 0xA742, 0xA744, 0xA744, 0xA746, 0xA746, 0xA748, 0xA748, + 0xA74A, 0xA74A, 0xA74C, 0xA74C, 0xA74E, 0xA74E, 0xA750, 0xA750, + 0xA752, 0xA752, 0xA754, 0xA754, 0xA756, 0xA756, 0xA758, 0xA758, + 0xA75A, 0xA75A, 0xA75C, 0xA75C, 0xA75E, 0xA75E, 0xA760, 0xA760, + 0xA762, 0xA762, 0xA764, 0xA764, 0xA766, 0xA766, 0xA768, 0xA768, + 0xA76A, 0xA76A, 0xA76C, 0xA76C, 0xA76E, 0xA76E, 0xA779, 0xA779, + 0xA77B, 0xA77B, 0xA77D, 0xA77E, 0xA780, 0xA780, 0xA782, 0xA782, + 0xA784, 0xA784, 0xA786, 0xA786, 0xA78B, 0xA78B, 0xA78D, 0xA78D, + 0xA790, 0xA790, 0xA792, 0xA792, 0xA796, 0xA796, 0xA798, 0xA798, + 0xA79A, 0xA79A, 0xA79C, 0xA79C, 0xA79E, 0xA79E, 0xA7A0, 0xA7A0, + 0xA7A2, 0xA7A2, 0xA7A4, 0xA7A4, 0xA7A6, 0xA7A6, 0xA7A8, 0xA7A8, + 0xA7AA, 0xA7AE, 0xA7B0, 0xA7B4, 0xA7B6, 0xA7B6, 0xA7B8, 0xA7B8, + 0xA7BA, 0xA7BA, 0xA7BC, 0xA7BC, 0xA7BE, 0xA7BE, 0xA7C0, 0xA7C0, + 0xA7C2, 0xA7C2, 0xA7C4, 0xA7C7, 0xA7C9, 0xA7C9, 0xA7CB, 0xA7CC, + 0xA7CE, 0xA7CE, 0xA7D0, 0xA7D0, 0xA7D2, 0xA7D2, 0xA7D4, 0xA7D4, + 0xA7D6, 0xA7D6, 0xA7D8, 0xA7D8, 0xA7DA, 0xA7DA, 0xA7DC, 0xA7DC, + 0xA7F5, 0xA7F5, 0xFF21, 0xFF3A, 0x10400, 0x10427, 0x104B0, 0x104D3, + 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, + 0x10C80, 0x10CB2, 0x10D50, 0x10D65, 0x118A0, 0x118BF, 0x16E40, 0x16E5F, + 0x16EA0, 0x16EB8, 0x1D400, 0x1D419, 0x1D434, 0x1D44D, 0x1D468, 0x1D481, + 0x1D49C, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, + 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B5, 0x1D4D0, 0x1D4E9, 0x1D504, 0x1D505, + 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, 0x1D538, 0x1D539, + 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, + 0x1D56C, 0x1D585, 0x1D5A0, 0x1D5B9, 0x1D5D4, 0x1D5ED, 0x1D608, 0x1D621, + 0x1D63C, 0x1D655, 0x1D670, 0x1D689, 0x1D6A8, 0x1D6C0, 0x1D6E2, 0x1D6FA, + 0x1D71C, 0x1D734, 0x1D756, 0x1D76E, 0x1D790, 0x1D7A8, 0x1D7CA, 0x1D7CA, + 0x1E900, 0x1E921, 0x1F130, 0x1F149, 0x1F150, 0x1F169, 0x1F170, 0x1F189, + // #92 (14504+4): bp=Variation_Selector:VS + 0x180B, 0x180D, 0x180F, 0x180F, 0xFE00, 0xFE0F, 0xE0100, 0xE01EF, + // #93 (14508+10): bp=White_Space:space + 0x0009, 0x000D, 0x0020, 0x0020, 0x0085, 0x0085, 0x00A0, 0x00A0, + 0x1680, 0x1680, 0x2000, 0x200A, 0x2028, 0x2029, 0x202F, 0x202F, + 0x205F, 0x205F, 0x3000, 0x3000, + // #94 (14518+806): bp=XID_Continue:XIDC + 0x0030, 0x0039, 0x0041, 0x005A, 0x005F, 0x005F, 0x0061, 0x007A, + 0x00AA, 0x00AA, 0x00B5, 0x00B5, 0x00B7, 0x00B7, 0x00BA, 0x00BA, + 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x02C1, 0x02C6, 0x02D1, + 0x02E0, 0x02E4, 0x02EC, 0x02EC, 0x02EE, 0x02EE, 0x0300, 0x0374, + 0x0376, 0x0377, 0x037B, 0x037D, 0x037F, 0x037F, 0x0386, 0x038A, + 0x038C, 0x038C, 0x038E, 0x03A1, 0x03A3, 0x03F5, 0x03F7, 0x0481, + 0x0483, 0x0487, 0x048A, 0x052F, 0x0531, 0x0556, 0x0559, 0x0559, + 0x0560, 0x0588, 0x0591, 0x05BD, 0x05BF, 0x05BF, 0x05C1, 0x05C2, + 0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x05D0, 0x05EA, 0x05EF, 0x05F2, + 0x0610, 0x061A, 0x0620, 0x0669, 0x066E, 0x06D3, 0x06D5, 0x06DC, + 0x06DF, 0x06E8, 0x06EA, 0x06FC, 0x06FF, 0x06FF, 0x0710, 0x074A, + 0x074D, 0x07B1, 0x07C0, 0x07F5, 0x07FA, 0x07FA, 0x07FD, 0x07FD, + 0x0800, 0x082D, 0x0840, 0x085B, 0x0860, 0x086A, 0x0870, 0x0887, + 0x0889, 0x088F, 0x0897, 0x08E1, 0x08E3, 0x0963, 0x0966, 0x096F, + 0x0971, 0x0983, 0x0985, 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, + 0x09AA, 0x09B0, 0x09B2, 0x09B2, 0x09B6, 0x09B9, 0x09BC, 0x09C4, + 0x09C7, 0x09C8, 0x09CB, 0x09CE, 0x09D7, 0x09D7, 0x09DC, 0x09DD, + 0x09DF, 0x09E3, 0x09E6, 0x09F1, 0x09FC, 0x09FC, 0x09FE, 0x09FE, + 0x0A01, 0x0A03, 0x0A05, 0x0A0A, 0x0A0F, 0x0A10, 0x0A13, 0x0A28, + 0x0A2A, 0x0A30, 0x0A32, 0x0A33, 0x0A35, 0x0A36, 0x0A38, 0x0A39, + 0x0A3C, 0x0A3C, 0x0A3E, 0x0A42, 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, + 0x0A51, 0x0A51, 0x0A59, 0x0A5C, 0x0A5E, 0x0A5E, 0x0A66, 0x0A75, + 0x0A81, 0x0A83, 0x0A85, 0x0A8D, 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, + 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, 0x0AB5, 0x0AB9, 0x0ABC, 0x0AC5, + 0x0AC7, 0x0AC9, 0x0ACB, 0x0ACD, 0x0AD0, 0x0AD0, 0x0AE0, 0x0AE3, + 0x0AE6, 0x0AEF, 0x0AF9, 0x0AFF, 0x0B01, 0x0B03, 0x0B05, 0x0B0C, + 0x0B0F, 0x0B10, 0x0B13, 0x0B28, 0x0B2A, 0x0B30, 0x0B32, 0x0B33, + 0x0B35, 0x0B39, 0x0B3C, 0x0B44, 0x0B47, 0x0B48, 0x0B4B, 0x0B4D, + 0x0B55, 0x0B57, 0x0B5C, 0x0B5D, 0x0B5F, 0x0B63, 0x0B66, 0x0B6F, + 0x0B71, 0x0B71, 0x0B82, 0x0B83, 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, + 0x0B92, 0x0B95, 0x0B99, 0x0B9A, 0x0B9C, 0x0B9C, 0x0B9E, 0x0B9F, + 0x0BA3, 0x0BA4, 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB9, 0x0BBE, 0x0BC2, + 0x0BC6, 0x0BC8, 0x0BCA, 0x0BCD, 0x0BD0, 0x0BD0, 0x0BD7, 0x0BD7, + 0x0BE6, 0x0BEF, 0x0C00, 0x0C0C, 0x0C0E, 0x0C10, 0x0C12, 0x0C28, + 0x0C2A, 0x0C39, 0x0C3C, 0x0C44, 0x0C46, 0x0C48, 0x0C4A, 0x0C4D, + 0x0C55, 0x0C56, 0x0C58, 0x0C5A, 0x0C5C, 0x0C5D, 0x0C60, 0x0C63, + 0x0C66, 0x0C6F, 0x0C80, 0x0C83, 0x0C85, 0x0C8C, 0x0C8E, 0x0C90, + 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, 0x0CB5, 0x0CB9, 0x0CBC, 0x0CC4, + 0x0CC6, 0x0CC8, 0x0CCA, 0x0CCD, 0x0CD5, 0x0CD6, 0x0CDC, 0x0CDE, + 0x0CE0, 0x0CE3, 0x0CE6, 0x0CEF, 0x0CF1, 0x0CF3, 0x0D00, 0x0D0C, + 0x0D0E, 0x0D10, 0x0D12, 0x0D44, 0x0D46, 0x0D48, 0x0D4A, 0x0D4E, + 0x0D54, 0x0D57, 0x0D5F, 0x0D63, 0x0D66, 0x0D6F, 0x0D7A, 0x0D7F, + 0x0D81, 0x0D83, 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, 0x0DB3, 0x0DBB, + 0x0DBD, 0x0DBD, 0x0DC0, 0x0DC6, 0x0DCA, 0x0DCA, 0x0DCF, 0x0DD4, + 0x0DD6, 0x0DD6, 0x0DD8, 0x0DDF, 0x0DE6, 0x0DEF, 0x0DF2, 0x0DF3, + 0x0E01, 0x0E3A, 0x0E40, 0x0E4E, 0x0E50, 0x0E59, 0x0E81, 0x0E82, + 0x0E84, 0x0E84, 0x0E86, 0x0E8A, 0x0E8C, 0x0EA3, 0x0EA5, 0x0EA5, + 0x0EA7, 0x0EBD, 0x0EC0, 0x0EC4, 0x0EC6, 0x0EC6, 0x0EC8, 0x0ECE, + 0x0ED0, 0x0ED9, 0x0EDC, 0x0EDF, 0x0F00, 0x0F00, 0x0F18, 0x0F19, + 0x0F20, 0x0F29, 0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, + 0x0F3E, 0x0F47, 0x0F49, 0x0F6C, 0x0F71, 0x0F84, 0x0F86, 0x0F97, + 0x0F99, 0x0FBC, 0x0FC6, 0x0FC6, 0x1000, 0x1049, 0x1050, 0x109D, + 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, + 0x10FC, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, + 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, + 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, + 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, 0x1318, 0x135A, + 0x135D, 0x135F, 0x1369, 0x1371, 0x1380, 0x138F, 0x13A0, 0x13F5, + 0x13F8, 0x13FD, 0x1401, 0x166C, 0x166F, 0x167F, 0x1681, 0x169A, + 0x16A0, 0x16EA, 0x16EE, 0x16F8, 0x1700, 0x1715, 0x171F, 0x1734, + 0x1740, 0x1753, 0x1760, 0x176C, 0x176E, 0x1770, 0x1772, 0x1773, + 0x1780, 0x17D3, 0x17D7, 0x17D7, 0x17DC, 0x17DD, 0x17E0, 0x17E9, + 0x180B, 0x180D, 0x180F, 0x1819, 0x1820, 0x1878, 0x1880, 0x18AA, + 0x18B0, 0x18F5, 0x1900, 0x191E, 0x1920, 0x192B, 0x1930, 0x193B, + 0x1946, 0x196D, 0x1970, 0x1974, 0x1980, 0x19AB, 0x19B0, 0x19C9, + 0x19D0, 0x19DA, 0x1A00, 0x1A1B, 0x1A20, 0x1A5E, 0x1A60, 0x1A7C, + 0x1A7F, 0x1A89, 0x1A90, 0x1A99, 0x1AA7, 0x1AA7, 0x1AB0, 0x1ABD, + 0x1ABF, 0x1ADD, 0x1AE0, 0x1AEB, 0x1B00, 0x1B4C, 0x1B50, 0x1B59, + 0x1B6B, 0x1B73, 0x1B80, 0x1BF3, 0x1C00, 0x1C37, 0x1C40, 0x1C49, + 0x1C4D, 0x1C7D, 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, + 0x1CD0, 0x1CD2, 0x1CD4, 0x1CFA, 0x1D00, 0x1F15, 0x1F18, 0x1F1D, + 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, + 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, + 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, + 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, + 0x1FF6, 0x1FFC, 0x200C, 0x200D, 0x203F, 0x2040, 0x2054, 0x2054, + 0x2071, 0x2071, 0x207F, 0x207F, 0x2090, 0x209C, 0x20D0, 0x20DC, + 0x20E1, 0x20E1, 0x20E5, 0x20F0, 0x2102, 0x2102, 0x2107, 0x2107, + 0x210A, 0x2113, 0x2115, 0x2115, 0x2118, 0x211D, 0x2124, 0x2124, + 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x2139, 0x213C, 0x213F, + 0x2145, 0x2149, 0x214E, 0x214E, 0x2160, 0x2188, 0x2C00, 0x2CE4, + 0x2CEB, 0x2CF3, 0x2D00, 0x2D25, 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, + 0x2D30, 0x2D67, 0x2D6F, 0x2D6F, 0x2D7F, 0x2D96, 0x2DA0, 0x2DA6, + 0x2DA8, 0x2DAE, 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, + 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, 0x2DE0, 0x2DFF, + 0x3005, 0x3007, 0x3021, 0x302F, 0x3031, 0x3035, 0x3038, 0x303C, + 0x3041, 0x3096, 0x3099, 0x309A, 0x309D, 0x309F, 0x30A1, 0x30FF, + 0x3105, 0x312F, 0x3131, 0x318E, 0x31A0, 0x31BF, 0x31F0, 0x31FF, + 0x3400, 0x4DBF, 0x4E00, 0xA48C, 0xA4D0, 0xA4FD, 0xA500, 0xA60C, + 0xA610, 0xA62B, 0xA640, 0xA66F, 0xA674, 0xA67D, 0xA67F, 0xA6F1, + 0xA717, 0xA71F, 0xA722, 0xA788, 0xA78B, 0xA7DC, 0xA7F1, 0xA827, + 0xA82C, 0xA82C, 0xA840, 0xA873, 0xA880, 0xA8C5, 0xA8D0, 0xA8D9, + 0xA8E0, 0xA8F7, 0xA8FB, 0xA8FB, 0xA8FD, 0xA92D, 0xA930, 0xA953, + 0xA960, 0xA97C, 0xA980, 0xA9C0, 0xA9CF, 0xA9D9, 0xA9E0, 0xA9FE, + 0xAA00, 0xAA36, 0xAA40, 0xAA4D, 0xAA50, 0xAA59, 0xAA60, 0xAA76, + 0xAA7A, 0xAAC2, 0xAADB, 0xAADD, 0xAAE0, 0xAAEF, 0xAAF2, 0xAAF6, + 0xAB01, 0xAB06, 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, + 0xAB28, 0xAB2E, 0xAB30, 0xAB5A, 0xAB5C, 0xAB69, 0xAB70, 0xABEA, + 0xABEC, 0xABED, 0xABF0, 0xABF9, 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, + 0xD7CB, 0xD7FB, 0xF900, 0xFA6D, 0xFA70, 0xFAD9, 0xFB00, 0xFB06, + 0xFB13, 0xFB17, 0xFB1D, 0xFB28, 0xFB2A, 0xFB36, 0xFB38, 0xFB3C, + 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFBB1, + 0xFBD3, 0xFC5D, 0xFC64, 0xFD3D, 0xFD50, 0xFD8F, 0xFD92, 0xFDC7, + 0xFDF0, 0xFDF9, 0xFE00, 0xFE0F, 0xFE20, 0xFE2F, 0xFE33, 0xFE34, + 0xFE4D, 0xFE4F, 0xFE71, 0xFE71, 0xFE73, 0xFE73, 0xFE77, 0xFE77, + 0xFE79, 0xFE79, 0xFE7B, 0xFE7B, 0xFE7D, 0xFE7D, 0xFE7F, 0xFEFC, + 0xFF10, 0xFF19, 0xFF21, 0xFF3A, 0xFF3F, 0xFF3F, 0xFF41, 0xFF5A, + 0xFF65, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, + 0xFFDA, 0xFFDC, 0x10000, 0x1000B, 0x1000D, 0x10026, 0x10028, 0x1003A, + 0x1003C, 0x1003D, 0x1003F, 0x1004D, 0x10050, 0x1005D, 0x10080, 0x100FA, + 0x10140, 0x10174, 0x101FD, 0x101FD, 0x10280, 0x1029C, 0x102A0, 0x102D0, + 0x102E0, 0x102E0, 0x10300, 0x1031F, 0x1032D, 0x1034A, 0x10350, 0x1037A, + 0x10380, 0x1039D, 0x103A0, 0x103C3, 0x103C8, 0x103CF, 0x103D1, 0x103D5, + 0x10400, 0x1049D, 0x104A0, 0x104A9, 0x104B0, 0x104D3, 0x104D8, 0x104FB, + 0x10500, 0x10527, 0x10530, 0x10563, 0x10570, 0x1057A, 0x1057C, 0x1058A, + 0x1058C, 0x10592, 0x10594, 0x10595, 0x10597, 0x105A1, 0x105A3, 0x105B1, + 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x105C0, 0x105F3, 0x10600, 0x10736, + 0x10740, 0x10755, 0x10760, 0x10767, 0x10780, 0x10785, 0x10787, 0x107B0, + 0x107B2, 0x107BA, 0x10800, 0x10805, 0x10808, 0x10808, 0x1080A, 0x10835, + 0x10837, 0x10838, 0x1083C, 0x1083C, 0x1083F, 0x10855, 0x10860, 0x10876, + 0x10880, 0x1089E, 0x108E0, 0x108F2, 0x108F4, 0x108F5, 0x10900, 0x10915, + 0x10920, 0x10939, 0x10940, 0x10959, 0x10980, 0x109B7, 0x109BE, 0x109BF, + 0x10A00, 0x10A03, 0x10A05, 0x10A06, 0x10A0C, 0x10A13, 0x10A15, 0x10A17, + 0x10A19, 0x10A35, 0x10A38, 0x10A3A, 0x10A3F, 0x10A3F, 0x10A60, 0x10A7C, + 0x10A80, 0x10A9C, 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE6, 0x10B00, 0x10B35, + 0x10B40, 0x10B55, 0x10B60, 0x10B72, 0x10B80, 0x10B91, 0x10C00, 0x10C48, + 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, 0x10D00, 0x10D27, 0x10D30, 0x10D39, + 0x10D40, 0x10D65, 0x10D69, 0x10D6D, 0x10D6F, 0x10D85, 0x10E80, 0x10EA9, + 0x10EAB, 0x10EAC, 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC7, 0x10EFA, 0x10F1C, + 0x10F27, 0x10F27, 0x10F30, 0x10F50, 0x10F70, 0x10F85, 0x10FB0, 0x10FC4, + 0x10FE0, 0x10FF6, 0x11000, 0x11046, 0x11066, 0x11075, 0x1107F, 0x110BA, + 0x110C2, 0x110C2, 0x110D0, 0x110E8, 0x110F0, 0x110F9, 0x11100, 0x11134, + 0x11136, 0x1113F, 0x11144, 0x11147, 0x11150, 0x11173, 0x11176, 0x11176, + 0x11180, 0x111C4, 0x111C9, 0x111CC, 0x111CE, 0x111DA, 0x111DC, 0x111DC, + 0x11200, 0x11211, 0x11213, 0x11237, 0x1123E, 0x11241, 0x11280, 0x11286, + 0x11288, 0x11288, 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A8, + 0x112B0, 0x112EA, 0x112F0, 0x112F9, 0x11300, 0x11303, 0x11305, 0x1130C, + 0x1130F, 0x11310, 0x11313, 0x11328, 0x1132A, 0x11330, 0x11332, 0x11333, + 0x11335, 0x11339, 0x1133B, 0x11344, 0x11347, 0x11348, 0x1134B, 0x1134D, + 0x11350, 0x11350, 0x11357, 0x11357, 0x1135D, 0x11363, 0x11366, 0x1136C, + 0x11370, 0x11374, 0x11380, 0x11389, 0x1138B, 0x1138B, 0x1138E, 0x1138E, + 0x11390, 0x113B5, 0x113B7, 0x113C0, 0x113C2, 0x113C2, 0x113C5, 0x113C5, + 0x113C7, 0x113CA, 0x113CC, 0x113D3, 0x113E1, 0x113E2, 0x11400, 0x1144A, + 0x11450, 0x11459, 0x1145E, 0x11461, 0x11480, 0x114C5, 0x114C7, 0x114C7, + 0x114D0, 0x114D9, 0x11580, 0x115B5, 0x115B8, 0x115C0, 0x115D8, 0x115DD, + 0x11600, 0x11640, 0x11644, 0x11644, 0x11650, 0x11659, 0x11680, 0x116B8, + 0x116C0, 0x116C9, 0x116D0, 0x116E3, 0x11700, 0x1171A, 0x1171D, 0x1172B, + 0x11730, 0x11739, 0x11740, 0x11746, 0x11800, 0x1183A, 0x118A0, 0x118E9, + 0x118FF, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, 0x11916, + 0x11918, 0x11935, 0x11937, 0x11938, 0x1193B, 0x11943, 0x11950, 0x11959, + 0x119A0, 0x119A7, 0x119AA, 0x119D7, 0x119DA, 0x119E1, 0x119E3, 0x119E4, + 0x11A00, 0x11A3E, 0x11A47, 0x11A47, 0x11A50, 0x11A99, 0x11A9D, 0x11A9D, + 0x11AB0, 0x11AF8, 0x11B60, 0x11B67, 0x11BC0, 0x11BE0, 0x11BF0, 0x11BF9, + 0x11C00, 0x11C08, 0x11C0A, 0x11C36, 0x11C38, 0x11C40, 0x11C50, 0x11C59, + 0x11C72, 0x11C8F, 0x11C92, 0x11CA7, 0x11CA9, 0x11CB6, 0x11D00, 0x11D06, + 0x11D08, 0x11D09, 0x11D0B, 0x11D36, 0x11D3A, 0x11D3A, 0x11D3C, 0x11D3D, + 0x11D3F, 0x11D47, 0x11D50, 0x11D59, 0x11D60, 0x11D65, 0x11D67, 0x11D68, + 0x11D6A, 0x11D8E, 0x11D90, 0x11D91, 0x11D93, 0x11D98, 0x11DA0, 0x11DA9, + 0x11DB0, 0x11DDB, 0x11DE0, 0x11DE9, 0x11EE0, 0x11EF6, 0x11F00, 0x11F10, + 0x11F12, 0x11F3A, 0x11F3E, 0x11F42, 0x11F50, 0x11F5A, 0x11FB0, 0x11FB0, + 0x12000, 0x12399, 0x12400, 0x1246E, 0x12480, 0x12543, 0x12F90, 0x12FF0, + 0x13000, 0x1342F, 0x13440, 0x13455, 0x13460, 0x143FA, 0x14400, 0x14646, + 0x16100, 0x16139, 0x16800, 0x16A38, 0x16A40, 0x16A5E, 0x16A60, 0x16A69, + 0x16A70, 0x16ABE, 0x16AC0, 0x16AC9, 0x16AD0, 0x16AED, 0x16AF0, 0x16AF4, + 0x16B00, 0x16B36, 0x16B40, 0x16B43, 0x16B50, 0x16B59, 0x16B63, 0x16B77, + 0x16B7D, 0x16B8F, 0x16D40, 0x16D6C, 0x16D70, 0x16D79, 0x16E40, 0x16E7F, + 0x16EA0, 0x16EB8, 0x16EBB, 0x16ED3, 0x16F00, 0x16F4A, 0x16F4F, 0x16F87, + 0x16F8F, 0x16F9F, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE4, 0x16FF0, 0x16FF6, + 0x17000, 0x18CD5, 0x18CFF, 0x18D1E, 0x18D80, 0x18DF2, 0x1AFF0, 0x1AFF3, + 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B122, 0x1B132, 0x1B132, + 0x1B150, 0x1B152, 0x1B155, 0x1B155, 0x1B164, 0x1B167, 0x1B170, 0x1B2FB, + 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, 0x1BC88, 0x1BC90, 0x1BC99, + 0x1BC9D, 0x1BC9E, 0x1CCF0, 0x1CCF9, 0x1CF00, 0x1CF2D, 0x1CF30, 0x1CF46, + 0x1D165, 0x1D169, 0x1D16D, 0x1D172, 0x1D17B, 0x1D182, 0x1D185, 0x1D18B, + 0x1D1AA, 0x1D1AD, 0x1D242, 0x1D244, 0x1D400, 0x1D454, 0x1D456, 0x1D49C, + 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, + 0x1D4AE, 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, + 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, + 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, + 0x1D552, 0x1D6A5, 0x1D6A8, 0x1D6C0, 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6FA, + 0x1D6FC, 0x1D714, 0x1D716, 0x1D734, 0x1D736, 0x1D74E, 0x1D750, 0x1D76E, + 0x1D770, 0x1D788, 0x1D78A, 0x1D7A8, 0x1D7AA, 0x1D7C2, 0x1D7C4, 0x1D7CB, + 0x1D7CE, 0x1D7FF, 0x1DA00, 0x1DA36, 0x1DA3B, 0x1DA6C, 0x1DA75, 0x1DA75, + 0x1DA84, 0x1DA84, 0x1DA9B, 0x1DA9F, 0x1DAA1, 0x1DAAF, 0x1DF00, 0x1DF1E, + 0x1DF25, 0x1DF2A, 0x1E000, 0x1E006, 0x1E008, 0x1E018, 0x1E01B, 0x1E021, + 0x1E023, 0x1E024, 0x1E026, 0x1E02A, 0x1E030, 0x1E06D, 0x1E08F, 0x1E08F, + 0x1E100, 0x1E12C, 0x1E130, 0x1E13D, 0x1E140, 0x1E149, 0x1E14E, 0x1E14E, + 0x1E290, 0x1E2AE, 0x1E2C0, 0x1E2F9, 0x1E4D0, 0x1E4F9, 0x1E5D0, 0x1E5FA, + 0x1E6C0, 0x1E6DE, 0x1E6E0, 0x1E6F5, 0x1E6FE, 0x1E6FF, 0x1E7E0, 0x1E7E6, + 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, 0x1E7FE, 0x1E800, 0x1E8C4, + 0x1E8D0, 0x1E8D6, 0x1E900, 0x1E94B, 0x1E950, 0x1E959, 0x1EE00, 0x1EE03, + 0x1EE05, 0x1EE1F, 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, + 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, 0x1EE3B, 0x1EE3B, + 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, + 0x1EE4D, 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, + 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, + 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, + 0x1EE74, 0x1EE77, 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, + 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, + 0x1FBF0, 0x1FBF9, 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, + 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, + 0x31350, 0x33479, 0xE0100, 0xE01EF, + // #95 (15324+691): bp=XID_Start:XIDS + 0x0041, 0x005A, 0x0061, 0x007A, 0x00AA, 0x00AA, 0x00B5, 0x00B5, + 0x00BA, 0x00BA, 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x02C1, + 0x02C6, 0x02D1, 0x02E0, 0x02E4, 0x02EC, 0x02EC, 0x02EE, 0x02EE, + 0x0370, 0x0374, 0x0376, 0x0377, 0x037B, 0x037D, 0x037F, 0x037F, + 0x0386, 0x0386, 0x0388, 0x038A, 0x038C, 0x038C, 0x038E, 0x03A1, + 0x03A3, 0x03F5, 0x03F7, 0x0481, 0x048A, 0x052F, 0x0531, 0x0556, + 0x0559, 0x0559, 0x0560, 0x0588, 0x05D0, 0x05EA, 0x05EF, 0x05F2, + 0x0620, 0x064A, 0x066E, 0x066F, 0x0671, 0x06D3, 0x06D5, 0x06D5, + 0x06E5, 0x06E6, 0x06EE, 0x06EF, 0x06FA, 0x06FC, 0x06FF, 0x06FF, + 0x0710, 0x0710, 0x0712, 0x072F, 0x074D, 0x07A5, 0x07B1, 0x07B1, + 0x07CA, 0x07EA, 0x07F4, 0x07F5, 0x07FA, 0x07FA, 0x0800, 0x0815, + 0x081A, 0x081A, 0x0824, 0x0824, 0x0828, 0x0828, 0x0840, 0x0858, + 0x0860, 0x086A, 0x0870, 0x0887, 0x0889, 0x088F, 0x08A0, 0x08C9, + 0x0904, 0x0939, 0x093D, 0x093D, 0x0950, 0x0950, 0x0958, 0x0961, + 0x0971, 0x0980, 0x0985, 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, + 0x09AA, 0x09B0, 0x09B2, 0x09B2, 0x09B6, 0x09B9, 0x09BD, 0x09BD, + 0x09CE, 0x09CE, 0x09DC, 0x09DD, 0x09DF, 0x09E1, 0x09F0, 0x09F1, + 0x09FC, 0x09FC, 0x0A05, 0x0A0A, 0x0A0F, 0x0A10, 0x0A13, 0x0A28, + 0x0A2A, 0x0A30, 0x0A32, 0x0A33, 0x0A35, 0x0A36, 0x0A38, 0x0A39, + 0x0A59, 0x0A5C, 0x0A5E, 0x0A5E, 0x0A72, 0x0A74, 0x0A85, 0x0A8D, + 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, + 0x0AB5, 0x0AB9, 0x0ABD, 0x0ABD, 0x0AD0, 0x0AD0, 0x0AE0, 0x0AE1, + 0x0AF9, 0x0AF9, 0x0B05, 0x0B0C, 0x0B0F, 0x0B10, 0x0B13, 0x0B28, + 0x0B2A, 0x0B30, 0x0B32, 0x0B33, 0x0B35, 0x0B39, 0x0B3D, 0x0B3D, + 0x0B5C, 0x0B5D, 0x0B5F, 0x0B61, 0x0B71, 0x0B71, 0x0B83, 0x0B83, + 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, 0x0B92, 0x0B95, 0x0B99, 0x0B9A, + 0x0B9C, 0x0B9C, 0x0B9E, 0x0B9F, 0x0BA3, 0x0BA4, 0x0BA8, 0x0BAA, + 0x0BAE, 0x0BB9, 0x0BD0, 0x0BD0, 0x0C05, 0x0C0C, 0x0C0E, 0x0C10, + 0x0C12, 0x0C28, 0x0C2A, 0x0C39, 0x0C3D, 0x0C3D, 0x0C58, 0x0C5A, + 0x0C5C, 0x0C5D, 0x0C60, 0x0C61, 0x0C80, 0x0C80, 0x0C85, 0x0C8C, + 0x0C8E, 0x0C90, 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, 0x0CB5, 0x0CB9, + 0x0CBD, 0x0CBD, 0x0CDC, 0x0CDE, 0x0CE0, 0x0CE1, 0x0CF1, 0x0CF2, + 0x0D04, 0x0D0C, 0x0D0E, 0x0D10, 0x0D12, 0x0D3A, 0x0D3D, 0x0D3D, + 0x0D4E, 0x0D4E, 0x0D54, 0x0D56, 0x0D5F, 0x0D61, 0x0D7A, 0x0D7F, + 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, 0x0DB3, 0x0DBB, 0x0DBD, 0x0DBD, + 0x0DC0, 0x0DC6, 0x0E01, 0x0E30, 0x0E32, 0x0E32, 0x0E40, 0x0E46, + 0x0E81, 0x0E82, 0x0E84, 0x0E84, 0x0E86, 0x0E8A, 0x0E8C, 0x0EA3, + 0x0EA5, 0x0EA5, 0x0EA7, 0x0EB0, 0x0EB2, 0x0EB2, 0x0EBD, 0x0EBD, + 0x0EC0, 0x0EC4, 0x0EC6, 0x0EC6, 0x0EDC, 0x0EDF, 0x0F00, 0x0F00, + 0x0F40, 0x0F47, 0x0F49, 0x0F6C, 0x0F88, 0x0F8C, 0x1000, 0x102A, + 0x103F, 0x103F, 0x1050, 0x1055, 0x105A, 0x105D, 0x1061, 0x1061, + 0x1065, 0x1066, 0x106E, 0x1070, 0x1075, 0x1081, 0x108E, 0x108E, + 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, + 0x10FC, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, + 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, + 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, + 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, 0x1318, 0x135A, + 0x1380, 0x138F, 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0x1401, 0x166C, + 0x166F, 0x167F, 0x1681, 0x169A, 0x16A0, 0x16EA, 0x16EE, 0x16F8, + 0x1700, 0x1711, 0x171F, 0x1731, 0x1740, 0x1751, 0x1760, 0x176C, + 0x176E, 0x1770, 0x1780, 0x17B3, 0x17D7, 0x17D7, 0x17DC, 0x17DC, + 0x1820, 0x1878, 0x1880, 0x18A8, 0x18AA, 0x18AA, 0x18B0, 0x18F5, + 0x1900, 0x191E, 0x1950, 0x196D, 0x1970, 0x1974, 0x1980, 0x19AB, + 0x19B0, 0x19C9, 0x1A00, 0x1A16, 0x1A20, 0x1A54, 0x1AA7, 0x1AA7, + 0x1B05, 0x1B33, 0x1B45, 0x1B4C, 0x1B83, 0x1BA0, 0x1BAE, 0x1BAF, + 0x1BBA, 0x1BE5, 0x1C00, 0x1C23, 0x1C4D, 0x1C4F, 0x1C5A, 0x1C7D, + 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1CE9, 0x1CEC, + 0x1CEE, 0x1CF3, 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, + 0x1E00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, 0x1F48, 0x1F4D, + 0x1F50, 0x1F57, 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, + 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, + 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, + 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFC, 0x2071, 0x2071, + 0x207F, 0x207F, 0x2090, 0x209C, 0x2102, 0x2102, 0x2107, 0x2107, + 0x210A, 0x2113, 0x2115, 0x2115, 0x2118, 0x211D, 0x2124, 0x2124, + 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x2139, 0x213C, 0x213F, + 0x2145, 0x2149, 0x214E, 0x214E, 0x2160, 0x2188, 0x2C00, 0x2CE4, + 0x2CEB, 0x2CEE, 0x2CF2, 0x2CF3, 0x2D00, 0x2D25, 0x2D27, 0x2D27, + 0x2D2D, 0x2D2D, 0x2D30, 0x2D67, 0x2D6F, 0x2D6F, 0x2D80, 0x2D96, + 0x2DA0, 0x2DA6, 0x2DA8, 0x2DAE, 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, + 0x2DC0, 0x2DC6, 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, + 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303C, + 0x3041, 0x3096, 0x309D, 0x309F, 0x30A1, 0x30FA, 0x30FC, 0x30FF, + 0x3105, 0x312F, 0x3131, 0x318E, 0x31A0, 0x31BF, 0x31F0, 0x31FF, + 0x3400, 0x4DBF, 0x4E00, 0xA48C, 0xA4D0, 0xA4FD, 0xA500, 0xA60C, + 0xA610, 0xA61F, 0xA62A, 0xA62B, 0xA640, 0xA66E, 0xA67F, 0xA69D, + 0xA6A0, 0xA6EF, 0xA717, 0xA71F, 0xA722, 0xA788, 0xA78B, 0xA7DC, + 0xA7F1, 0xA801, 0xA803, 0xA805, 0xA807, 0xA80A, 0xA80C, 0xA822, + 0xA840, 0xA873, 0xA882, 0xA8B3, 0xA8F2, 0xA8F7, 0xA8FB, 0xA8FB, + 0xA8FD, 0xA8FE, 0xA90A, 0xA925, 0xA930, 0xA946, 0xA960, 0xA97C, + 0xA984, 0xA9B2, 0xA9CF, 0xA9CF, 0xA9E0, 0xA9E4, 0xA9E6, 0xA9EF, + 0xA9FA, 0xA9FE, 0xAA00, 0xAA28, 0xAA40, 0xAA42, 0xAA44, 0xAA4B, + 0xAA60, 0xAA76, 0xAA7A, 0xAA7A, 0xAA7E, 0xAAAF, 0xAAB1, 0xAAB1, + 0xAAB5, 0xAAB6, 0xAAB9, 0xAABD, 0xAAC0, 0xAAC0, 0xAAC2, 0xAAC2, + 0xAADB, 0xAADD, 0xAAE0, 0xAAEA, 0xAAF2, 0xAAF4, 0xAB01, 0xAB06, + 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, + 0xAB30, 0xAB5A, 0xAB5C, 0xAB69, 0xAB70, 0xABE2, 0xAC00, 0xD7A3, + 0xD7B0, 0xD7C6, 0xD7CB, 0xD7FB, 0xF900, 0xFA6D, 0xFA70, 0xFAD9, + 0xFB00, 0xFB06, 0xFB13, 0xFB17, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB28, + 0xFB2A, 0xFB36, 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, + 0xFB43, 0xFB44, 0xFB46, 0xFBB1, 0xFBD3, 0xFC5D, 0xFC64, 0xFD3D, + 0xFD50, 0xFD8F, 0xFD92, 0xFDC7, 0xFDF0, 0xFDF9, 0xFE71, 0xFE71, + 0xFE73, 0xFE73, 0xFE77, 0xFE77, 0xFE79, 0xFE79, 0xFE7B, 0xFE7B, + 0xFE7D, 0xFE7D, 0xFE7F, 0xFEFC, 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, + 0xFF66, 0xFF9D, 0xFFA0, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, + 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0x10000, 0x1000B, 0x1000D, 0x10026, + 0x10028, 0x1003A, 0x1003C, 0x1003D, 0x1003F, 0x1004D, 0x10050, 0x1005D, + 0x10080, 0x100FA, 0x10140, 0x10174, 0x10280, 0x1029C, 0x102A0, 0x102D0, + 0x10300, 0x1031F, 0x1032D, 0x1034A, 0x10350, 0x10375, 0x10380, 0x1039D, + 0x103A0, 0x103C3, 0x103C8, 0x103CF, 0x103D1, 0x103D5, 0x10400, 0x1049D, + 0x104B0, 0x104D3, 0x104D8, 0x104FB, 0x10500, 0x10527, 0x10530, 0x10563, + 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, + 0x10597, 0x105A1, 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, + 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, + 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10800, 0x10805, + 0x10808, 0x10808, 0x1080A, 0x10835, 0x10837, 0x10838, 0x1083C, 0x1083C, + 0x1083F, 0x10855, 0x10860, 0x10876, 0x10880, 0x1089E, 0x108E0, 0x108F2, + 0x108F4, 0x108F5, 0x10900, 0x10915, 0x10920, 0x10939, 0x10940, 0x10959, + 0x10980, 0x109B7, 0x109BE, 0x109BF, 0x10A00, 0x10A00, 0x10A10, 0x10A13, + 0x10A15, 0x10A17, 0x10A19, 0x10A35, 0x10A60, 0x10A7C, 0x10A80, 0x10A9C, + 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE4, 0x10B00, 0x10B35, 0x10B40, 0x10B55, + 0x10B60, 0x10B72, 0x10B80, 0x10B91, 0x10C00, 0x10C48, 0x10C80, 0x10CB2, + 0x10CC0, 0x10CF2, 0x10D00, 0x10D23, 0x10D4A, 0x10D65, 0x10D6F, 0x10D85, + 0x10E80, 0x10EA9, 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC7, 0x10F00, 0x10F1C, + 0x10F27, 0x10F27, 0x10F30, 0x10F45, 0x10F70, 0x10F81, 0x10FB0, 0x10FC4, + 0x10FE0, 0x10FF6, 0x11003, 0x11037, 0x11071, 0x11072, 0x11075, 0x11075, + 0x11083, 0x110AF, 0x110D0, 0x110E8, 0x11103, 0x11126, 0x11144, 0x11144, + 0x11147, 0x11147, 0x11150, 0x11172, 0x11176, 0x11176, 0x11183, 0x111B2, + 0x111C1, 0x111C4, 0x111DA, 0x111DA, 0x111DC, 0x111DC, 0x11200, 0x11211, + 0x11213, 0x1122B, 0x1123F, 0x11240, 0x11280, 0x11286, 0x11288, 0x11288, + 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A8, 0x112B0, 0x112DE, + 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, 0x1132A, 0x11330, + 0x11332, 0x11333, 0x11335, 0x11339, 0x1133D, 0x1133D, 0x11350, 0x11350, + 0x1135D, 0x11361, 0x11380, 0x11389, 0x1138B, 0x1138B, 0x1138E, 0x1138E, + 0x11390, 0x113B5, 0x113B7, 0x113B7, 0x113D1, 0x113D1, 0x113D3, 0x113D3, + 0x11400, 0x11434, 0x11447, 0x1144A, 0x1145F, 0x11461, 0x11480, 0x114AF, + 0x114C4, 0x114C5, 0x114C7, 0x114C7, 0x11580, 0x115AE, 0x115D8, 0x115DB, + 0x11600, 0x1162F, 0x11644, 0x11644, 0x11680, 0x116AA, 0x116B8, 0x116B8, + 0x11700, 0x1171A, 0x11740, 0x11746, 0x11800, 0x1182B, 0x118A0, 0x118DF, + 0x118FF, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, 0x11916, + 0x11918, 0x1192F, 0x1193F, 0x1193F, 0x11941, 0x11941, 0x119A0, 0x119A7, + 0x119AA, 0x119D0, 0x119E1, 0x119E1, 0x119E3, 0x119E3, 0x11A00, 0x11A00, + 0x11A0B, 0x11A32, 0x11A3A, 0x11A3A, 0x11A50, 0x11A50, 0x11A5C, 0x11A89, + 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, 0x11BC0, 0x11BE0, 0x11C00, 0x11C08, + 0x11C0A, 0x11C2E, 0x11C40, 0x11C40, 0x11C72, 0x11C8F, 0x11D00, 0x11D06, + 0x11D08, 0x11D09, 0x11D0B, 0x11D30, 0x11D46, 0x11D46, 0x11D60, 0x11D65, + 0x11D67, 0x11D68, 0x11D6A, 0x11D89, 0x11D98, 0x11D98, 0x11DB0, 0x11DDB, + 0x11EE0, 0x11EF2, 0x11F02, 0x11F02, 0x11F04, 0x11F10, 0x11F12, 0x11F33, + 0x11FB0, 0x11FB0, 0x12000, 0x12399, 0x12400, 0x1246E, 0x12480, 0x12543, + 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13441, 0x13446, 0x13460, 0x143FA, + 0x14400, 0x14646, 0x16100, 0x1611D, 0x16800, 0x16A38, 0x16A40, 0x16A5E, + 0x16A70, 0x16ABE, 0x16AD0, 0x16AED, 0x16B00, 0x16B2F, 0x16B40, 0x16B43, + 0x16B63, 0x16B77, 0x16B7D, 0x16B8F, 0x16D40, 0x16D6C, 0x16E40, 0x16E7F, + 0x16EA0, 0x16EB8, 0x16EBB, 0x16ED3, 0x16F00, 0x16F4A, 0x16F50, 0x16F50, + 0x16F93, 0x16F9F, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE3, 0x16FF2, 0x16FF6, + 0x17000, 0x18CD5, 0x18CFF, 0x18D1E, 0x18D80, 0x18DF2, 0x1AFF0, 0x1AFF3, + 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B122, 0x1B132, 0x1B132, + 0x1B150, 0x1B152, 0x1B155, 0x1B155, 0x1B164, 0x1B167, 0x1B170, 0x1B2FB, + 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, 0x1BC88, 0x1BC90, 0x1BC99, + 0x1D400, 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, + 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, 0x1D4BB, 0x1D4BB, + 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, + 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, + 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, 0x1D6A8, 0x1D6C0, + 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6FA, 0x1D6FC, 0x1D714, 0x1D716, 0x1D734, + 0x1D736, 0x1D74E, 0x1D750, 0x1D76E, 0x1D770, 0x1D788, 0x1D78A, 0x1D7A8, + 0x1D7AA, 0x1D7C2, 0x1D7C4, 0x1D7CB, 0x1DF00, 0x1DF1E, 0x1DF25, 0x1DF2A, + 0x1E030, 0x1E06D, 0x1E100, 0x1E12C, 0x1E137, 0x1E13D, 0x1E14E, 0x1E14E, + 0x1E290, 0x1E2AD, 0x1E2C0, 0x1E2EB, 0x1E4D0, 0x1E4EB, 0x1E5D0, 0x1E5ED, + 0x1E5F0, 0x1E5F0, 0x1E6C0, 0x1E6DE, 0x1E6E0, 0x1E6E2, 0x1E6E4, 0x1E6E5, + 0x1E6E7, 0x1E6ED, 0x1E6F0, 0x1E6F4, 0x1E6FE, 0x1E6FF, 0x1E7E0, 0x1E7E6, + 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, 0x1E7FE, 0x1E800, 0x1E8C4, + 0x1E900, 0x1E943, 0x1E94B, 0x1E94B, 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, + 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, + 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, + 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, 0x1EE4F, + 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, + 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, + 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, 0x1EE74, 0x1EE77, + 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, + 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, 0x20000, 0x2A6DF, + 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, + 0x2F800, 0x2FA1D, 0x30000, 0x3134A, 0x31350, 0x33479, + // #96 (16015+176): sc=Common:Zyyy + 0x0000, 0x0040, 0x005B, 0x0060, 0x007B, 0x00A9, 0x00AB, 0x00B9, + 0x00BB, 0x00BF, 0x00D7, 0x00D7, 0x00F7, 0x00F7, 0x02B9, 0x02DF, + 0x02E5, 0x02E9, 0x02EC, 0x02FF, 0x0374, 0x0374, 0x037E, 0x037E, + 0x0385, 0x0385, 0x0387, 0x0387, 0x0605, 0x0605, 0x060C, 0x060C, + 0x061B, 0x061B, 0x061F, 0x061F, 0x0640, 0x0640, 0x06DD, 0x06DD, + 0x08E2, 0x08E2, 0x0964, 0x0965, 0x0E3F, 0x0E3F, 0x0FD5, 0x0FD8, + 0x10FB, 0x10FB, 0x16EB, 0x16ED, 0x1735, 0x1736, 0x1802, 0x1803, + 0x1805, 0x1805, 0x1CD3, 0x1CD3, 0x1CE1, 0x1CE1, 0x1CE9, 0x1CEC, + 0x1CEE, 0x1CF3, 0x1CF5, 0x1CF7, 0x1CFA, 0x1CFA, 0x2000, 0x200B, + 0x200E, 0x2064, 0x2066, 0x2070, 0x2074, 0x207E, 0x2080, 0x208E, + 0x20A0, 0x20C1, 0x2100, 0x2125, 0x2127, 0x2129, 0x212C, 0x2131, + 0x2133, 0x214D, 0x214F, 0x215F, 0x2189, 0x218B, 0x2190, 0x2429, + 0x2440, 0x244A, 0x2460, 0x27FF, 0x2900, 0x2B73, 0x2B76, 0x2BFF, + 0x2E00, 0x2E5D, 0x2FF0, 0x3004, 0x3006, 0x3006, 0x3008, 0x3020, + 0x3030, 0x3037, 0x303C, 0x303F, 0x309B, 0x309C, 0x30A0, 0x30A0, + 0x30FB, 0x30FC, 0x3190, 0x319F, 0x31C0, 0x31E5, 0x31EF, 0x31EF, + 0x3220, 0x325F, 0x327F, 0x32CF, 0x32FF, 0x32FF, 0x3358, 0x33FF, + 0x4DC0, 0x4DFF, 0xA700, 0xA721, 0xA788, 0xA78A, 0xA830, 0xA839, + 0xA92E, 0xA92E, 0xA9CF, 0xA9CF, 0xAB5B, 0xAB5B, 0xAB6A, 0xAB6B, + 0xFD3E, 0xFD3F, 0xFE10, 0xFE19, 0xFE30, 0xFE52, 0xFE54, 0xFE66, + 0xFE68, 0xFE6B, 0xFEFF, 0xFEFF, 0xFF01, 0xFF20, 0xFF3B, 0xFF40, + 0xFF5B, 0xFF65, 0xFF70, 0xFF70, 0xFF9E, 0xFF9F, 0xFFE0, 0xFFE6, + 0xFFE8, 0xFFEE, 0xFFF9, 0xFFFD, 0x10100, 0x10102, 0x10107, 0x10133, + 0x10137, 0x1013F, 0x10190, 0x1019C, 0x101D0, 0x101FC, 0x102E1, 0x102FB, + 0x1BCA0, 0x1BCA3, 0x1CC00, 0x1CCFC, 0x1CD00, 0x1CEB3, 0x1CEBA, 0x1CED0, + 0x1CEE0, 0x1CEF0, 0x1CF50, 0x1CFC3, 0x1D000, 0x1D0F5, 0x1D100, 0x1D126, + 0x1D129, 0x1D166, 0x1D16A, 0x1D17A, 0x1D183, 0x1D184, 0x1D18C, 0x1D1A9, + 0x1D1AE, 0x1D1EA, 0x1D2C0, 0x1D2D3, 0x1D2E0, 0x1D2F3, 0x1D300, 0x1D356, + 0x1D360, 0x1D378, 0x1D400, 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, + 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, + 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, + 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, + 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, + 0x1D6A8, 0x1D7CB, 0x1D7CE, 0x1D7FF, 0x1EC71, 0x1ECB4, 0x1ED01, 0x1ED3D, + 0x1F000, 0x1F02B, 0x1F030, 0x1F093, 0x1F0A0, 0x1F0AE, 0x1F0B1, 0x1F0BF, + 0x1F0C1, 0x1F0CF, 0x1F0D1, 0x1F0F5, 0x1F100, 0x1F1AD, 0x1F1E6, 0x1F1FF, + 0x1F201, 0x1F202, 0x1F210, 0x1F23B, 0x1F240, 0x1F248, 0x1F250, 0x1F251, + 0x1F260, 0x1F265, 0x1F300, 0x1F6D8, 0x1F6DC, 0x1F6EC, 0x1F6F0, 0x1F6FC, + 0x1F700, 0x1F7D9, 0x1F7E0, 0x1F7EB, 0x1F7F0, 0x1F7F0, 0x1F800, 0x1F80B, + 0x1F810, 0x1F847, 0x1F850, 0x1F859, 0x1F860, 0x1F887, 0x1F890, 0x1F8AD, + 0x1F8B0, 0x1F8BB, 0x1F8C0, 0x1F8C1, 0x1F8D0, 0x1F8D8, 0x1F900, 0x1FA57, + 0x1FA60, 0x1FA6D, 0x1FA70, 0x1FA7C, 0x1FA80, 0x1FA8A, 0x1FA8E, 0x1FAC6, + 0x1FAC8, 0x1FAC8, 0x1FACD, 0x1FADC, 0x1FADF, 0x1FAEA, 0x1FAEF, 0x1FAF8, + 0x1FB00, 0x1FB92, 0x1FB94, 0x1FBFA, 0xE0001, 0xE0001, 0xE0020, 0xE007F, + // #97 (16191+36): sc=Latin:Latn + 0x0041, 0x005A, 0x0061, 0x007A, 0x00AA, 0x00AA, 0x00BA, 0x00BA, + 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x02B8, 0x02E0, 0x02E4, + 0x1D00, 0x1D25, 0x1D2C, 0x1D5C, 0x1D62, 0x1D65, 0x1D6B, 0x1D77, + 0x1D79, 0x1DBE, 0x1E00, 0x1EFF, 0x2071, 0x2071, 0x207F, 0x207F, + 0x2090, 0x209C, 0x212A, 0x212B, 0x2132, 0x2132, 0x214E, 0x214E, + 0x2160, 0x2188, 0x2C60, 0x2C7F, 0xA722, 0xA787, 0xA78B, 0xA7DC, + 0xA7F1, 0xA7FF, 0xAB30, 0xAB5A, 0xAB5C, 0xAB64, 0xAB66, 0xAB69, + 0xFB00, 0xFB06, 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, 0x10780, 0x10785, + 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x1DF00, 0x1DF1E, 0x1DF25, 0x1DF2A, + // #98 (16227+36): sc=Greek:Grek + 0x0370, 0x0373, 0x0375, 0x0377, 0x037A, 0x037D, 0x037F, 0x037F, + 0x0384, 0x0384, 0x0386, 0x0386, 0x0388, 0x038A, 0x038C, 0x038C, + 0x038E, 0x03A1, 0x03A3, 0x03E1, 0x03F0, 0x03FF, 0x1D26, 0x1D2A, + 0x1D5D, 0x1D61, 0x1D66, 0x1D6A, 0x1DBF, 0x1DBF, 0x1F00, 0x1F15, + 0x1F18, 0x1F1D, 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, 0x1F57, + 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, + 0x1F80, 0x1FB4, 0x1FB6, 0x1FC4, 0x1FC6, 0x1FD3, 0x1FD6, 0x1FDB, + 0x1FDD, 0x1FEF, 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFE, 0x2126, 0x2126, + 0xAB65, 0xAB65, 0x10140, 0x1018E, 0x101A0, 0x101A0, 0x1D200, 0x1D245, + // #99 (16263+10): sc=Cyrillic:Cyrl + 0x0400, 0x0484, 0x0487, 0x052F, 0x1C80, 0x1C8A, 0x1D2B, 0x1D2B, + 0x1D78, 0x1D78, 0x2DE0, 0x2DFF, 0xA640, 0xA69F, 0xFE2E, 0xFE2F, + 0x1E030, 0x1E06D, 0x1E08F, 0x1E08F, + // #100 (16273+4): sc=Armenian:Armn + 0x0531, 0x0556, 0x0559, 0x058A, 0x058D, 0x058F, 0xFB13, 0xFB17, + // #101 (16277+9): sc=Hebrew:Hebr + 0x0591, 0x05C7, 0x05D0, 0x05EA, 0x05EF, 0x05F4, 0xFB1D, 0xFB36, + 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, + 0xFB46, 0xFB4F, + // #102 (16286+56): sc=Arabic:Arab + 0x0600, 0x0604, 0x0606, 0x060B, 0x060D, 0x061A, 0x061C, 0x061E, + 0x0620, 0x063F, 0x0641, 0x064A, 0x0656, 0x066F, 0x0671, 0x06DC, + 0x06DE, 0x06FF, 0x0750, 0x077F, 0x0870, 0x0891, 0x0897, 0x08E1, + 0x08E3, 0x08FF, 0xFB50, 0xFD3D, 0xFD40, 0xFDCF, 0xFDF0, 0xFDFF, + 0xFE70, 0xFE74, 0xFE76, 0xFEFC, 0x10E60, 0x10E7E, 0x10EC2, 0x10EC7, + 0x10ED0, 0x10ED8, 0x10EFA, 0x10EFF, 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, + 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, + 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, + 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, 0x1EE4F, + 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, + 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, + 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, 0x1EE74, 0x1EE77, + 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, + 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, 0x1EEF0, 0x1EEF1, + // #103 (16342+4): sc=Syriac:Syrc + 0x0700, 0x070D, 0x070F, 0x074A, 0x074D, 0x074F, 0x0860, 0x086A, + // #104 (16346+1): sc=Thaana:Thaa + 0x0780, 0x07B1, + // #105 (16347+5): sc=Devanagari:Deva + 0x0900, 0x0950, 0x0955, 0x0963, 0x0966, 0x097F, 0xA8E0, 0xA8FF, + 0x11B00, 0x11B09, + // #106 (16352+14): sc=Bengali:Beng + 0x0980, 0x0983, 0x0985, 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, + 0x09AA, 0x09B0, 0x09B2, 0x09B2, 0x09B6, 0x09B9, 0x09BC, 0x09C4, + 0x09C7, 0x09C8, 0x09CB, 0x09CE, 0x09D7, 0x09D7, 0x09DC, 0x09DD, + 0x09DF, 0x09E3, 0x09E6, 0x09FE, + // #107 (16366+16): sc=Gurmukhi:Guru + 0x0A01, 0x0A03, 0x0A05, 0x0A0A, 0x0A0F, 0x0A10, 0x0A13, 0x0A28, + 0x0A2A, 0x0A30, 0x0A32, 0x0A33, 0x0A35, 0x0A36, 0x0A38, 0x0A39, + 0x0A3C, 0x0A3C, 0x0A3E, 0x0A42, 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, + 0x0A51, 0x0A51, 0x0A59, 0x0A5C, 0x0A5E, 0x0A5E, 0x0A66, 0x0A76, + // #108 (16382+14): sc=Gujarati:Gujr + 0x0A81, 0x0A83, 0x0A85, 0x0A8D, 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, + 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, 0x0AB5, 0x0AB9, 0x0ABC, 0x0AC5, + 0x0AC7, 0x0AC9, 0x0ACB, 0x0ACD, 0x0AD0, 0x0AD0, 0x0AE0, 0x0AE3, + 0x0AE6, 0x0AF1, 0x0AF9, 0x0AFF, + // #109 (16396+14): sc=Oriya:Orya + 0x0B01, 0x0B03, 0x0B05, 0x0B0C, 0x0B0F, 0x0B10, 0x0B13, 0x0B28, + 0x0B2A, 0x0B30, 0x0B32, 0x0B33, 0x0B35, 0x0B39, 0x0B3C, 0x0B44, + 0x0B47, 0x0B48, 0x0B4B, 0x0B4D, 0x0B55, 0x0B57, 0x0B5C, 0x0B5D, + 0x0B5F, 0x0B63, 0x0B66, 0x0B77, + // #110 (16410+18): sc=Tamil:Taml + 0x0B82, 0x0B83, 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, 0x0B92, 0x0B95, + 0x0B99, 0x0B9A, 0x0B9C, 0x0B9C, 0x0B9E, 0x0B9F, 0x0BA3, 0x0BA4, + 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB9, 0x0BBE, 0x0BC2, 0x0BC6, 0x0BC8, + 0x0BCA, 0x0BCD, 0x0BD0, 0x0BD0, 0x0BD7, 0x0BD7, 0x0BE6, 0x0BFA, + 0x11FC0, 0x11FF1, 0x11FFF, 0x11FFF, + // #111 (16428+13): sc=Telugu:Telu + 0x0C00, 0x0C0C, 0x0C0E, 0x0C10, 0x0C12, 0x0C28, 0x0C2A, 0x0C39, + 0x0C3C, 0x0C44, 0x0C46, 0x0C48, 0x0C4A, 0x0C4D, 0x0C55, 0x0C56, + 0x0C58, 0x0C5A, 0x0C5C, 0x0C5D, 0x0C60, 0x0C63, 0x0C66, 0x0C6F, + 0x0C77, 0x0C7F, + // #112 (16441+13): sc=Kannada:Knda + 0x0C80, 0x0C8C, 0x0C8E, 0x0C90, 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, + 0x0CB5, 0x0CB9, 0x0CBC, 0x0CC4, 0x0CC6, 0x0CC8, 0x0CCA, 0x0CCD, + 0x0CD5, 0x0CD6, 0x0CDC, 0x0CDE, 0x0CE0, 0x0CE3, 0x0CE6, 0x0CEF, + 0x0CF1, 0x0CF3, + // #113 (16454+7): sc=Malayalam:Mlym + 0x0D00, 0x0D0C, 0x0D0E, 0x0D10, 0x0D12, 0x0D44, 0x0D46, 0x0D48, + 0x0D4A, 0x0D4F, 0x0D54, 0x0D63, 0x0D66, 0x0D7F, + // #114 (16461+13): sc=Sinhala:Sinh + 0x0D81, 0x0D83, 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, 0x0DB3, 0x0DBB, + 0x0DBD, 0x0DBD, 0x0DC0, 0x0DC6, 0x0DCA, 0x0DCA, 0x0DCF, 0x0DD4, + 0x0DD6, 0x0DD6, 0x0DD8, 0x0DDF, 0x0DE6, 0x0DEF, 0x0DF2, 0x0DF4, + 0x111E1, 0x111F4, + // #115 (16474+2): sc=Thai + 0x0E01, 0x0E3A, 0x0E40, 0x0E5B, + // #116 (16476+11): sc=Lao:Laoo scx=Lao:Laoo + 0x0E81, 0x0E82, 0x0E84, 0x0E84, 0x0E86, 0x0E8A, 0x0E8C, 0x0EA3, + 0x0EA5, 0x0EA5, 0x0EA7, 0x0EBD, 0x0EC0, 0x0EC4, 0x0EC6, 0x0EC6, + 0x0EC8, 0x0ECE, 0x0ED0, 0x0ED9, 0x0EDC, 0x0EDF, + // #117 (16487+7): sc=Tibetan:Tibt + 0x0F00, 0x0F47, 0x0F49, 0x0F6C, 0x0F71, 0x0F97, 0x0F99, 0x0FBC, + 0x0FBE, 0x0FCC, 0x0FCE, 0x0FD4, 0x0FD9, 0x0FDA, + // #118 (16494+4): sc=Myanmar:Mymr + 0x1000, 0x109F, 0xA9E0, 0xA9FE, 0xAA60, 0xAA7F, 0x116D0, 0x116E3, + // #119 (16498+10): sc=Georgian:Geor + 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, + 0x10FC, 0x10FF, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x2D00, 0x2D25, + 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, + // #120 (16508+14): sc=Hangul:Hang + 0x1100, 0x11FF, 0x302E, 0x302F, 0x3131, 0x318E, 0x3200, 0x321E, + 0x3260, 0x327E, 0xA960, 0xA97C, 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, + 0xD7CB, 0xD7FB, 0xFFA0, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, + 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, + // #121 (16522+36): sc=Ethiopic:Ethi + 0x1200, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, + 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, + 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, + 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, 0x1318, 0x135A, + 0x135D, 0x137C, 0x1380, 0x1399, 0x2D80, 0x2D96, 0x2DA0, 0x2DA6, + 0x2DA8, 0x2DAE, 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, + 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, 0xAB01, 0xAB06, + 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, + 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, 0x1E7FE, + // #122 (16558+3): sc=Cherokee:Cher + 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0xAB70, 0xABBF, + // #123 (16561+3): sc=Canadian_Aboriginal:Cans scx=Canadian_Aboriginal:Cans + 0x1400, 0x167F, 0x18B0, 0x18F5, 0x11AB0, 0x11ABF, + // #124 (16564+1): sc=Ogham:Ogam scx=Ogham:Ogam + 0x1680, 0x169C, + // #125 (16565+2): sc=Runic:Runr + 0x16A0, 0x16EA, 0x16EE, 0x16F8, + // #126 (16567+4): sc=Khmer:Khmr scx=Khmer:Khmr + 0x1780, 0x17DD, 0x17E0, 0x17E9, 0x17F0, 0x17F9, 0x19E0, 0x19FF, + // #127 (16571+6): sc=Mongolian:Mong + 0x1800, 0x1801, 0x1804, 0x1804, 0x1806, 0x1819, 0x1820, 0x1878, + 0x1880, 0x18AA, 0x11660, 0x1166C, + // #128 (16577+6): sc=Hiragana:Hira + 0x3041, 0x3096, 0x309D, 0x309F, 0x1B001, 0x1B11F, 0x1B132, 0x1B132, + 0x1B150, 0x1B152, 0x1F200, 0x1F200, + // #129 (16583+14): sc=Katakana:Kana + 0x30A1, 0x30FA, 0x30FD, 0x30FF, 0x31F0, 0x31FF, 0x32D0, 0x32FE, + 0x3300, 0x3357, 0xFF66, 0xFF6F, 0xFF71, 0xFF9D, 0x1AFF0, 0x1AFF3, + 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B000, 0x1B120, 0x1B122, + 0x1B155, 0x1B155, 0x1B164, 0x1B167, + // #130 (16597+3): sc=Bopomofo:Bopo + 0x02EA, 0x02EB, 0x3105, 0x312F, 0x31A0, 0x31BF, + // #131 (16600+21): sc=Han:Hani + 0x2E80, 0x2E99, 0x2E9B, 0x2EF3, 0x2F00, 0x2FD5, 0x3005, 0x3005, + 0x3007, 0x3007, 0x3021, 0x3029, 0x3038, 0x303B, 0x3400, 0x4DBF, + 0x4E00, 0x9FFF, 0xF900, 0xFA6D, 0xFA70, 0xFAD9, 0x16FE2, 0x16FE3, + 0x16FF0, 0x16FF6, 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, + 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, + 0x31350, 0x33479, + // #132 (16621+2): sc=Yi:Yiii + 0xA000, 0xA48C, 0xA490, 0xA4C6, + // #133 (16623+2): sc=Old_Italic:Ital scx=Old_Italic:Ital + 0x10300, 0x10323, 0x1032D, 0x1032F, + // #134 (16625+1): sc=Gothic:Goth + 0x10330, 0x1034A, + // #135 (16626+1): sc=Deseret:Dsrt scx=Deseret:Dsrt + 0x10400, 0x1044F, + // #136 (16627+30): sc=Inherited:Zinh:Qaai + 0x0300, 0x036F, 0x0485, 0x0486, 0x064B, 0x0655, 0x0670, 0x0670, + 0x0951, 0x0954, 0x1AB0, 0x1ADD, 0x1AE0, 0x1AEB, 0x1CD0, 0x1CD2, + 0x1CD4, 0x1CE0, 0x1CE2, 0x1CE8, 0x1CED, 0x1CED, 0x1CF4, 0x1CF4, + 0x1CF8, 0x1CF9, 0x1DC0, 0x1DFF, 0x200C, 0x200D, 0x20D0, 0x20F0, + 0x302A, 0x302D, 0x3099, 0x309A, 0xFE00, 0xFE0F, 0xFE20, 0xFE2D, + 0x101FD, 0x101FD, 0x102E0, 0x102E0, 0x1133B, 0x1133B, 0x1CF00, 0x1CF2D, + 0x1CF30, 0x1CF46, 0x1D167, 0x1D169, 0x1D17B, 0x1D182, 0x1D185, 0x1D18B, + 0x1D1AA, 0x1D1AD, 0xE0100, 0xE01EF, + // #137 (16657+2): sc=Tagalog:Tglg + 0x1700, 0x1715, 0x171F, 0x171F, + // #138 (16659+1): sc=Hanunoo:Hano + 0x1720, 0x1734, + // #139 (16660+1): sc=Buhid:Buhd + 0x1740, 0x1753, + // #140 (16661+3): sc=Tagbanwa:Tagb + 0x1760, 0x176C, 0x176E, 0x1770, 0x1772, 0x1773, + // #141 (16664+5): sc=Limbu:Limb + 0x1900, 0x191E, 0x1920, 0x192B, 0x1930, 0x193B, 0x1940, 0x1940, + 0x1944, 0x194F, + // #142 (16669+2): sc=Tai_Le:Tale + 0x1950, 0x196D, 0x1970, 0x1974, + // #143 (16671+7): sc=Linear_B:Linb + 0x10000, 0x1000B, 0x1000D, 0x10026, 0x10028, 0x1003A, 0x1003C, 0x1003D, + 0x1003F, 0x1004D, 0x10050, 0x1005D, 0x10080, 0x100FA, + // #144 (16678+2): sc=Ugaritic:Ugar scx=Ugaritic:Ugar + 0x10380, 0x1039D, 0x1039F, 0x1039F, + // #145 (16680+1): sc=Shavian:Shaw + 0x10450, 0x1047F, + // #146 (16681+2): sc=Osmanya:Osma scx=Osmanya:Osma + 0x10480, 0x1049D, 0x104A0, 0x104A9, + // #147 (16683+6): sc=Cypriot:Cprt + 0x10800, 0x10805, 0x10808, 0x10808, 0x1080A, 0x10835, 0x10837, 0x10838, + 0x1083C, 0x1083C, 0x1083F, 0x1083F, + // #148 (16689+1): sc=Braille:Brai scx=Braille:Brai + 0x2800, 0x28FF, + // #149 (16690+2): sc=Buginese:Bugi + 0x1A00, 0x1A1B, 0x1A1E, 0x1A1F, + // #150 (16692+3): sc=Coptic:Copt:Qaac + 0x03E2, 0x03EF, 0x2C80, 0x2CF3, 0x2CF9, 0x2CFF, + // #151 (16695+4): sc=New_Tai_Lue:Talu scx=New_Tai_Lue:Talu + 0x1980, 0x19AB, 0x19B0, 0x19C9, 0x19D0, 0x19DA, 0x19DE, 0x19DF, + // #152 (16699+6): sc=Glagolitic:Glag + 0x2C00, 0x2C5F, 0x1E000, 0x1E006, 0x1E008, 0x1E018, 0x1E01B, 0x1E021, + 0x1E023, 0x1E024, 0x1E026, 0x1E02A, + // #153 (16705+3): sc=Tifinagh:Tfng + 0x2D30, 0x2D67, 0x2D6F, 0x2D70, 0x2D7F, 0x2D7F, + // #154 (16708+1): sc=Syloti_Nagri:Sylo + 0xA800, 0xA82C, + // #155 (16709+2): sc=Old_Persian:Xpeo scx=Old_Persian:Xpeo + 0x103A0, 0x103C3, 0x103C8, 0x103D5, + // #156 (16711+8): sc=Kharoshthi:Khar scx=Kharoshthi:Khar + 0x10A00, 0x10A03, 0x10A05, 0x10A06, 0x10A0C, 0x10A13, 0x10A15, 0x10A17, + 0x10A19, 0x10A35, 0x10A38, 0x10A3A, 0x10A3F, 0x10A48, 0x10A50, 0x10A58, + // #157 (16719+2): sc=Balinese:Bali scx=Balinese:Bali + 0x1B00, 0x1B4C, 0x1B4E, 0x1B7F, + // #158 (16721+4): sc=Cuneiform:Xsux scx=Cuneiform:Xsux + 0x12000, 0x12399, 0x12400, 0x1246E, 0x12470, 0x12474, 0x12480, 0x12543, + // #159 (16725+2): sc=Phoenician:Phnx scx=Phoenician:Phnx + 0x10900, 0x1091B, 0x1091F, 0x1091F, + // #160 (16727+1): sc=Phags_Pa:Phag + 0xA840, 0xA877, + // #161 (16728+2): sc=Nko:Nkoo + 0x07C0, 0x07FA, 0x07FD, 0x07FF, + // #162 (16730+2): sc=Sundanese:Sund scx=Sundanese:Sund + 0x1B80, 0x1BBF, 0x1CC0, 0x1CC7, + // #163 (16732+3): sc=Lepcha:Lepc scx=Lepcha:Lepc + 0x1C00, 0x1C37, 0x1C3B, 0x1C49, 0x1C4D, 0x1C4F, + // #164 (16735+1): sc=Ol_Chiki:Olck scx=Ol_Chiki:Olck + 0x1C50, 0x1C7F, + // #165 (16736+1): sc=Vai:Vaii scx=Vai:Vaii + 0xA500, 0xA62B, + // #166 (16737+2): sc=Saurashtra:Saur scx=Saurashtra:Saur + 0xA880, 0xA8C5, 0xA8CE, 0xA8D9, + // #167 (16739+2): sc=Kayah_Li:Kali + 0xA900, 0xA92D, 0xA92F, 0xA92F, + // #168 (16741+2): sc=Rejang:Rjng scx=Rejang:Rjng + 0xA930, 0xA953, 0xA95F, 0xA95F, + // #169 (16743+1): sc=Lycian:Lyci + 0x10280, 0x1029C, + // #170 (16744+1): sc=Carian:Cari + 0x102A0, 0x102D0, + // #171 (16745+2): sc=Lydian:Lydi + 0x10920, 0x10939, 0x1093F, 0x1093F, + // #172 (16747+4): sc=Cham scx=Cham + 0xAA00, 0xAA36, 0xAA40, 0xAA4D, 0xAA50, 0xAA59, 0xAA5C, 0xAA5F, + // #173 (16751+5): sc=Tai_Tham:Lana scx=Tai_Tham:Lana + 0x1A20, 0x1A5E, 0x1A60, 0x1A7C, 0x1A7F, 0x1A89, 0x1A90, 0x1A99, + 0x1AA0, 0x1AAD, + // #174 (16756+2): sc=Tai_Viet:Tavt scx=Tai_Viet:Tavt + 0xAA80, 0xAAC2, 0xAADB, 0xAADF, + // #175 (16758+2): sc=Avestan:Avst + 0x10B00, 0x10B35, 0x10B39, 0x10B3F, + // #176 (16760+2): sc=Egyptian_Hieroglyphs:Egyp scx=Egyptian_Hieroglyphs:Egyp + 0x13000, 0x13455, 0x13460, 0x143FA, + // #177 (16762+2): sc=Samaritan:Samr + 0x0800, 0x082D, 0x0830, 0x083E, + // #178 (16764+2): sc=Lisu + 0xA4D0, 0xA4FF, 0x11FB0, 0x11FB0, + // #179 (16766+2): sc=Bamum:Bamu scx=Bamum:Bamu + 0xA6A0, 0xA6F7, 0x16800, 0x16A38, + // #180 (16768+3): sc=Javanese:Java + 0xA980, 0xA9CD, 0xA9D0, 0xA9D9, 0xA9DE, 0xA9DF, + // #181 (16771+3): sc=Meetei_Mayek:Mtei scx=Meetei_Mayek:Mtei + 0xAAE0, 0xAAF6, 0xABC0, 0xABED, 0xABF0, 0xABF9, + // #182 (16774+2): sc=Imperial_Aramaic:Armi scx=Imperial_Aramaic:Armi + 0x10840, 0x10855, 0x10857, 0x1085F, + // #183 (16776+1): sc=Old_South_Arabian:Sarb scx=Old_South_Arabian:Sarb + 0x10A60, 0x10A7F, + // #184 (16777+2): sc=Inscriptional_Parthian:Prti scx=Inscriptional_Parthian:Prti + 0x10B40, 0x10B55, 0x10B58, 0x10B5F, + // #185 (16779+2): sc=Inscriptional_Pahlavi:Phli scx=Inscriptional_Pahlavi:Phli + 0x10B60, 0x10B72, 0x10B78, 0x10B7F, + // #186 (16781+1): sc=Old_Turkic:Orkh + 0x10C00, 0x10C48, + // #187 (16782+2): sc=Kaithi:Kthi + 0x11080, 0x110C2, 0x110CD, 0x110CD, + // #188 (16784+2): sc=Batak:Batk scx=Batak:Batk + 0x1BC0, 0x1BF3, 0x1BFC, 0x1BFF, + // #189 (16786+3): sc=Brahmi:Brah scx=Brahmi:Brah + 0x11000, 0x1104D, 0x11052, 0x11075, 0x1107F, 0x1107F, + // #190 (16789+2): sc=Mandaic:Mand + 0x0840, 0x085B, 0x085E, 0x085E, + // #191 (16791+2): sc=Chakma:Cakm + 0x11100, 0x11134, 0x11136, 0x11147, + // #192 (16793+3): sc=Meroitic_Cursive:Merc scx=Meroitic_Cursive:Merc + 0x109A0, 0x109B7, 0x109BC, 0x109CF, 0x109D2, 0x109FF, + // #193 (16796+1): sc=Meroitic_Hieroglyphs:Mero + 0x10980, 0x1099F, + // #194 (16797+3): sc=Miao:Plrd scx=Miao:Plrd + 0x16F00, 0x16F4A, 0x16F4F, 0x16F87, 0x16F8F, 0x16F9F, + // #195 (16800+2): sc=Sharada:Shrd + 0x11180, 0x111DF, 0x11B60, 0x11B67, + // #196 (16802+2): sc=Sora_Sompeng:Sora scx=Sora_Sompeng:Sora + 0x110D0, 0x110E8, 0x110F0, 0x110F9, + // #197 (16804+2): sc=Takri:Takr + 0x11680, 0x116B9, 0x116C0, 0x116C9, + // #198 (16806+2): sc=Caucasian_Albanian:Aghb + 0x10530, 0x10563, 0x1056F, 0x1056F, + // #199 (16808+2): sc=Bassa_Vah:Bass scx=Bassa_Vah:Bass + 0x16AD0, 0x16AED, 0x16AF0, 0x16AF5, + // #200 (16810+5): sc=Duployan:Dupl + 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, 0x1BC88, 0x1BC90, 0x1BC99, + 0x1BC9C, 0x1BC9F, + // #201 (16815+1): sc=Elbasan:Elba + 0x10500, 0x10527, + // #202 (16816+15): sc=Grantha:Gran + 0x11300, 0x11303, 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, + 0x1132A, 0x11330, 0x11332, 0x11333, 0x11335, 0x11339, 0x1133C, 0x11344, + 0x11347, 0x11348, 0x1134B, 0x1134D, 0x11350, 0x11350, 0x11357, 0x11357, + 0x1135D, 0x11363, 0x11366, 0x1136C, 0x11370, 0x11374, + // #203 (16831+5): sc=Pahawh_Hmong:Hmng scx=Pahawh_Hmong:Hmng + 0x16B00, 0x16B45, 0x16B50, 0x16B59, 0x16B5B, 0x16B61, 0x16B63, 0x16B77, + 0x16B7D, 0x16B8F, + // #204 (16836+2): sc=Khojki:Khoj + 0x11200, 0x11211, 0x11213, 0x11241, + // #205 (16838+3): sc=Linear_A:Lina + 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, + // #206 (16841+1): sc=Mahajani:Mahj + 0x11150, 0x11176, + // #207 (16842+2): sc=Manichaean:Mani + 0x10AC0, 0x10AE6, 0x10AEB, 0x10AF6, + // #208 (16844+2): sc=Mende_Kikakui:Mend scx=Mende_Kikakui:Mend + 0x1E800, 0x1E8C4, 0x1E8C7, 0x1E8D6, + // #209 (16846+2): sc=Modi + 0x11600, 0x11644, 0x11650, 0x11659, + // #210 (16848+3): sc=Mro:Mroo scx=Mro:Mroo + 0x16A40, 0x16A5E, 0x16A60, 0x16A69, 0x16A6E, 0x16A6F, + // #211 (16851+1): sc=Old_North_Arabian:Narb scx=Old_North_Arabian:Narb + 0x10A80, 0x10A9F, + // #212 (16852+2): sc=Nabataean:Nbat scx=Nabataean:Nbat + 0x10880, 0x1089E, 0x108A7, 0x108AF, + // #213 (16854+1): sc=Palmyrene:Palm scx=Palmyrene:Palm + 0x10860, 0x1087F, + // #214 (16855+1): sc=Pau_Cin_Hau:Pauc scx=Pau_Cin_Hau:Pauc + 0x11AC0, 0x11AF8, + // #215 (16856+1): sc=Old_Permic:Perm + 0x10350, 0x1037A, + // #216 (16857+3): sc=Psalter_Pahlavi:Phlp + 0x10B80, 0x10B91, 0x10B99, 0x10B9C, 0x10BA9, 0x10BAF, + // #217 (16860+2): sc=Siddham:Sidd scx=Siddham:Sidd + 0x11580, 0x115B5, 0x115B8, 0x115DD, + // #218 (16862+2): sc=Khudawadi:Sind + 0x112B0, 0x112EA, 0x112F0, 0x112F9, + // #219 (16864+2): sc=Tirhuta:Tirh + 0x11480, 0x114C7, 0x114D0, 0x114D9, + // #220 (16866+2): sc=Warang_Citi:Wara scx=Warang_Citi:Wara + 0x118A0, 0x118F2, 0x118FF, 0x118FF, + // #221 (16868+3): sc=Ahom scx=Ahom + 0x11700, 0x1171A, 0x1171D, 0x1172B, 0x11730, 0x11746, + // #222 (16871+1): sc=Anatolian_Hieroglyphs:Hluw scx=Anatolian_Hieroglyphs:Hluw + 0x14400, 0x14646, + // #223 (16872+3): sc=Hatran:Hatr scx=Hatran:Hatr + 0x108E0, 0x108F2, 0x108F4, 0x108F5, 0x108FB, 0x108FF, + // #224 (16875+5): sc=Multani:Mult + 0x11280, 0x11286, 0x11288, 0x11288, 0x1128A, 0x1128D, 0x1128F, 0x1129D, + 0x1129F, 0x112A9, + // #225 (16880+3): sc=Old_Hungarian:Hung + 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, 0x10CFA, 0x10CFF, + // #226 (16883+3): sc=SignWriting:Sgnw scx=SignWriting:Sgnw + 0x1D800, 0x1DA8B, 0x1DA9B, 0x1DA9F, 0x1DAA1, 0x1DAAF, + // #227 (16886+3): sc=Adlam:Adlm + 0x1E900, 0x1E94B, 0x1E950, 0x1E959, 0x1E95E, 0x1E95F, + // #228 (16889+4): sc=Bhaiksuki:Bhks scx=Bhaiksuki:Bhks + 0x11C00, 0x11C08, 0x11C0A, 0x11C36, 0x11C38, 0x11C45, 0x11C50, 0x11C6C, + // #229 (16893+3): sc=Marchen:Marc scx=Marchen:Marc + 0x11C70, 0x11C8F, 0x11C92, 0x11CA7, 0x11CA9, 0x11CB6, + // #230 (16896+2): sc=Newa + 0x11400, 0x1145B, 0x1145D, 0x11461, + // #231 (16898+2): sc=Osage:Osge + 0x104B0, 0x104D3, 0x104D8, 0x104FB, + // #232 (16900+4): sc=Tangut:Tang + 0x16FE0, 0x16FE0, 0x17000, 0x18AFF, 0x18D00, 0x18D1E, 0x18D80, 0x18DF2, + // #233 (16904+7): sc=Masaram_Gondi:Gonm + 0x11D00, 0x11D06, 0x11D08, 0x11D09, 0x11D0B, 0x11D36, 0x11D3A, 0x11D3A, + 0x11D3C, 0x11D3D, 0x11D3F, 0x11D47, 0x11D50, 0x11D59, + // #234 (16911+2): sc=Nushu:Nshu scx=Nushu:Nshu + 0x16FE1, 0x16FE1, 0x1B170, 0x1B2FB, + // #235 (16913+1): sc=Soyombo:Soyo scx=Soyombo:Soyo + 0x11A50, 0x11AA2, + // #236 (16914+1): sc=Zanabazar_Square:Zanb scx=Zanabazar_Square:Zanb + 0x11A00, 0x11A47, + // #237 (16915+1): sc=Dogra:Dogr + 0x11800, 0x1183B, + // #238 (16916+6): sc=Gunjala_Gondi:Gong + 0x11D60, 0x11D65, 0x11D67, 0x11D68, 0x11D6A, 0x11D8E, 0x11D90, 0x11D91, + 0x11D93, 0x11D98, 0x11DA0, 0x11DA9, + // #239 (16922+1): sc=Makasar:Maka scx=Makasar:Maka + 0x11EE0, 0x11EF8, + // #240 (16923+1): sc=Medefaidrin:Medf scx=Medefaidrin:Medf + 0x16E40, 0x16E9A, + // #241 (16924+2): sc=Hanifi_Rohingya:Rohg + 0x10D00, 0x10D27, 0x10D30, 0x10D39, + // #242 (16926+1): sc=Sogdian:Sogd + 0x10F30, 0x10F59, + // #243 (16927+1): sc=Old_Sogdian:Sogo scx=Old_Sogdian:Sogo + 0x10F00, 0x10F27, + // #244 (16928+1): sc=Elymaic:Elym scx=Elymaic:Elym + 0x10FE0, 0x10FF6, + // #245 (16929+3): sc=Nandinagari:Nand + 0x119A0, 0x119A7, 0x119AA, 0x119D7, 0x119DA, 0x119E4, + // #246 (16932+4): sc=Nyiakeng_Puachue_Hmong:Hmnp scx=Nyiakeng_Puachue_Hmong:Hmnp + 0x1E100, 0x1E12C, 0x1E130, 0x1E13D, 0x1E140, 0x1E149, 0x1E14E, 0x1E14F, + // #247 (16936+2): sc=Wancho:Wcho scx=Wancho:Wcho + 0x1E2C0, 0x1E2F9, 0x1E2FF, 0x1E2FF, + // #248 (16938+1): sc=Chorasmian:Chrs scx=Chorasmian:Chrs + 0x10FB0, 0x10FCB, + // #249 (16939+8): sc=Dives_Akuru:Diak scx=Dives_Akuru:Diak + 0x11900, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, 0x11916, + 0x11918, 0x11935, 0x11937, 0x11938, 0x1193B, 0x11946, 0x11950, 0x11959, + // #250 (16947+3): sc=Khitan_Small_Script:Kits scx=Khitan_Small_Script:Kits + 0x16FE4, 0x16FE4, 0x18B00, 0x18CD5, 0x18CFF, 0x18CFF, + // #251 (16950+3): sc=Yezidi:Yezi + 0x10E80, 0x10EA9, 0x10EAB, 0x10EAD, 0x10EB0, 0x10EB1, + // #252 (16953+1): sc=Cypro_Minoan:Cpmn + 0x12F90, 0x12FF2, + // #253 (16954+1): sc=Old_Uyghur:Ougr + 0x10F70, 0x10F89, + // #254 (16955+2): sc=Tangsa:Tnsa scx=Tangsa:Tnsa + 0x16A70, 0x16ABE, 0x16AC0, 0x16AC9, + // #255 (16957+1): sc=Toto + 0x1E290, 0x1E2AE, + // #256 (16958+8): sc=Vithkuqi:Vith scx=Vithkuqi:Vith + 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, + 0x10597, 0x105A1, 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, + // #257 (16966+3): sc=Kawi scx=Kawi + 0x11F00, 0x11F10, 0x11F12, 0x11F3A, 0x11F3E, 0x11F5A, + // #258 (16969+1): sc=Nag_Mundari:Nagm scx=Nag_Mundari:Nagm + 0x1E4D0, 0x1E4F9, + // #259 (16970+3): sc=Garay:Gara + 0x10D40, 0x10D65, 0x10D69, 0x10D85, 0x10D8E, 0x10D8F, + // #260 (16973+1): sc=Gurung_Khema:Gukh + 0x16100, 0x16139, + // #261 (16974+1): sc=Kirat_Rai:Krai scx=Kirat_Rai:Krai + 0x16D40, 0x16D79, + // #262 (16975+2): sc=Ol_Onal:Onao + 0x1E5D0, 0x1E5FA, 0x1E5FF, 0x1E5FF, + // #263 (16977+2): sc=Sunuwar:Sunu + 0x11BC0, 0x11BE1, 0x11BF0, 0x11BF9, + // #264 (16979+1): sc=Todhri:Todr + 0x105C0, 0x105F3, + // #265 (16980+11): sc=Tulu_Tigalari:Tutg + 0x11380, 0x11389, 0x1138B, 0x1138B, 0x1138E, 0x1138E, 0x11390, 0x113B5, + 0x113B7, 0x113C0, 0x113C2, 0x113C2, 0x113C5, 0x113C5, 0x113C7, 0x113CA, + 0x113CC, 0x113D5, 0x113D7, 0x113D8, 0x113E1, 0x113E2, + // #266 (16991+1): sc=Sidetic:Sidt scx=Sidetic:Sidt + 0x10940, 0x10959, + // #267 (16992+3): sc=Tai_Yo:Tayo scx=Tai_Yo:Tayo + 0x1E6C0, 0x1E6DE, 0x1E6E0, 0x1E6F5, 0x1E6FE, 0x1E6FF, + // #268 (16995+2): sc=Tolong_Siki:Tols scx=Tolong_Siki:Tols + 0x11DB0, 0x11DDB, 0x11DE0, 0x11DE9, + // #269 (16997+2): sc=Beria_Erfe:Berf scx=Beria_Erfe:Berf + 0x16EA0, 0x16EB8, 0x16EBB, 0x16ED3, + // #270 (16999+733): sc=Unknown:Zzzz scx=Unknown:Zzzz + 0x0378, 0x0379, 0x0380, 0x0383, 0x038B, 0x038B, 0x038D, 0x038D, + 0x03A2, 0x03A2, 0x0530, 0x0530, 0x0557, 0x0558, 0x058B, 0x058C, + 0x0590, 0x0590, 0x05C8, 0x05CF, 0x05EB, 0x05EE, 0x05F5, 0x05FF, + 0x070E, 0x070E, 0x074B, 0x074C, 0x07B2, 0x07BF, 0x07FB, 0x07FC, + 0x082E, 0x082F, 0x083F, 0x083F, 0x085C, 0x085D, 0x085F, 0x085F, + 0x086B, 0x086F, 0x0892, 0x0896, 0x0984, 0x0984, 0x098D, 0x098E, + 0x0991, 0x0992, 0x09A9, 0x09A9, 0x09B1, 0x09B1, 0x09B3, 0x09B5, + 0x09BA, 0x09BB, 0x09C5, 0x09C6, 0x09C9, 0x09CA, 0x09CF, 0x09D6, + 0x09D8, 0x09DB, 0x09DE, 0x09DE, 0x09E4, 0x09E5, 0x09FF, 0x0A00, + 0x0A04, 0x0A04, 0x0A0B, 0x0A0E, 0x0A11, 0x0A12, 0x0A29, 0x0A29, + 0x0A31, 0x0A31, 0x0A34, 0x0A34, 0x0A37, 0x0A37, 0x0A3A, 0x0A3B, + 0x0A3D, 0x0A3D, 0x0A43, 0x0A46, 0x0A49, 0x0A4A, 0x0A4E, 0x0A50, + 0x0A52, 0x0A58, 0x0A5D, 0x0A5D, 0x0A5F, 0x0A65, 0x0A77, 0x0A80, + 0x0A84, 0x0A84, 0x0A8E, 0x0A8E, 0x0A92, 0x0A92, 0x0AA9, 0x0AA9, + 0x0AB1, 0x0AB1, 0x0AB4, 0x0AB4, 0x0ABA, 0x0ABB, 0x0AC6, 0x0AC6, + 0x0ACA, 0x0ACA, 0x0ACE, 0x0ACF, 0x0AD1, 0x0ADF, 0x0AE4, 0x0AE5, + 0x0AF2, 0x0AF8, 0x0B00, 0x0B00, 0x0B04, 0x0B04, 0x0B0D, 0x0B0E, + 0x0B11, 0x0B12, 0x0B29, 0x0B29, 0x0B31, 0x0B31, 0x0B34, 0x0B34, + 0x0B3A, 0x0B3B, 0x0B45, 0x0B46, 0x0B49, 0x0B4A, 0x0B4E, 0x0B54, + 0x0B58, 0x0B5B, 0x0B5E, 0x0B5E, 0x0B64, 0x0B65, 0x0B78, 0x0B81, + 0x0B84, 0x0B84, 0x0B8B, 0x0B8D, 0x0B91, 0x0B91, 0x0B96, 0x0B98, + 0x0B9B, 0x0B9B, 0x0B9D, 0x0B9D, 0x0BA0, 0x0BA2, 0x0BA5, 0x0BA7, + 0x0BAB, 0x0BAD, 0x0BBA, 0x0BBD, 0x0BC3, 0x0BC5, 0x0BC9, 0x0BC9, + 0x0BCE, 0x0BCF, 0x0BD1, 0x0BD6, 0x0BD8, 0x0BE5, 0x0BFB, 0x0BFF, + 0x0C0D, 0x0C0D, 0x0C11, 0x0C11, 0x0C29, 0x0C29, 0x0C3A, 0x0C3B, + 0x0C45, 0x0C45, 0x0C49, 0x0C49, 0x0C4E, 0x0C54, 0x0C57, 0x0C57, + 0x0C5B, 0x0C5B, 0x0C5E, 0x0C5F, 0x0C64, 0x0C65, 0x0C70, 0x0C76, + 0x0C8D, 0x0C8D, 0x0C91, 0x0C91, 0x0CA9, 0x0CA9, 0x0CB4, 0x0CB4, + 0x0CBA, 0x0CBB, 0x0CC5, 0x0CC5, 0x0CC9, 0x0CC9, 0x0CCE, 0x0CD4, + 0x0CD7, 0x0CDB, 0x0CDF, 0x0CDF, 0x0CE4, 0x0CE5, 0x0CF0, 0x0CF0, + 0x0CF4, 0x0CFF, 0x0D0D, 0x0D0D, 0x0D11, 0x0D11, 0x0D45, 0x0D45, + 0x0D49, 0x0D49, 0x0D50, 0x0D53, 0x0D64, 0x0D65, 0x0D80, 0x0D80, + 0x0D84, 0x0D84, 0x0D97, 0x0D99, 0x0DB2, 0x0DB2, 0x0DBC, 0x0DBC, + 0x0DBE, 0x0DBF, 0x0DC7, 0x0DC9, 0x0DCB, 0x0DCE, 0x0DD5, 0x0DD5, + 0x0DD7, 0x0DD7, 0x0DE0, 0x0DE5, 0x0DF0, 0x0DF1, 0x0DF5, 0x0E00, + 0x0E3B, 0x0E3E, 0x0E5C, 0x0E80, 0x0E83, 0x0E83, 0x0E85, 0x0E85, + 0x0E8B, 0x0E8B, 0x0EA4, 0x0EA4, 0x0EA6, 0x0EA6, 0x0EBE, 0x0EBF, + 0x0EC5, 0x0EC5, 0x0EC7, 0x0EC7, 0x0ECF, 0x0ECF, 0x0EDA, 0x0EDB, + 0x0EE0, 0x0EFF, 0x0F48, 0x0F48, 0x0F6D, 0x0F70, 0x0F98, 0x0F98, + 0x0FBD, 0x0FBD, 0x0FCD, 0x0FCD, 0x0FDB, 0x0FFF, 0x10C6, 0x10C6, + 0x10C8, 0x10CC, 0x10CE, 0x10CF, 0x1249, 0x1249, 0x124E, 0x124F, + 0x1257, 0x1257, 0x1259, 0x1259, 0x125E, 0x125F, 0x1289, 0x1289, + 0x128E, 0x128F, 0x12B1, 0x12B1, 0x12B6, 0x12B7, 0x12BF, 0x12BF, + 0x12C1, 0x12C1, 0x12C6, 0x12C7, 0x12D7, 0x12D7, 0x1311, 0x1311, + 0x1316, 0x1317, 0x135B, 0x135C, 0x137D, 0x137F, 0x139A, 0x139F, + 0x13F6, 0x13F7, 0x13FE, 0x13FF, 0x169D, 0x169F, 0x16F9, 0x16FF, + 0x1716, 0x171E, 0x1737, 0x173F, 0x1754, 0x175F, 0x176D, 0x176D, + 0x1771, 0x1771, 0x1774, 0x177F, 0x17DE, 0x17DF, 0x17EA, 0x17EF, + 0x17FA, 0x17FF, 0x181A, 0x181F, 0x1879, 0x187F, 0x18AB, 0x18AF, + 0x18F6, 0x18FF, 0x191F, 0x191F, 0x192C, 0x192F, 0x193C, 0x193F, + 0x1941, 0x1943, 0x196E, 0x196F, 0x1975, 0x197F, 0x19AC, 0x19AF, + 0x19CA, 0x19CF, 0x19DB, 0x19DD, 0x1A1C, 0x1A1D, 0x1A5F, 0x1A5F, + 0x1A7D, 0x1A7E, 0x1A8A, 0x1A8F, 0x1A9A, 0x1A9F, 0x1AAE, 0x1AAF, + 0x1ADE, 0x1ADF, 0x1AEC, 0x1AFF, 0x1B4D, 0x1B4D, 0x1BF4, 0x1BFB, + 0x1C38, 0x1C3A, 0x1C4A, 0x1C4C, 0x1C8B, 0x1C8F, 0x1CBB, 0x1CBC, + 0x1CC8, 0x1CCF, 0x1CFB, 0x1CFF, 0x1F16, 0x1F17, 0x1F1E, 0x1F1F, + 0x1F46, 0x1F47, 0x1F4E, 0x1F4F, 0x1F58, 0x1F58, 0x1F5A, 0x1F5A, + 0x1F5C, 0x1F5C, 0x1F5E, 0x1F5E, 0x1F7E, 0x1F7F, 0x1FB5, 0x1FB5, + 0x1FC5, 0x1FC5, 0x1FD4, 0x1FD5, 0x1FDC, 0x1FDC, 0x1FF0, 0x1FF1, + 0x1FF5, 0x1FF5, 0x1FFF, 0x1FFF, 0x2065, 0x2065, 0x2072, 0x2073, + 0x208F, 0x208F, 0x209D, 0x209F, 0x20C2, 0x20CF, 0x20F1, 0x20FF, + 0x218C, 0x218F, 0x242A, 0x243F, 0x244B, 0x245F, 0x2B74, 0x2B75, + 0x2CF4, 0x2CF8, 0x2D26, 0x2D26, 0x2D28, 0x2D2C, 0x2D2E, 0x2D2F, + 0x2D68, 0x2D6E, 0x2D71, 0x2D7E, 0x2D97, 0x2D9F, 0x2DA7, 0x2DA7, + 0x2DAF, 0x2DAF, 0x2DB7, 0x2DB7, 0x2DBF, 0x2DBF, 0x2DC7, 0x2DC7, + 0x2DCF, 0x2DCF, 0x2DD7, 0x2DD7, 0x2DDF, 0x2DDF, 0x2E5E, 0x2E7F, + 0x2E9A, 0x2E9A, 0x2EF4, 0x2EFF, 0x2FD6, 0x2FEF, 0x3040, 0x3040, + 0x3097, 0x3098, 0x3100, 0x3104, 0x3130, 0x3130, 0x318F, 0x318F, + 0x31E6, 0x31EE, 0x321F, 0x321F, 0xA48D, 0xA48F, 0xA4C7, 0xA4CF, + 0xA62C, 0xA63F, 0xA6F8, 0xA6FF, 0xA7DD, 0xA7F0, 0xA82D, 0xA82F, + 0xA83A, 0xA83F, 0xA878, 0xA87F, 0xA8C6, 0xA8CD, 0xA8DA, 0xA8DF, + 0xA954, 0xA95E, 0xA97D, 0xA97F, 0xA9CE, 0xA9CE, 0xA9DA, 0xA9DD, + 0xA9FF, 0xA9FF, 0xAA37, 0xAA3F, 0xAA4E, 0xAA4F, 0xAA5A, 0xAA5B, + 0xAAC3, 0xAADA, 0xAAF7, 0xAB00, 0xAB07, 0xAB08, 0xAB0F, 0xAB10, + 0xAB17, 0xAB1F, 0xAB27, 0xAB27, 0xAB2F, 0xAB2F, 0xAB6C, 0xAB6F, + 0xABEE, 0xABEF, 0xABFA, 0xABFF, 0xD7A4, 0xD7AF, 0xD7C7, 0xD7CA, + 0xD7FC, 0xF8FF, 0xFA6E, 0xFA6F, 0xFADA, 0xFAFF, 0xFB07, 0xFB12, + 0xFB18, 0xFB1C, 0xFB37, 0xFB37, 0xFB3D, 0xFB3D, 0xFB3F, 0xFB3F, + 0xFB42, 0xFB42, 0xFB45, 0xFB45, 0xFDD0, 0xFDEF, 0xFE1A, 0xFE1F, + 0xFE53, 0xFE53, 0xFE67, 0xFE67, 0xFE6C, 0xFE6F, 0xFE75, 0xFE75, + 0xFEFD, 0xFEFE, 0xFF00, 0xFF00, 0xFFBF, 0xFFC1, 0xFFC8, 0xFFC9, + 0xFFD0, 0xFFD1, 0xFFD8, 0xFFD9, 0xFFDD, 0xFFDF, 0xFFE7, 0xFFE7, + 0xFFEF, 0xFFF8, 0xFFFE, 0xFFFF, 0x1000C, 0x1000C, 0x10027, 0x10027, + 0x1003B, 0x1003B, 0x1003E, 0x1003E, 0x1004E, 0x1004F, 0x1005E, 0x1007F, + 0x100FB, 0x100FF, 0x10103, 0x10106, 0x10134, 0x10136, 0x1018F, 0x1018F, + 0x1019D, 0x1019F, 0x101A1, 0x101CF, 0x101FE, 0x1027F, 0x1029D, 0x1029F, + 0x102D1, 0x102DF, 0x102FC, 0x102FF, 0x10324, 0x1032C, 0x1034B, 0x1034F, + 0x1037B, 0x1037F, 0x1039E, 0x1039E, 0x103C4, 0x103C7, 0x103D6, 0x103FF, + 0x1049E, 0x1049F, 0x104AA, 0x104AF, 0x104D4, 0x104D7, 0x104FC, 0x104FF, + 0x10528, 0x1052F, 0x10564, 0x1056E, 0x1057B, 0x1057B, 0x1058B, 0x1058B, + 0x10593, 0x10593, 0x10596, 0x10596, 0x105A2, 0x105A2, 0x105B2, 0x105B2, + 0x105BA, 0x105BA, 0x105BD, 0x105BF, 0x105F4, 0x105FF, 0x10737, 0x1073F, + 0x10756, 0x1075F, 0x10768, 0x1077F, 0x10786, 0x10786, 0x107B1, 0x107B1, + 0x107BB, 0x107FF, 0x10806, 0x10807, 0x10809, 0x10809, 0x10836, 0x10836, + 0x10839, 0x1083B, 0x1083D, 0x1083E, 0x10856, 0x10856, 0x1089F, 0x108A6, + 0x108B0, 0x108DF, 0x108F3, 0x108F3, 0x108F6, 0x108FA, 0x1091C, 0x1091E, + 0x1093A, 0x1093E, 0x1095A, 0x1097F, 0x109B8, 0x109BB, 0x109D0, 0x109D1, + 0x10A04, 0x10A04, 0x10A07, 0x10A0B, 0x10A14, 0x10A14, 0x10A18, 0x10A18, + 0x10A36, 0x10A37, 0x10A3B, 0x10A3E, 0x10A49, 0x10A4F, 0x10A59, 0x10A5F, + 0x10AA0, 0x10ABF, 0x10AE7, 0x10AEA, 0x10AF7, 0x10AFF, 0x10B36, 0x10B38, + 0x10B56, 0x10B57, 0x10B73, 0x10B77, 0x10B92, 0x10B98, 0x10B9D, 0x10BA8, + 0x10BB0, 0x10BFF, 0x10C49, 0x10C7F, 0x10CB3, 0x10CBF, 0x10CF3, 0x10CF9, + 0x10D28, 0x10D2F, 0x10D3A, 0x10D3F, 0x10D66, 0x10D68, 0x10D86, 0x10D8D, + 0x10D90, 0x10E5F, 0x10E7F, 0x10E7F, 0x10EAA, 0x10EAA, 0x10EAE, 0x10EAF, + 0x10EB2, 0x10EC1, 0x10EC8, 0x10ECF, 0x10ED9, 0x10EF9, 0x10F28, 0x10F2F, + 0x10F5A, 0x10F6F, 0x10F8A, 0x10FAF, 0x10FCC, 0x10FDF, 0x10FF7, 0x10FFF, + 0x1104E, 0x11051, 0x11076, 0x1107E, 0x110C3, 0x110CC, 0x110CE, 0x110CF, + 0x110E9, 0x110EF, 0x110FA, 0x110FF, 0x11135, 0x11135, 0x11148, 0x1114F, + 0x11177, 0x1117F, 0x111E0, 0x111E0, 0x111F5, 0x111FF, 0x11212, 0x11212, + 0x11242, 0x1127F, 0x11287, 0x11287, 0x11289, 0x11289, 0x1128E, 0x1128E, + 0x1129E, 0x1129E, 0x112AA, 0x112AF, 0x112EB, 0x112EF, 0x112FA, 0x112FF, + 0x11304, 0x11304, 0x1130D, 0x1130E, 0x11311, 0x11312, 0x11329, 0x11329, + 0x11331, 0x11331, 0x11334, 0x11334, 0x1133A, 0x1133A, 0x11345, 0x11346, + 0x11349, 0x1134A, 0x1134E, 0x1134F, 0x11351, 0x11356, 0x11358, 0x1135C, + 0x11364, 0x11365, 0x1136D, 0x1136F, 0x11375, 0x1137F, 0x1138A, 0x1138A, + 0x1138C, 0x1138D, 0x1138F, 0x1138F, 0x113B6, 0x113B6, 0x113C1, 0x113C1, + 0x113C3, 0x113C4, 0x113C6, 0x113C6, 0x113CB, 0x113CB, 0x113D6, 0x113D6, + 0x113D9, 0x113E0, 0x113E3, 0x113FF, 0x1145C, 0x1145C, 0x11462, 0x1147F, + 0x114C8, 0x114CF, 0x114DA, 0x1157F, 0x115B6, 0x115B7, 0x115DE, 0x115FF, + 0x11645, 0x1164F, 0x1165A, 0x1165F, 0x1166D, 0x1167F, 0x116BA, 0x116BF, + 0x116CA, 0x116CF, 0x116E4, 0x116FF, 0x1171B, 0x1171C, 0x1172C, 0x1172F, + 0x11747, 0x117FF, 0x1183C, 0x1189F, 0x118F3, 0x118FE, 0x11907, 0x11908, + 0x1190A, 0x1190B, 0x11914, 0x11914, 0x11917, 0x11917, 0x11936, 0x11936, + 0x11939, 0x1193A, 0x11947, 0x1194F, 0x1195A, 0x1199F, 0x119A8, 0x119A9, + 0x119D8, 0x119D9, 0x119E5, 0x119FF, 0x11A48, 0x11A4F, 0x11AA3, 0x11AAF, + 0x11AF9, 0x11AFF, 0x11B0A, 0x11B5F, 0x11B68, 0x11BBF, 0x11BE2, 0x11BEF, + 0x11BFA, 0x11BFF, 0x11C09, 0x11C09, 0x11C37, 0x11C37, 0x11C46, 0x11C4F, + 0x11C6D, 0x11C6F, 0x11C90, 0x11C91, 0x11CA8, 0x11CA8, 0x11CB7, 0x11CFF, + 0x11D07, 0x11D07, 0x11D0A, 0x11D0A, 0x11D37, 0x11D39, 0x11D3B, 0x11D3B, + 0x11D3E, 0x11D3E, 0x11D48, 0x11D4F, 0x11D5A, 0x11D5F, 0x11D66, 0x11D66, + 0x11D69, 0x11D69, 0x11D8F, 0x11D8F, 0x11D92, 0x11D92, 0x11D99, 0x11D9F, + 0x11DAA, 0x11DAF, 0x11DDC, 0x11DDF, 0x11DEA, 0x11EDF, 0x11EF9, 0x11EFF, + 0x11F11, 0x11F11, 0x11F3B, 0x11F3D, 0x11F5B, 0x11FAF, 0x11FB1, 0x11FBF, + 0x11FF2, 0x11FFE, 0x1239A, 0x123FF, 0x1246F, 0x1246F, 0x12475, 0x1247F, + 0x12544, 0x12F8F, 0x12FF3, 0x12FFF, 0x13456, 0x1345F, 0x143FB, 0x143FF, + 0x14647, 0x160FF, 0x1613A, 0x167FF, 0x16A39, 0x16A3F, 0x16A5F, 0x16A5F, + 0x16A6A, 0x16A6D, 0x16ABF, 0x16ABF, 0x16ACA, 0x16ACF, 0x16AEE, 0x16AEF, + 0x16AF6, 0x16AFF, 0x16B46, 0x16B4F, 0x16B5A, 0x16B5A, 0x16B62, 0x16B62, + 0x16B78, 0x16B7C, 0x16B90, 0x16D3F, 0x16D7A, 0x16E3F, 0x16E9B, 0x16E9F, + 0x16EB9, 0x16EBA, 0x16ED4, 0x16EFF, 0x16F4B, 0x16F4E, 0x16F88, 0x16F8E, + 0x16FA0, 0x16FDF, 0x16FE5, 0x16FEF, 0x16FF7, 0x16FFF, 0x18CD6, 0x18CFE, + 0x18D1F, 0x18D7F, 0x18DF3, 0x1AFEF, 0x1AFF4, 0x1AFF4, 0x1AFFC, 0x1AFFC, + 0x1AFFF, 0x1AFFF, 0x1B123, 0x1B131, 0x1B133, 0x1B14F, 0x1B153, 0x1B154, + 0x1B156, 0x1B163, 0x1B168, 0x1B16F, 0x1B2FC, 0x1BBFF, 0x1BC6B, 0x1BC6F, + 0x1BC7D, 0x1BC7F, 0x1BC89, 0x1BC8F, 0x1BC9A, 0x1BC9B, 0x1BCA4, 0x1CBFF, + 0x1CCFD, 0x1CCFF, 0x1CEB4, 0x1CEB9, 0x1CED1, 0x1CEDF, 0x1CEF1, 0x1CEFF, + 0x1CF2E, 0x1CF2F, 0x1CF47, 0x1CF4F, 0x1CFC4, 0x1CFFF, 0x1D0F6, 0x1D0FF, + 0x1D127, 0x1D128, 0x1D1EB, 0x1D1FF, 0x1D246, 0x1D2BF, 0x1D2D4, 0x1D2DF, + 0x1D2F4, 0x1D2FF, 0x1D357, 0x1D35F, 0x1D379, 0x1D3FF, 0x1D455, 0x1D455, + 0x1D49D, 0x1D49D, 0x1D4A0, 0x1D4A1, 0x1D4A3, 0x1D4A4, 0x1D4A7, 0x1D4A8, + 0x1D4AD, 0x1D4AD, 0x1D4BA, 0x1D4BA, 0x1D4BC, 0x1D4BC, 0x1D4C4, 0x1D4C4, + 0x1D506, 0x1D506, 0x1D50B, 0x1D50C, 0x1D515, 0x1D515, 0x1D51D, 0x1D51D, + 0x1D53A, 0x1D53A, 0x1D53F, 0x1D53F, 0x1D545, 0x1D545, 0x1D547, 0x1D549, + 0x1D551, 0x1D551, 0x1D6A6, 0x1D6A7, 0x1D7CC, 0x1D7CD, 0x1DA8C, 0x1DA9A, + 0x1DAA0, 0x1DAA0, 0x1DAB0, 0x1DEFF, 0x1DF1F, 0x1DF24, 0x1DF2B, 0x1DFFF, + 0x1E007, 0x1E007, 0x1E019, 0x1E01A, 0x1E022, 0x1E022, 0x1E025, 0x1E025, + 0x1E02B, 0x1E02F, 0x1E06E, 0x1E08E, 0x1E090, 0x1E0FF, 0x1E12D, 0x1E12F, + 0x1E13E, 0x1E13F, 0x1E14A, 0x1E14D, 0x1E150, 0x1E28F, 0x1E2AF, 0x1E2BF, + 0x1E2FA, 0x1E2FE, 0x1E300, 0x1E4CF, 0x1E4FA, 0x1E5CF, 0x1E5FB, 0x1E5FE, + 0x1E600, 0x1E6BF, 0x1E6DF, 0x1E6DF, 0x1E6F6, 0x1E6FD, 0x1E700, 0x1E7DF, + 0x1E7E7, 0x1E7E7, 0x1E7EC, 0x1E7EC, 0x1E7EF, 0x1E7EF, 0x1E7FF, 0x1E7FF, + 0x1E8C5, 0x1E8C6, 0x1E8D7, 0x1E8FF, 0x1E94C, 0x1E94F, 0x1E95A, 0x1E95D, + 0x1E960, 0x1EC70, 0x1ECB5, 0x1ED00, 0x1ED3E, 0x1EDFF, 0x1EE04, 0x1EE04, + 0x1EE20, 0x1EE20, 0x1EE23, 0x1EE23, 0x1EE25, 0x1EE26, 0x1EE28, 0x1EE28, + 0x1EE33, 0x1EE33, 0x1EE38, 0x1EE38, 0x1EE3A, 0x1EE3A, 0x1EE3C, 0x1EE41, + 0x1EE43, 0x1EE46, 0x1EE48, 0x1EE48, 0x1EE4A, 0x1EE4A, 0x1EE4C, 0x1EE4C, + 0x1EE50, 0x1EE50, 0x1EE53, 0x1EE53, 0x1EE55, 0x1EE56, 0x1EE58, 0x1EE58, + 0x1EE5A, 0x1EE5A, 0x1EE5C, 0x1EE5C, 0x1EE5E, 0x1EE5E, 0x1EE60, 0x1EE60, + 0x1EE63, 0x1EE63, 0x1EE65, 0x1EE66, 0x1EE6B, 0x1EE6B, 0x1EE73, 0x1EE73, + 0x1EE78, 0x1EE78, 0x1EE7D, 0x1EE7D, 0x1EE7F, 0x1EE7F, 0x1EE8A, 0x1EE8A, + 0x1EE9C, 0x1EEA0, 0x1EEA4, 0x1EEA4, 0x1EEAA, 0x1EEAA, 0x1EEBC, 0x1EEEF, + 0x1EEF2, 0x1EFFF, 0x1F02C, 0x1F02F, 0x1F094, 0x1F09F, 0x1F0AF, 0x1F0B0, + 0x1F0C0, 0x1F0C0, 0x1F0D0, 0x1F0D0, 0x1F0F6, 0x1F0FF, 0x1F1AE, 0x1F1E5, + 0x1F203, 0x1F20F, 0x1F23C, 0x1F23F, 0x1F249, 0x1F24F, 0x1F252, 0x1F25F, + 0x1F266, 0x1F2FF, 0x1F6D9, 0x1F6DB, 0x1F6ED, 0x1F6EF, 0x1F6FD, 0x1F6FF, + 0x1F7DA, 0x1F7DF, 0x1F7EC, 0x1F7EF, 0x1F7F1, 0x1F7FF, 0x1F80C, 0x1F80F, + 0x1F848, 0x1F84F, 0x1F85A, 0x1F85F, 0x1F888, 0x1F88F, 0x1F8AE, 0x1F8AF, + 0x1F8BC, 0x1F8BF, 0x1F8C2, 0x1F8CF, 0x1F8D9, 0x1F8FF, 0x1FA58, 0x1FA5F, + 0x1FA6E, 0x1FA6F, 0x1FA7D, 0x1FA7F, 0x1FA8B, 0x1FA8D, 0x1FAC7, 0x1FAC7, + 0x1FAC9, 0x1FACC, 0x1FADD, 0x1FADE, 0x1FAEB, 0x1FAEE, 0x1FAF9, 0x1FAFF, + 0x1FB93, 0x1FB93, 0x1FBFB, 0x1FFFF, 0x2A6E0, 0x2A6FF, 0x2B81E, 0x2B81F, + 0x2CEAE, 0x2CEAF, 0x2EBE1, 0x2EBEF, 0x2EE5E, 0x2F7FF, 0x2FA1E, 0x2FFFF, + 0x3134B, 0x3134F, 0x3347A, 0xE0000, 0xE0002, 0xE001F, 0xE0080, 0xE00FF, + 0xE01F0, 0x10FFFF, + // #271 (17732+161): scx=Common:Zyyy + 0x0000, 0x0040, 0x005B, 0x0060, 0x007B, 0x00A9, 0x00AB, 0x00B6, + 0x00B8, 0x00B9, 0x00BB, 0x00BF, 0x00D7, 0x00D7, 0x00F7, 0x00F7, + 0x02B9, 0x02BB, 0x02BD, 0x02C6, 0x02C8, 0x02C8, 0x02CC, 0x02CC, + 0x02CE, 0x02D6, 0x02D8, 0x02D8, 0x02DA, 0x02DF, 0x02E5, 0x02E9, + 0x02EC, 0x02FF, 0x037E, 0x037E, 0x0385, 0x0385, 0x0387, 0x0387, + 0x0605, 0x0605, 0x06DD, 0x06DD, 0x08E2, 0x08E2, 0x0E3F, 0x0E3F, + 0x0FD5, 0x0FD8, 0x2000, 0x200B, 0x200E, 0x202E, 0x2030, 0x204E, + 0x2050, 0x2059, 0x205B, 0x205C, 0x205E, 0x2064, 0x2066, 0x2070, + 0x2074, 0x207E, 0x2080, 0x208E, 0x20A0, 0x20C1, 0x2100, 0x2125, + 0x2127, 0x2129, 0x212C, 0x2131, 0x2133, 0x214D, 0x214F, 0x215F, + 0x2189, 0x218B, 0x2190, 0x2429, 0x2440, 0x244A, 0x2460, 0x27FF, + 0x2900, 0x2B73, 0x2B76, 0x2BFF, 0x2E00, 0x2E16, 0x2E18, 0x2E2F, + 0x2E32, 0x2E3B, 0x2E3D, 0x2E40, 0x2E42, 0x2E42, 0x2E44, 0x2E5D, + 0x3000, 0x3000, 0x3004, 0x3004, 0x3012, 0x3012, 0x3020, 0x3020, + 0x3036, 0x3036, 0x3248, 0x325F, 0x327F, 0x327F, 0x32B1, 0x32BF, + 0x32CC, 0x32CF, 0x3371, 0x337A, 0x3380, 0x33DF, 0x33FF, 0x33FF, + 0x4DC0, 0x4DFF, 0xA708, 0xA721, 0xA788, 0xA78A, 0xAB5B, 0xAB5B, + 0xAB6A, 0xAB6B, 0xFE10, 0xFE19, 0xFE30, 0xFE44, 0xFE47, 0xFE52, + 0xFE54, 0xFE66, 0xFE68, 0xFE6B, 0xFEFF, 0xFEFF, 0xFF01, 0xFF20, + 0xFF3B, 0xFF40, 0xFF5B, 0xFF60, 0xFFE0, 0xFFE6, 0xFFE8, 0xFFEE, + 0xFFF9, 0xFFFD, 0x10190, 0x1019C, 0x101D0, 0x101FC, 0x1CC00, 0x1CCFC, + 0x1CD00, 0x1CEB3, 0x1CEBA, 0x1CED0, 0x1CEE0, 0x1CEF0, 0x1CF50, 0x1CFC3, + 0x1D000, 0x1D0F5, 0x1D100, 0x1D126, 0x1D129, 0x1D166, 0x1D16A, 0x1D17A, + 0x1D183, 0x1D184, 0x1D18C, 0x1D1A9, 0x1D1AE, 0x1D1EA, 0x1D2C0, 0x1D2D3, + 0x1D2E0, 0x1D2F3, 0x1D300, 0x1D356, 0x1D372, 0x1D378, 0x1D400, 0x1D454, + 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, + 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, + 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, + 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, + 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, 0x1D6A8, 0x1D7CB, 0x1D7CE, 0x1D7FF, + 0x1EC71, 0x1ECB4, 0x1ED01, 0x1ED3D, 0x1F000, 0x1F02B, 0x1F030, 0x1F093, + 0x1F0A0, 0x1F0AE, 0x1F0B1, 0x1F0BF, 0x1F0C1, 0x1F0CF, 0x1F0D1, 0x1F0F5, + 0x1F100, 0x1F1AD, 0x1F1E6, 0x1F1FF, 0x1F201, 0x1F202, 0x1F210, 0x1F23B, + 0x1F240, 0x1F248, 0x1F260, 0x1F265, 0x1F300, 0x1F6D8, 0x1F6DC, 0x1F6EC, + 0x1F6F0, 0x1F6FC, 0x1F700, 0x1F7D9, 0x1F7E0, 0x1F7EB, 0x1F7F0, 0x1F7F0, + 0x1F800, 0x1F80B, 0x1F810, 0x1F847, 0x1F850, 0x1F859, 0x1F860, 0x1F887, + 0x1F890, 0x1F8AD, 0x1F8B0, 0x1F8BB, 0x1F8C0, 0x1F8C1, 0x1F8D0, 0x1F8D8, + 0x1F900, 0x1FA57, 0x1FA60, 0x1FA6D, 0x1FA70, 0x1FA7C, 0x1FA80, 0x1FA8A, + 0x1FA8E, 0x1FAC6, 0x1FAC8, 0x1FAC8, 0x1FACD, 0x1FADC, 0x1FADF, 0x1FAEA, + 0x1FAEF, 0x1FAF8, 0x1FB00, 0x1FB92, 0x1FB94, 0x1FBFA, 0xE0001, 0xE0001, + 0xE0020, 0xE007F, + // #272 (17893+61): scx=Latin:Latn + 0x0041, 0x005A, 0x0061, 0x007A, 0x00AA, 0x00AA, 0x00B7, 0x00B7, + 0x00BA, 0x00BA, 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x02B8, + 0x02BC, 0x02BC, 0x02C7, 0x02C7, 0x02C9, 0x02CB, 0x02CD, 0x02CD, + 0x02D7, 0x02D7, 0x02D9, 0x02D9, 0x02E0, 0x02E4, 0x0300, 0x030E, + 0x0310, 0x0311, 0x0313, 0x0313, 0x0323, 0x0325, 0x032D, 0x032E, + 0x0330, 0x0331, 0x0358, 0x0358, 0x035E, 0x035E, 0x0363, 0x036F, + 0x0485, 0x0486, 0x0951, 0x0952, 0x10FB, 0x10FB, 0x1D00, 0x1D25, + 0x1D2C, 0x1D5C, 0x1D62, 0x1D65, 0x1D6B, 0x1D77, 0x1D79, 0x1DBE, + 0x1DF8, 0x1DF8, 0x1E00, 0x1EFF, 0x202F, 0x202F, 0x2071, 0x2071, + 0x207F, 0x207F, 0x2090, 0x209C, 0x20F0, 0x20F0, 0x212A, 0x212B, + 0x2132, 0x2132, 0x214E, 0x214E, 0x2160, 0x2188, 0x2C60, 0x2C7F, + 0x2E17, 0x2E17, 0xA700, 0xA707, 0xA722, 0xA787, 0xA78B, 0xA7DC, + 0xA7F1, 0xA7FF, 0xA92E, 0xA92E, 0xAB30, 0xAB5A, 0xAB5C, 0xAB64, + 0xAB66, 0xAB69, 0xFB00, 0xFB06, 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, + 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x1DF00, 0x1DF1E, + 0x1DF25, 0x1DF2A, + // #273 (17954+44): scx=Greek:Grek + 0x00B7, 0x00B7, 0x0300, 0x0301, 0x0304, 0x0304, 0x0306, 0x0306, + 0x0308, 0x0308, 0x0313, 0x0313, 0x0342, 0x0342, 0x0345, 0x0345, + 0x0370, 0x0377, 0x037A, 0x037D, 0x037F, 0x037F, 0x0384, 0x0384, + 0x0386, 0x0386, 0x0388, 0x038A, 0x038C, 0x038C, 0x038E, 0x03A1, + 0x03A3, 0x03E1, 0x03F0, 0x03FF, 0x1D26, 0x1D2A, 0x1D5D, 0x1D61, + 0x1D66, 0x1D6A, 0x1DBF, 0x1DC1, 0x1F00, 0x1F15, 0x1F18, 0x1F1D, + 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, + 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, + 0x1FB6, 0x1FC4, 0x1FC6, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FDD, 0x1FEF, + 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFE, 0x205D, 0x205D, 0x2126, 0x2126, + 0xAB65, 0xAB65, 0x10140, 0x1018E, 0x101A0, 0x101A0, 0x1D200, 0x1D245, + // #274 (17998+18): scx=Cyrillic:Cyrl + 0x02BC, 0x02BC, 0x0300, 0x0302, 0x0304, 0x0304, 0x0306, 0x0306, + 0x0308, 0x0308, 0x030B, 0x030B, 0x0311, 0x0311, 0x0400, 0x052F, + 0x1C80, 0x1C8A, 0x1D2B, 0x1D2B, 0x1D78, 0x1D78, 0x1DF8, 0x1DF8, + 0x2DE0, 0x2DFF, 0x2E43, 0x2E43, 0xA640, 0xA69F, 0xFE2E, 0xFE2F, + 0x1E030, 0x1E06D, 0x1E08F, 0x1E08F, + // #275 (18016+5): scx=Armenian:Armn + 0x0308, 0x0308, 0x0531, 0x0556, 0x0559, 0x058A, 0x058D, 0x058F, + 0xFB13, 0xFB17, + // #276 (18021+10): scx=Hebrew:Hebr + 0x0307, 0x0308, 0x0591, 0x05C7, 0x05D0, 0x05EA, 0x05EF, 0x05F4, + 0xFB1D, 0xFB36, 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, + 0xFB43, 0xFB44, 0xFB46, 0xFB4F, + // #277 (18031+52): scx=Arabic:Arab + 0x0600, 0x0604, 0x0606, 0x06DC, 0x06DE, 0x06FF, 0x0750, 0x077F, + 0x0870, 0x0891, 0x0897, 0x08E1, 0x08E3, 0x08FF, 0x204F, 0x204F, + 0x2E41, 0x2E41, 0xFB50, 0xFDCF, 0xFDF0, 0xFDFF, 0xFE70, 0xFE74, + 0xFE76, 0xFEFC, 0x102E0, 0x102FB, 0x10E60, 0x10E7E, 0x10EC2, 0x10EC7, + 0x10ED0, 0x10ED8, 0x10EFA, 0x10EFF, 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, + 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, + 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, + 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, 0x1EE4F, + 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, + 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, + 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, 0x1EE74, 0x1EE77, + 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, + 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, 0x1EEF0, 0x1EEF1, + // #278 (18083+18): scx=Syriac:Syrc + 0x0303, 0x0304, 0x0307, 0x0308, 0x030A, 0x030A, 0x0323, 0x0325, + 0x032D, 0x032E, 0x0330, 0x0331, 0x060C, 0x060C, 0x061B, 0x061C, + 0x061F, 0x061F, 0x0640, 0x0640, 0x064B, 0x0655, 0x0670, 0x0670, + 0x0700, 0x070D, 0x070F, 0x074A, 0x074D, 0x074F, 0x0860, 0x086A, + 0x1DF8, 0x1DF8, 0x1DFA, 0x1DFA, + // #279 (18101+7): scx=Thaana:Thaa + 0x060C, 0x060C, 0x061B, 0x061C, 0x061F, 0x061F, 0x0660, 0x0669, + 0x0780, 0x07B1, 0xFDF2, 0xFDF2, 0xFDFD, 0xFDFD, + // #280 (18108+9): scx=Devanagari:Deva + 0x02BC, 0x02BC, 0x0900, 0x0952, 0x0955, 0x097F, 0x1CD0, 0x1CF6, + 0x1CF8, 0x1CF9, 0x20F0, 0x20F0, 0xA830, 0xA839, 0xA8E0, 0xA8FF, + 0x11B00, 0x11B09, + // #281 (18117+27): scx=Bengali:Beng + 0x02BC, 0x02BC, 0x0951, 0x0952, 0x0964, 0x0965, 0x0980, 0x0983, + 0x0985, 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, 0x09AA, 0x09B0, + 0x09B2, 0x09B2, 0x09B6, 0x09B9, 0x09BC, 0x09C4, 0x09C7, 0x09C8, + 0x09CB, 0x09CE, 0x09D7, 0x09D7, 0x09DC, 0x09DD, 0x09DF, 0x09E3, + 0x09E6, 0x09FE, 0x1CD0, 0x1CD0, 0x1CD2, 0x1CD2, 0x1CD5, 0x1CD6, + 0x1CD8, 0x1CD8, 0x1CE1, 0x1CE1, 0x1CEA, 0x1CEA, 0x1CED, 0x1CED, + 0x1CF2, 0x1CF2, 0x1CF5, 0x1CF7, 0xA8F1, 0xA8F1, + // #282 (18144+19): scx=Gurmukhi:Guru + 0x0951, 0x0952, 0x0964, 0x0965, 0x0A01, 0x0A03, 0x0A05, 0x0A0A, + 0x0A0F, 0x0A10, 0x0A13, 0x0A28, 0x0A2A, 0x0A30, 0x0A32, 0x0A33, + 0x0A35, 0x0A36, 0x0A38, 0x0A39, 0x0A3C, 0x0A3C, 0x0A3E, 0x0A42, + 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, 0x0A51, 0x0A51, 0x0A59, 0x0A5C, + 0x0A5E, 0x0A5E, 0x0A66, 0x0A76, 0xA830, 0xA839, + // #283 (18163+17): scx=Gujarati:Gujr + 0x0951, 0x0952, 0x0964, 0x0965, 0x0A81, 0x0A83, 0x0A85, 0x0A8D, + 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, + 0x0AB5, 0x0AB9, 0x0ABC, 0x0AC5, 0x0AC7, 0x0AC9, 0x0ACB, 0x0ACD, + 0x0AD0, 0x0AD0, 0x0AE0, 0x0AE3, 0x0AE6, 0x0AF1, 0x0AF9, 0x0AFF, + 0xA830, 0xA839, + // #284 (18180+18): scx=Oriya:Orya + 0x0951, 0x0952, 0x0964, 0x0965, 0x0B01, 0x0B03, 0x0B05, 0x0B0C, + 0x0B0F, 0x0B10, 0x0B13, 0x0B28, 0x0B2A, 0x0B30, 0x0B32, 0x0B33, + 0x0B35, 0x0B39, 0x0B3C, 0x0B44, 0x0B47, 0x0B48, 0x0B4B, 0x0B4D, + 0x0B55, 0x0B57, 0x0B5C, 0x0B5D, 0x0B5F, 0x0B63, 0x0B66, 0x0B77, + 0x1CDA, 0x1CDA, 0x1CF2, 0x1CF2, + // #285 (18198+25): scx=Tamil:Taml + 0x0951, 0x0952, 0x0964, 0x0965, 0x0B82, 0x0B83, 0x0B85, 0x0B8A, + 0x0B8E, 0x0B90, 0x0B92, 0x0B95, 0x0B99, 0x0B9A, 0x0B9C, 0x0B9C, + 0x0B9E, 0x0B9F, 0x0BA3, 0x0BA4, 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB9, + 0x0BBE, 0x0BC2, 0x0BC6, 0x0BC8, 0x0BCA, 0x0BCD, 0x0BD0, 0x0BD0, + 0x0BD7, 0x0BD7, 0x0BE6, 0x0BFA, 0x1CDA, 0x1CDA, 0xA8F3, 0xA8F3, + 0x11301, 0x11301, 0x11303, 0x11303, 0x1133B, 0x1133C, 0x11FC0, 0x11FF1, + 0x11FFF, 0x11FFF, + // #286 (18223+19): scx=Telugu:Telu + 0x0951, 0x0952, 0x0964, 0x0965, 0x0C00, 0x0C0C, 0x0C0E, 0x0C10, + 0x0C12, 0x0C28, 0x0C2A, 0x0C39, 0x0C3C, 0x0C44, 0x0C46, 0x0C48, + 0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0C58, 0x0C5A, 0x0C5C, 0x0C5D, + 0x0C60, 0x0C63, 0x0C66, 0x0C6F, 0x0C77, 0x0C7F, 0x1CD5, 0x1CD6, + 0x1CD8, 0x1CD8, 0x1CDA, 0x1CDA, 0x1CF2, 0x1CF2, + // #287 (18242+21): scx=Kannada:Knda + 0x0951, 0x0952, 0x0964, 0x0965, 0x0C80, 0x0C8C, 0x0C8E, 0x0C90, + 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, 0x0CB5, 0x0CB9, 0x0CBC, 0x0CC4, + 0x0CC6, 0x0CC8, 0x0CCA, 0x0CCD, 0x0CD5, 0x0CD6, 0x0CDC, 0x0CDE, + 0x0CE0, 0x0CE3, 0x0CE6, 0x0CEF, 0x0CF1, 0x0CF3, 0x1CD0, 0x1CD0, + 0x1CD2, 0x1CD3, 0x1CDA, 0x1CDA, 0x1CF2, 0x1CF2, 0x1CF4, 0x1CF4, + 0xA830, 0xA835, + // #288 (18263+12): scx=Malayalam:Mlym + 0x0951, 0x0952, 0x0964, 0x0965, 0x0D00, 0x0D0C, 0x0D0E, 0x0D10, + 0x0D12, 0x0D44, 0x0D46, 0x0D48, 0x0D4A, 0x0D4F, 0x0D54, 0x0D63, + 0x0D66, 0x0D7F, 0x1CDA, 0x1CDA, 0x1CF2, 0x1CF2, 0xA830, 0xA832, + // #289 (18275+15): scx=Sinhala:Sinh + 0x0964, 0x0965, 0x0D81, 0x0D83, 0x0D85, 0x0D96, 0x0D9A, 0x0DB1, + 0x0DB3, 0x0DBB, 0x0DBD, 0x0DBD, 0x0DC0, 0x0DC6, 0x0DCA, 0x0DCA, + 0x0DCF, 0x0DD4, 0x0DD6, 0x0DD6, 0x0DD8, 0x0DDF, 0x0DE6, 0x0DEF, + 0x0DF2, 0x0DF4, 0x1CF2, 0x1CF2, 0x111E1, 0x111F4, + // #290 (18290+6): scx=Thai + 0x02BC, 0x02BC, 0x02D7, 0x02D7, 0x0303, 0x0303, 0x0331, 0x0331, + 0x0E01, 0x0E3A, 0x0E40, 0x0E5B, + // #291 (18296+8): scx=Tibetan:Tibt + 0x0F00, 0x0F47, 0x0F49, 0x0F6C, 0x0F71, 0x0F97, 0x0F99, 0x0FBC, + 0x0FBE, 0x0FCC, 0x0FCE, 0x0FD4, 0x0FD9, 0x0FDA, 0x3008, 0x300B, + // #292 (18304+5): scx=Myanmar:Mymr + 0x1000, 0x109F, 0xA92E, 0xA92E, 0xA9E0, 0xA9FE, 0xAA60, 0xAA7F, + 0x116D0, 0x116E3, + // #293 (18309+13): scx=Georgian:Geor + 0x00B7, 0x00B7, 0x0589, 0x0589, 0x10A0, 0x10C5, 0x10C7, 0x10C7, + 0x10CD, 0x10CD, 0x10D0, 0x10FF, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, + 0x205A, 0x205A, 0x2D00, 0x2D25, 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, + 0x2E31, 0x2E31, + // #294 (18322+21): scx=Hangul:Hang + 0x1100, 0x11FF, 0x3001, 0x3003, 0x3008, 0x3011, 0x3013, 0x301F, + 0x302E, 0x3030, 0x3037, 0x3037, 0x30FB, 0x30FB, 0x3131, 0x318E, + 0x3200, 0x321E, 0x3260, 0x327E, 0xA960, 0xA97C, 0xAC00, 0xD7A3, + 0xD7B0, 0xD7C6, 0xD7CB, 0xD7FB, 0xFE45, 0xFE46, 0xFF61, 0xFF65, + 0xFFA0, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, + 0xFFDA, 0xFFDC, + // #295 (18343+37): scx=Ethiopic:Ethi + 0x030E, 0x030E, 0x1200, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, + 0x1258, 0x1258, 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, + 0x1290, 0x12B0, 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, + 0x12C2, 0x12C5, 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, + 0x1318, 0x135A, 0x135D, 0x137C, 0x1380, 0x1399, 0x2D80, 0x2D96, + 0x2DA0, 0x2DA6, 0x2DA8, 0x2DAE, 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, + 0x2DC0, 0x2DC6, 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, + 0xAB01, 0xAB06, 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, + 0xAB28, 0xAB2E, 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, + 0x1E7F0, 0x1E7FE, + // #296 (18380+8): scx=Cherokee:Cher + 0x0300, 0x0302, 0x0304, 0x0304, 0x030B, 0x030C, 0x0323, 0x0324, + 0x0330, 0x0331, 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0xAB70, 0xABBF, + // #297 (18388+1): scx=Runic:Runr + 0x16A0, 0x16F8, + // #298 (18389+7): scx=Mongolian:Mong + 0x1800, 0x1819, 0x1820, 0x1878, 0x1880, 0x18AA, 0x202F, 0x202F, + 0x3001, 0x3002, 0x3008, 0x300B, 0x11660, 0x1166C, + // #299 (18396+17): scx=Hiragana:Hira + 0x3001, 0x3003, 0x3008, 0x3011, 0x3013, 0x301F, 0x3030, 0x3035, + 0x3037, 0x3037, 0x303C, 0x303D, 0x3041, 0x3096, 0x3099, 0x30A0, + 0x30FB, 0x30FC, 0xFE45, 0xFE46, 0xFF61, 0xFF65, 0xFF70, 0xFF70, + 0xFF9E, 0xFF9F, 0x1B001, 0x1B11F, 0x1B132, 0x1B132, 0x1B150, 0x1B152, + 0x1F200, 0x1F200, + // #300 (18413+22): scx=Katakana:Kana + 0x0305, 0x0305, 0x0323, 0x0323, 0x3001, 0x3003, 0x3008, 0x3011, + 0x3013, 0x301F, 0x3030, 0x3035, 0x3037, 0x3037, 0x303C, 0x303D, + 0x3099, 0x309C, 0x30A0, 0x30FF, 0x31F0, 0x31FF, 0x32D0, 0x32FE, + 0x3300, 0x3357, 0xFE45, 0xFE46, 0xFF61, 0xFF9F, 0x1AFF0, 0x1AFF3, + 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B000, 0x1B120, 0x1B122, + 0x1B155, 0x1B155, 0x1B164, 0x1B167, + // #301 (18435+15): scx=Bopomofo:Bopo + 0x02C7, 0x02C7, 0x02C9, 0x02CB, 0x02D9, 0x02D9, 0x02EA, 0x02EB, + 0x3001, 0x3003, 0x3008, 0x3011, 0x3013, 0x301F, 0x302A, 0x302D, + 0x3030, 0x3030, 0x3037, 0x3037, 0x30FB, 0x30FB, 0x3105, 0x312F, + 0x31A0, 0x31BF, 0xFE45, 0xFE46, 0xFF61, 0xFF65, + // #302 (18450+41): scx=Han:Hani + 0x00B7, 0x00B7, 0x2E80, 0x2E99, 0x2E9B, 0x2EF3, 0x2F00, 0x2FD5, + 0x2FF0, 0x2FFF, 0x3001, 0x3003, 0x3005, 0x3011, 0x3013, 0x301F, + 0x3021, 0x302D, 0x3030, 0x3030, 0x3037, 0x303F, 0x30FB, 0x30FB, + 0x3190, 0x319F, 0x31C0, 0x31E5, 0x31EF, 0x31EF, 0x3220, 0x3247, + 0x3280, 0x32B0, 0x32C0, 0x32CB, 0x32FF, 0x32FF, 0x3358, 0x3370, + 0x337B, 0x337F, 0x33E0, 0x33FE, 0x3400, 0x4DBF, 0x4E00, 0x9FFF, + 0xA700, 0xA707, 0xF900, 0xFA6D, 0xFA70, 0xFAD9, 0xFE45, 0xFE46, + 0xFF61, 0xFF65, 0x16FE2, 0x16FE3, 0x16FF0, 0x16FF6, 0x1D360, 0x1D371, + 0x1F250, 0x1F251, 0x20000, 0x2A6DF, 0x2A700, 0x2B81D, 0x2B820, 0x2CEAD, + 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, + 0x31350, 0x33479, + // #303 (18491+7): scx=Yi:Yiii + 0x3001, 0x3002, 0x3008, 0x3011, 0x3014, 0x301B, 0x30FB, 0x30FB, + 0xA000, 0xA48C, 0xA490, 0xA4C6, 0xFF61, 0xFF65, + // #304 (18498+5): scx=Gothic:Goth + 0x00B7, 0x00B7, 0x0304, 0x0305, 0x0308, 0x0308, 0x0331, 0x0331, + 0x10330, 0x1034A, + // #305 (18503+28): scx=Inherited:Zinh:Qaai + 0x030F, 0x030F, 0x0312, 0x0312, 0x0314, 0x0322, 0x0326, 0x032C, + 0x032F, 0x032F, 0x0332, 0x0341, 0x0343, 0x0344, 0x0346, 0x0357, + 0x0359, 0x035D, 0x035F, 0x0362, 0x0953, 0x0954, 0x1AB0, 0x1ADD, + 0x1AE0, 0x1AEB, 0x1DC2, 0x1DF7, 0x1DF9, 0x1DF9, 0x1DFB, 0x1DFF, + 0x200C, 0x200D, 0x20D0, 0x20EF, 0xFE00, 0xFE0F, 0xFE20, 0xFE2D, + 0x101FD, 0x101FD, 0x1CF00, 0x1CF2D, 0x1CF30, 0x1CF46, 0x1D167, 0x1D169, + 0x1D17B, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, 0xE0100, 0xE01EF, + // #306 (18531+3): scx=Tagalog:Tglg + 0x1700, 0x1715, 0x171F, 0x171F, 0x1735, 0x1736, + // #307 (18534+1): scx=Hanunoo:Hano + 0x1720, 0x1736, + // #308 (18535+2): scx=Buhid:Buhd + 0x1735, 0x1736, 0x1740, 0x1753, + // #309 (18537+4): scx=Tagbanwa:Tagb + 0x1735, 0x1736, 0x1760, 0x176C, 0x176E, 0x1770, 0x1772, 0x1773, + // #310 (18541+6): scx=Limbu:Limb + 0x0965, 0x0965, 0x1900, 0x191E, 0x1920, 0x192B, 0x1930, 0x193B, + 0x1940, 0x1940, 0x1944, 0x194F, + // #311 (18547+6): scx=Tai_Le:Tale + 0x0300, 0x0301, 0x0307, 0x0308, 0x030C, 0x030C, 0x1040, 0x1049, + 0x1950, 0x196D, 0x1970, 0x1974, + // #312 (18553+10): scx=Linear_B:Linb + 0x10000, 0x1000B, 0x1000D, 0x10026, 0x10028, 0x1003A, 0x1003C, 0x1003D, + 0x1003F, 0x1004D, 0x10050, 0x1005D, 0x10080, 0x100FA, 0x10100, 0x10102, + 0x10107, 0x10133, 0x10137, 0x1013F, + // #313 (18563+2): scx=Shavian:Shaw + 0x00B7, 0x00B7, 0x10450, 0x1047F, + // #314 (18565+9): scx=Cypriot:Cprt + 0x10100, 0x10102, 0x10107, 0x10133, 0x10137, 0x1013F, 0x10800, 0x10805, + 0x10808, 0x10808, 0x1080A, 0x10835, 0x10837, 0x10838, 0x1083C, 0x1083C, + 0x1083F, 0x1083F, + // #315 (18574+3): scx=Buginese:Bugi + 0x1A00, 0x1A1B, 0x1A1E, 0x1A1F, 0xA9CF, 0xA9CF, + // #316 (18577+10): scx=Coptic:Copt:Qaac + 0x00B7, 0x00B7, 0x0300, 0x0300, 0x0304, 0x0305, 0x0307, 0x0307, + 0x0374, 0x0375, 0x03E2, 0x03EF, 0x2C80, 0x2CF3, 0x2CF9, 0x2CFF, + 0x2E17, 0x2E17, 0x102E0, 0x102FB, + // #317 (18587+16): scx=Glagolitic:Glag + 0x00B7, 0x00B7, 0x0303, 0x0303, 0x0305, 0x0305, 0x0484, 0x0484, + 0x0487, 0x0487, 0x0589, 0x0589, 0x10FB, 0x10FB, 0x205A, 0x205A, + 0x2C00, 0x2C5F, 0x2E43, 0x2E43, 0xA66F, 0xA66F, 0x1E000, 0x1E006, + 0x1E008, 0x1E018, 0x1E01B, 0x1E021, 0x1E023, 0x1E024, 0x1E026, 0x1E02A, + // #318 (18603+7): scx=Tifinagh:Tfng + 0x0302, 0x0302, 0x0304, 0x0304, 0x0306, 0x0309, 0x0323, 0x0323, + 0x2D30, 0x2D67, 0x2D6F, 0x2D70, 0x2D7F, 0x2D7F, + // #319 (18610+3): scx=Syloti_Nagri:Sylo + 0x0964, 0x0965, 0x09E6, 0x09EF, 0xA800, 0xA82C, + // #320 (18613+5): scx=Phags_Pa:Phag + 0x1802, 0x1803, 0x1805, 0x1805, 0x202F, 0x202F, 0x3002, 0x3002, + 0xA840, 0xA877, + // #321 (18618+6): scx=Nko:Nkoo + 0x060C, 0x060C, 0x061B, 0x061B, 0x061F, 0x061F, 0x07C0, 0x07FA, + 0x07FD, 0x07FF, 0xFD3E, 0xFD3F, + // #322 (18624+1): scx=Kayah_Li:Kali + 0xA900, 0xA92F, + // #323 (18625+2): scx=Lycian:Lyci + 0x205A, 0x205A, 0x10280, 0x1029C, + // #324 (18627+5): scx=Carian:Cari + 0x00B7, 0x00B7, 0x205A, 0x205A, 0x205D, 0x205D, 0x2E31, 0x2E31, + 0x102A0, 0x102D0, + // #325 (18632+4): scx=Lydian:Lydi + 0x00B7, 0x00B7, 0x2E31, 0x2E31, 0x10920, 0x10939, 0x1093F, 0x1093F, + // #326 (18636+4): scx=Avestan:Avst + 0x00B7, 0x00B7, 0x2E30, 0x2E31, 0x10B00, 0x10B35, 0x10B39, 0x10B3F, + // #327 (18640+3): scx=Samaritan:Samr + 0x0800, 0x082D, 0x0830, 0x083E, 0x2E31, 0x2E31, + // #328 (18643+5): scx=Lisu + 0x02BC, 0x02BC, 0x02CD, 0x02CD, 0x300A, 0x300B, 0xA4D0, 0xA4FF, + 0x11FB0, 0x11FB0, + // #329 (18648+3): scx=Javanese:Java + 0xA980, 0xA9CD, 0xA9CF, 0xA9D9, 0xA9DE, 0xA9DF, + // #330 (18651+3): scx=Old_Turkic:Orkh + 0x205A, 0x205A, 0x2E30, 0x2E30, 0x10C00, 0x10C48, + // #331 (18654+5): scx=Kaithi:Kthi + 0x0966, 0x096F, 0x2E31, 0x2E31, 0xA830, 0xA839, 0x11080, 0x110C2, + 0x110CD, 0x110CD, + // #332 (18659+3): scx=Mandaic:Mand + 0x0640, 0x0640, 0x0840, 0x085B, 0x085E, 0x085E, + // #333 (18662+4): scx=Chakma:Cakm + 0x09E6, 0x09EF, 0x1040, 0x1049, 0x11100, 0x11134, 0x11136, 0x11147, + // #334 (18666+2): scx=Meroitic_Hieroglyphs:Mero + 0x205D, 0x205D, 0x10980, 0x1099F, + // #335 (18668+11): scx=Sharada:Shrd + 0x0951, 0x0951, 0x1CD7, 0x1CD7, 0x1CD9, 0x1CD9, 0x1CDC, 0x1CDD, + 0x1CE0, 0x1CE0, 0x1CEA, 0x1CEA, 0x1CED, 0x1CED, 0xA830, 0xA835, + 0xA838, 0xA838, 0x11180, 0x111DF, 0x11B60, 0x11B67, + // #336 (18679+4): scx=Takri:Takr + 0x0964, 0x0965, 0xA830, 0xA839, 0x11680, 0x116B9, 0x116C0, 0x116C9, + // #337 (18683+5): scx=Caucasian_Albanian:Aghb + 0x0304, 0x0304, 0x0331, 0x0331, 0x035E, 0x035E, 0x10530, 0x10563, + 0x1056F, 0x1056F, + // #338 (18688+10): scx=Duployan:Dupl + 0x00B7, 0x00B7, 0x0307, 0x0308, 0x030A, 0x030A, 0x0323, 0x0324, + 0x2E3C, 0x2E3C, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, 0x1BC88, + 0x1BC90, 0x1BC99, 0x1BC9C, 0x1BCA3, + // #339 (18698+3): scx=Elbasan:Elba + 0x00B7, 0x00B7, 0x0305, 0x0305, 0x10500, 0x10527, + // #340 (18701+25): scx=Grantha:Gran + 0x0951, 0x0952, 0x0964, 0x0965, 0x0BE6, 0x0BF3, 0x1CD0, 0x1CD0, + 0x1CD2, 0x1CD3, 0x1CF2, 0x1CF4, 0x1CF8, 0x1CF9, 0x20F0, 0x20F0, + 0x11300, 0x11303, 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, + 0x1132A, 0x11330, 0x11332, 0x11333, 0x11335, 0x11339, 0x1133B, 0x11344, + 0x11347, 0x11348, 0x1134B, 0x1134D, 0x11350, 0x11350, 0x11357, 0x11357, + 0x1135D, 0x11363, 0x11366, 0x1136C, 0x11370, 0x11374, 0x11FD0, 0x11FD1, + 0x11FD3, 0x11FD3, + // #341 (18726+4): scx=Khojki:Khoj + 0x0AE6, 0x0AEF, 0xA830, 0xA839, 0x11200, 0x11211, 0x11213, 0x11241, + // #342 (18730+4): scx=Linear_A:Lina + 0x10107, 0x10133, 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, + // #343 (18734+4): scx=Mahajani:Mahj + 0x00B7, 0x00B7, 0x0964, 0x096F, 0xA830, 0xA839, 0x11150, 0x11176, + // #344 (18738+3): scx=Manichaean:Mani + 0x0640, 0x0640, 0x10AC0, 0x10AE6, 0x10AEB, 0x10AF6, + // #345 (18741+3): scx=Modi + 0xA830, 0xA839, 0x11600, 0x11644, 0x11650, 0x11659, + // #346 (18744+6): scx=Old_Permic:Perm + 0x00B7, 0x00B7, 0x0300, 0x0300, 0x0306, 0x0308, 0x0313, 0x0313, + 0x0483, 0x0483, 0x10350, 0x1037A, + // #347 (18750+4): scx=Psalter_Pahlavi:Phlp + 0x0640, 0x0640, 0x10B80, 0x10B91, 0x10B99, 0x10B9C, 0x10BA9, 0x10BAF, + // #348 (18754+4): scx=Khudawadi:Sind + 0x0964, 0x0965, 0xA830, 0xA839, 0x112B0, 0x112EA, 0x112F0, 0x112F9, + // #349 (18758+8): scx=Tirhuta:Tirh + 0x0951, 0x0952, 0x0964, 0x0965, 0x1CD5, 0x1CD5, 0x1CE2, 0x1CE2, + 0x1CF2, 0x1CF2, 0xA830, 0xA839, 0x11480, 0x114C7, 0x114D0, 0x114D9, + // #350 (18766+6): scx=Multani:Mult + 0x0A66, 0x0A6F, 0x11280, 0x11286, 0x11288, 0x11288, 0x1128A, 0x1128D, + 0x1128F, 0x1129D, 0x1129F, 0x112A9, + // #351 (18772+7): scx=Old_Hungarian:Hung + 0x205A, 0x205A, 0x205D, 0x205D, 0x2E31, 0x2E31, 0x2E41, 0x2E41, + 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, 0x10CFA, 0x10CFF, + // #352 (18779+7): scx=Adlam:Adlm + 0x061F, 0x061F, 0x0640, 0x0640, 0x204F, 0x204F, 0x2E41, 0x2E41, + 0x1E900, 0x1E94B, 0x1E950, 0x1E959, 0x1E95E, 0x1E95F, + // #353 (18786+9): scx=Newa + 0x0951, 0x0952, 0x1CD5, 0x1CD5, 0x1CD7, 0x1CD8, 0x1CE2, 0x1CE2, + 0x1CE9, 0x1CE9, 0x1CEB, 0x1CEB, 0x1CED, 0x1CED, 0x11400, 0x1145B, + 0x1145D, 0x11461, + // #354 (18795+6): scx=Osage:Osge + 0x0301, 0x0301, 0x0304, 0x0304, 0x030B, 0x030B, 0x0358, 0x0358, + 0x104B0, 0x104D3, 0x104D8, 0x104FB, + // #355 (18801+6): scx=Tangut:Tang + 0x2FF0, 0x2FFF, 0x31EF, 0x31EF, 0x16FE0, 0x16FE0, 0x17000, 0x18AFF, + 0x18D00, 0x18D1E, 0x18D80, 0x18DF2, + // #356 (18807+8): scx=Masaram_Gondi:Gonm + 0x0964, 0x0965, 0x11D00, 0x11D06, 0x11D08, 0x11D09, 0x11D0B, 0x11D36, + 0x11D3A, 0x11D3A, 0x11D3C, 0x11D3D, 0x11D3F, 0x11D47, 0x11D50, 0x11D59, + // #357 (18815+3): scx=Dogra:Dogr + 0x0964, 0x096F, 0xA830, 0xA839, 0x11800, 0x1183B, + // #358 (18818+8): scx=Gunjala_Gondi:Gong + 0x00B7, 0x00B7, 0x0964, 0x0965, 0x11D60, 0x11D65, 0x11D67, 0x11D68, + 0x11D6A, 0x11D8E, 0x11D90, 0x11D91, 0x11D93, 0x11D98, 0x11DA0, 0x11DA9, + // #359 (18826+7): scx=Hanifi_Rohingya:Rohg + 0x060C, 0x060C, 0x061B, 0x061B, 0x061F, 0x061F, 0x0640, 0x0640, + 0x06D4, 0x06D4, 0x10D00, 0x10D27, 0x10D30, 0x10D39, + // #360 (18833+2): scx=Sogdian:Sogd + 0x0640, 0x0640, 0x10F30, 0x10F59, + // #361 (18835+10): scx=Nandinagari:Nand + 0x0951, 0x0951, 0x0964, 0x0965, 0x0CE6, 0x0CEF, 0x1CE9, 0x1CE9, + 0x1CF2, 0x1CF2, 0x1CFA, 0x1CFA, 0xA830, 0xA835, 0x119A0, 0x119A7, + 0x119AA, 0x119D7, 0x119DA, 0x119E4, + // #362 (18845+7): scx=Yezidi:Yezi + 0x060C, 0x060C, 0x061B, 0x061B, 0x061F, 0x061F, 0x0660, 0x0669, + 0x10E80, 0x10EA9, 0x10EAB, 0x10EAD, 0x10EB0, 0x10EB1, + // #363 (18852+2): scx=Cypro_Minoan:Cpmn + 0x10100, 0x10101, 0x12F90, 0x12FF2, + // #364 (18854+3): scx=Old_Uyghur:Ougr + 0x0640, 0x0640, 0x10AF2, 0x10AF2, 0x10F70, 0x10F89, + // #365 (18857+2): scx=Toto + 0x02BC, 0x02BC, 0x1E290, 0x1E2AE, + // #366 (18859+6): scx=Garay:Gara + 0x060C, 0x060C, 0x061B, 0x061B, 0x061F, 0x061F, 0x10D40, 0x10D65, + 0x10D69, 0x10D85, 0x10D8E, 0x10D8F, + // #367 (18865+2): scx=Gurung_Khema:Gukh + 0x0965, 0x0965, 0x16100, 0x16139, + // #368 (18867+3): scx=Ol_Onal:Onao + 0x0964, 0x0965, 0x1E5D0, 0x1E5FA, 0x1E5FF, 0x1E5FF, + // #369 (18870+8): scx=Sunuwar:Sunu + 0x0300, 0x0301, 0x0303, 0x0303, 0x030D, 0x030D, 0x0310, 0x0310, + 0x032D, 0x032D, 0x0331, 0x0331, 0x11BC0, 0x11BE1, 0x11BF0, 0x11BF9, + // #370 (18878+7): scx=Todhri:Todr + 0x0301, 0x0301, 0x0304, 0x0304, 0x0307, 0x0307, 0x0311, 0x0311, + 0x0313, 0x0313, 0x035E, 0x035E, 0x105C0, 0x105F3, + // #371 (18885+16): scx=Tulu_Tigalari:Tutg + 0x0CE6, 0x0CEF, 0x1CF2, 0x1CF2, 0x1CF4, 0x1CF4, 0xA830, 0xA835, + 0xA8F1, 0xA8F1, 0x11380, 0x11389, 0x1138B, 0x1138B, 0x1138E, 0x1138E, + 0x11390, 0x113B5, 0x113B7, 0x113C0, 0x113C2, 0x113C2, 0x113C5, 0x113C5, + 0x113C7, 0x113CA, 0x113CC, 0x113D5, 0x113D7, 0x113D8, 0x113E1, 0x113E2 +#if !defined(SRELL_NO_UNICODE_POS) + , + // #372 (18901+14704/2): bp=RGI_Emoji + // 1366/2 + 48/2 + 1996/2 + 778/2 + 24/2 + 10492/2 + // #373 (18901+1366/2): bp=Basic_Emoji + 1, 0x231A, 0x231B, + 1, 0x23E9, 0x23EC, + 2, 0x23F0, + 2, 0x23F3, + 1, 0x25FD, 0x25FE, + 1, 0x2614, 0x2615, + 1, 0x2648, 0x2653, + 2, 0x267F, + 2, 0x2693, + 2, 0x26A1, + 1, 0x26AA, 0x26AB, + 1, 0x26BD, 0x26BE, + 1, 0x26C4, 0x26C5, + 2, 0x26CE, + 2, 0x26D4, + 2, 0x26EA, + 1, 0x26F2, 0x26F3, + 2, 0x26F5, + 2, 0x26FA, + 2, 0x26FD, + 2, 0x2705, + 1, 0x270A, 0x270B, + 2, 0x2728, + 2, 0x274C, + 2, 0x274E, + 1, 0x2753, 0x2755, + 2, 0x2757, + 1, 0x2795, 0x2797, + 2, 0x27B0, + 2, 0x27BF, + 1, 0x2B1B, 0x2B1C, + 2, 0x2B50, + 2, 0x2B55, + 2, 0x1F004, + 2, 0x1F0CF, + 2, 0x1F18E, + 1, 0x1F191, 0x1F19A, + 2, 0x1F201, + 2, 0x1F21A, + 2, 0x1F22F, + 1, 0x1F232, 0x1F236, + 1, 0x1F238, 0x1F23A, + 1, 0x1F250, 0x1F251, + 1, 0x1F300, 0x1F30C, + 1, 0x1F30D, 0x1F30E, + 2, 0x1F30F, + 2, 0x1F310, + 2, 0x1F311, + 2, 0x1F312, + 1, 0x1F313, 0x1F315, + 1, 0x1F316, 0x1F318, + 2, 0x1F319, + 2, 0x1F31A, + 2, 0x1F31B, + 2, 0x1F31C, + 1, 0x1F31D, 0x1F31E, + 1, 0x1F31F, 0x1F320, + 1, 0x1F32D, 0x1F32F, + 1, 0x1F330, 0x1F331, + 1, 0x1F332, 0x1F333, + 1, 0x1F334, 0x1F335, + 1, 0x1F337, 0x1F34A, + 2, 0x1F34B, + 1, 0x1F34C, 0x1F34F, + 2, 0x1F350, + 1, 0x1F351, 0x1F37B, + 2, 0x1F37C, + 1, 0x1F37E, 0x1F37F, + 1, 0x1F380, 0x1F393, + 1, 0x1F3A0, 0x1F3C4, + 2, 0x1F3C5, + 2, 0x1F3C6, + 2, 0x1F3C7, + 2, 0x1F3C8, + 2, 0x1F3C9, + 2, 0x1F3CA, + 1, 0x1F3CF, 0x1F3D3, + 1, 0x1F3E0, 0x1F3E3, + 2, 0x1F3E4, + 1, 0x1F3E5, 0x1F3F0, + 2, 0x1F3F4, + 1, 0x1F3F8, 0x1F407, + 2, 0x1F408, + 1, 0x1F409, 0x1F40B, + 1, 0x1F40C, 0x1F40E, + 1, 0x1F40F, 0x1F410, + 1, 0x1F411, 0x1F412, + 2, 0x1F413, + 2, 0x1F414, + 2, 0x1F415, + 2, 0x1F416, + 1, 0x1F417, 0x1F429, + 2, 0x1F42A, + 1, 0x1F42B, 0x1F43E, + 2, 0x1F440, + 1, 0x1F442, 0x1F464, + 2, 0x1F465, + 1, 0x1F466, 0x1F46B, + 1, 0x1F46C, 0x1F46D, + 1, 0x1F46E, 0x1F4AC, + 2, 0x1F4AD, + 1, 0x1F4AE, 0x1F4B5, + 1, 0x1F4B6, 0x1F4B7, + 1, 0x1F4B8, 0x1F4EB, + 1, 0x1F4EC, 0x1F4ED, + 2, 0x1F4EE, + 2, 0x1F4EF, + 1, 0x1F4F0, 0x1F4F4, + 2, 0x1F4F5, + 1, 0x1F4F6, 0x1F4F7, + 2, 0x1F4F8, + 1, 0x1F4F9, 0x1F4FC, + 1, 0x1F4FF, 0x1F502, + 2, 0x1F503, + 1, 0x1F504, 0x1F507, + 2, 0x1F508, + 2, 0x1F509, + 1, 0x1F50A, 0x1F514, + 2, 0x1F515, + 1, 0x1F516, 0x1F52B, + 1, 0x1F52C, 0x1F52D, + 1, 0x1F52E, 0x1F53D, + 1, 0x1F54B, 0x1F54E, + 1, 0x1F550, 0x1F55B, + 1, 0x1F55C, 0x1F567, + 2, 0x1F57A, + 1, 0x1F595, 0x1F596, + 2, 0x1F5A4, + 1, 0x1F5FB, 0x1F5FF, + 2, 0x1F600, + 1, 0x1F601, 0x1F606, + 1, 0x1F607, 0x1F608, + 1, 0x1F609, 0x1F60D, + 2, 0x1F60E, + 2, 0x1F60F, + 2, 0x1F610, + 2, 0x1F611, + 1, 0x1F612, 0x1F614, + 2, 0x1F615, + 2, 0x1F616, + 2, 0x1F617, + 2, 0x1F618, + 2, 0x1F619, + 2, 0x1F61A, + 2, 0x1F61B, + 1, 0x1F61C, 0x1F61E, + 2, 0x1F61F, + 1, 0x1F620, 0x1F625, + 1, 0x1F626, 0x1F627, + 1, 0x1F628, 0x1F62B, + 2, 0x1F62C, + 2, 0x1F62D, + 1, 0x1F62E, 0x1F62F, + 1, 0x1F630, 0x1F633, + 2, 0x1F634, + 2, 0x1F635, + 2, 0x1F636, + 1, 0x1F637, 0x1F640, + 1, 0x1F641, 0x1F644, + 1, 0x1F645, 0x1F64F, + 2, 0x1F680, + 1, 0x1F681, 0x1F682, + 1, 0x1F683, 0x1F685, + 2, 0x1F686, + 2, 0x1F687, + 2, 0x1F688, + 2, 0x1F689, + 1, 0x1F68A, 0x1F68B, + 2, 0x1F68C, + 2, 0x1F68D, + 2, 0x1F68E, + 2, 0x1F68F, + 2, 0x1F690, + 1, 0x1F691, 0x1F693, + 2, 0x1F694, + 2, 0x1F695, + 2, 0x1F696, + 2, 0x1F697, + 2, 0x1F698, + 1, 0x1F699, 0x1F69A, + 1, 0x1F69B, 0x1F6A1, + 2, 0x1F6A2, + 2, 0x1F6A3, + 1, 0x1F6A4, 0x1F6A5, + 2, 0x1F6A6, + 1, 0x1F6A7, 0x1F6AD, + 1, 0x1F6AE, 0x1F6B1, + 2, 0x1F6B2, + 1, 0x1F6B3, 0x1F6B5, + 2, 0x1F6B6, + 1, 0x1F6B7, 0x1F6B8, + 1, 0x1F6B9, 0x1F6BE, + 2, 0x1F6BF, + 2, 0x1F6C0, + 1, 0x1F6C1, 0x1F6C5, + 2, 0x1F6CC, + 2, 0x1F6D0, + 1, 0x1F6D1, 0x1F6D2, + 2, 0x1F6D5, + 1, 0x1F6D6, 0x1F6D7, + 2, 0x1F6D8, + 2, 0x1F6DC, + 1, 0x1F6DD, 0x1F6DF, + 1, 0x1F6EB, 0x1F6EC, + 1, 0x1F6F4, 0x1F6F6, + 1, 0x1F6F7, 0x1F6F8, + 2, 0x1F6F9, + 2, 0x1F6FA, + 1, 0x1F6FB, 0x1F6FC, + 1, 0x1F7E0, 0x1F7EB, + 2, 0x1F7F0, + 2, 0x1F90C, + 1, 0x1F90D, 0x1F90F, + 1, 0x1F910, 0x1F918, + 1, 0x1F919, 0x1F91E, + 2, 0x1F91F, + 1, 0x1F920, 0x1F927, + 1, 0x1F928, 0x1F92F, + 2, 0x1F930, + 1, 0x1F931, 0x1F932, + 1, 0x1F933, 0x1F93A, + 1, 0x1F93C, 0x1F93E, + 2, 0x1F93F, + 1, 0x1F940, 0x1F945, + 1, 0x1F947, 0x1F94B, + 2, 0x1F94C, + 1, 0x1F94D, 0x1F94F, + 1, 0x1F950, 0x1F95E, + 1, 0x1F95F, 0x1F96B, + 1, 0x1F96C, 0x1F970, + 2, 0x1F971, + 2, 0x1F972, + 1, 0x1F973, 0x1F976, + 1, 0x1F977, 0x1F978, + 2, 0x1F979, + 2, 0x1F97A, + 2, 0x1F97B, + 1, 0x1F97C, 0x1F97F, + 1, 0x1F980, 0x1F984, + 1, 0x1F985, 0x1F991, + 1, 0x1F992, 0x1F997, + 1, 0x1F998, 0x1F9A2, + 1, 0x1F9A3, 0x1F9A4, + 1, 0x1F9A5, 0x1F9AA, + 1, 0x1F9AB, 0x1F9AD, + 1, 0x1F9AE, 0x1F9AF, + 1, 0x1F9B0, 0x1F9B9, + 1, 0x1F9BA, 0x1F9BF, + 2, 0x1F9C0, + 1, 0x1F9C1, 0x1F9C2, + 1, 0x1F9C3, 0x1F9CA, + 2, 0x1F9CB, + 2, 0x1F9CC, + 1, 0x1F9CD, 0x1F9CF, + 1, 0x1F9D0, 0x1F9E6, + 1, 0x1F9E7, 0x1F9FF, + 1, 0x1FA70, 0x1FA73, + 2, 0x1FA74, + 1, 0x1FA75, 0x1FA77, + 1, 0x1FA78, 0x1FA7A, + 1, 0x1FA7B, 0x1FA7C, + 1, 0x1FA80, 0x1FA82, + 1, 0x1FA83, 0x1FA86, + 1, 0x1FA87, 0x1FA88, + 2, 0x1FA89, + 2, 0x1FA8A, + 2, 0x1FA8E, + 2, 0x1FA8F, + 1, 0x1FA90, 0x1FA95, + 1, 0x1FA96, 0x1FAA8, + 1, 0x1FAA9, 0x1FAAC, + 1, 0x1FAAD, 0x1FAAF, + 1, 0x1FAB0, 0x1FAB6, + 1, 0x1FAB7, 0x1FABA, + 1, 0x1FABB, 0x1FABD, + 2, 0x1FABE, + 2, 0x1FABF, + 1, 0x1FAC0, 0x1FAC2, + 1, 0x1FAC3, 0x1FAC5, + 2, 0x1FAC6, + 2, 0x1FAC8, + 2, 0x1FACD, + 1, 0x1FACE, 0x1FACF, + 1, 0x1FAD0, 0x1FAD6, + 1, 0x1FAD7, 0x1FAD9, + 1, 0x1FADA, 0x1FADB, + 2, 0x1FADC, + 2, 0x1FADF, + 1, 0x1FAE0, 0x1FAE7, + 2, 0x1FAE8, + 2, 0x1FAE9, + 2, 0x1FAEA, + 2, 0x1FAEF, + 1, 0x1FAF0, 0x1FAF6, + 1, 0x1FAF7, 0x1FAF8, + 3, 0x00A9, 0xFE0F, + 3, 0x00AE, 0xFE0F, + 3, 0x203C, 0xFE0F, + 3, 0x2049, 0xFE0F, + 3, 0x2122, 0xFE0F, + 3, 0x2139, 0xFE0F, + 3, 0x2194, 0xFE0F, + 3, 0x2195, 0xFE0F, + 3, 0x2196, 0xFE0F, + 3, 0x2197, 0xFE0F, + 3, 0x2198, 0xFE0F, + 3, 0x2199, 0xFE0F, + 3, 0x21A9, 0xFE0F, + 3, 0x21AA, 0xFE0F, + 3, 0x2328, 0xFE0F, + 3, 0x23CF, 0xFE0F, + 3, 0x23ED, 0xFE0F, + 3, 0x23EE, 0xFE0F, + 3, 0x23EF, 0xFE0F, + 3, 0x23F1, 0xFE0F, + 3, 0x23F2, 0xFE0F, + 3, 0x23F8, 0xFE0F, + 3, 0x23F9, 0xFE0F, + 3, 0x23FA, 0xFE0F, + 3, 0x24C2, 0xFE0F, + 3, 0x25AA, 0xFE0F, + 3, 0x25AB, 0xFE0F, + 3, 0x25B6, 0xFE0F, + 3, 0x25C0, 0xFE0F, + 3, 0x25FB, 0xFE0F, + 3, 0x25FC, 0xFE0F, + 3, 0x2600, 0xFE0F, + 3, 0x2601, 0xFE0F, + 3, 0x2602, 0xFE0F, + 3, 0x2603, 0xFE0F, + 3, 0x2604, 0xFE0F, + 3, 0x260E, 0xFE0F, + 3, 0x2611, 0xFE0F, + 3, 0x2618, 0xFE0F, + 3, 0x261D, 0xFE0F, + 3, 0x2620, 0xFE0F, + 3, 0x2622, 0xFE0F, + 3, 0x2623, 0xFE0F, + 3, 0x2626, 0xFE0F, + 3, 0x262A, 0xFE0F, + 3, 0x262E, 0xFE0F, + 3, 0x262F, 0xFE0F, + 3, 0x2638, 0xFE0F, + 3, 0x2639, 0xFE0F, + 3, 0x263A, 0xFE0F, + 3, 0x2640, 0xFE0F, + 3, 0x2642, 0xFE0F, + 3, 0x265F, 0xFE0F, + 3, 0x2660, 0xFE0F, + 3, 0x2663, 0xFE0F, + 3, 0x2665, 0xFE0F, + 3, 0x2666, 0xFE0F, + 3, 0x2668, 0xFE0F, + 3, 0x267B, 0xFE0F, + 3, 0x267E, 0xFE0F, + 3, 0x2692, 0xFE0F, + 3, 0x2694, 0xFE0F, + 3, 0x2695, 0xFE0F, + 3, 0x2696, 0xFE0F, + 3, 0x2697, 0xFE0F, + 3, 0x2699, 0xFE0F, + 3, 0x269B, 0xFE0F, + 3, 0x269C, 0xFE0F, + 3, 0x26A0, 0xFE0F, + 3, 0x26A7, 0xFE0F, + 3, 0x26B0, 0xFE0F, + 3, 0x26B1, 0xFE0F, + 3, 0x26C8, 0xFE0F, + 3, 0x26CF, 0xFE0F, + 3, 0x26D1, 0xFE0F, + 3, 0x26D3, 0xFE0F, + 3, 0x26E9, 0xFE0F, + 3, 0x26F0, 0xFE0F, + 3, 0x26F1, 0xFE0F, + 3, 0x26F4, 0xFE0F, + 3, 0x26F7, 0xFE0F, + 3, 0x26F8, 0xFE0F, + 3, 0x26F9, 0xFE0F, + 3, 0x2702, 0xFE0F, + 3, 0x2708, 0xFE0F, + 3, 0x2709, 0xFE0F, + 3, 0x270C, 0xFE0F, + 3, 0x270D, 0xFE0F, + 3, 0x270F, 0xFE0F, + 3, 0x2712, 0xFE0F, + 3, 0x2714, 0xFE0F, + 3, 0x2716, 0xFE0F, + 3, 0x271D, 0xFE0F, + 3, 0x2721, 0xFE0F, + 3, 0x2733, 0xFE0F, + 3, 0x2734, 0xFE0F, + 3, 0x2744, 0xFE0F, + 3, 0x2747, 0xFE0F, + 3, 0x2763, 0xFE0F, + 3, 0x2764, 0xFE0F, + 3, 0x27A1, 0xFE0F, + 3, 0x2934, 0xFE0F, + 3, 0x2935, 0xFE0F, + 3, 0x2B05, 0xFE0F, + 3, 0x2B06, 0xFE0F, + 3, 0x2B07, 0xFE0F, + 3, 0x3030, 0xFE0F, + 3, 0x303D, 0xFE0F, + 3, 0x3297, 0xFE0F, + 3, 0x3299, 0xFE0F, + 3, 0x1F170, 0xFE0F, + 3, 0x1F171, 0xFE0F, + 3, 0x1F17E, 0xFE0F, + 3, 0x1F17F, 0xFE0F, + 3, 0x1F202, 0xFE0F, + 3, 0x1F237, 0xFE0F, + 3, 0x1F321, 0xFE0F, + 3, 0x1F324, 0xFE0F, + 3, 0x1F325, 0xFE0F, + 3, 0x1F326, 0xFE0F, + 3, 0x1F327, 0xFE0F, + 3, 0x1F328, 0xFE0F, + 3, 0x1F329, 0xFE0F, + 3, 0x1F32A, 0xFE0F, + 3, 0x1F32B, 0xFE0F, + 3, 0x1F32C, 0xFE0F, + 3, 0x1F336, 0xFE0F, + 3, 0x1F37D, 0xFE0F, + 3, 0x1F396, 0xFE0F, + 3, 0x1F397, 0xFE0F, + 3, 0x1F399, 0xFE0F, + 3, 0x1F39A, 0xFE0F, + 3, 0x1F39B, 0xFE0F, + 3, 0x1F39E, 0xFE0F, + 3, 0x1F39F, 0xFE0F, + 3, 0x1F3CB, 0xFE0F, + 3, 0x1F3CC, 0xFE0F, + 3, 0x1F3CD, 0xFE0F, + 3, 0x1F3CE, 0xFE0F, + 3, 0x1F3D4, 0xFE0F, + 3, 0x1F3D5, 0xFE0F, + 3, 0x1F3D6, 0xFE0F, + 3, 0x1F3D7, 0xFE0F, + 3, 0x1F3D8, 0xFE0F, + 3, 0x1F3D9, 0xFE0F, + 3, 0x1F3DA, 0xFE0F, + 3, 0x1F3DB, 0xFE0F, + 3, 0x1F3DC, 0xFE0F, + 3, 0x1F3DD, 0xFE0F, + 3, 0x1F3DE, 0xFE0F, + 3, 0x1F3DF, 0xFE0F, + 3, 0x1F3F3, 0xFE0F, + 3, 0x1F3F5, 0xFE0F, + 3, 0x1F3F7, 0xFE0F, + 3, 0x1F43F, 0xFE0F, + 3, 0x1F441, 0xFE0F, + 3, 0x1F4FD, 0xFE0F, + 3, 0x1F549, 0xFE0F, + 3, 0x1F54A, 0xFE0F, + 3, 0x1F56F, 0xFE0F, + 3, 0x1F570, 0xFE0F, + 3, 0x1F573, 0xFE0F, + 3, 0x1F574, 0xFE0F, + 3, 0x1F575, 0xFE0F, + 3, 0x1F576, 0xFE0F, + 3, 0x1F577, 0xFE0F, + 3, 0x1F578, 0xFE0F, + 3, 0x1F579, 0xFE0F, + 3, 0x1F587, 0xFE0F, + 3, 0x1F58A, 0xFE0F, + 3, 0x1F58B, 0xFE0F, + 3, 0x1F58C, 0xFE0F, + 3, 0x1F58D, 0xFE0F, + 3, 0x1F590, 0xFE0F, + 3, 0x1F5A5, 0xFE0F, + 3, 0x1F5A8, 0xFE0F, + 3, 0x1F5B1, 0xFE0F, + 3, 0x1F5B2, 0xFE0F, + 3, 0x1F5BC, 0xFE0F, + 3, 0x1F5C2, 0xFE0F, + 3, 0x1F5C3, 0xFE0F, + 3, 0x1F5C4, 0xFE0F, + 3, 0x1F5D1, 0xFE0F, + 3, 0x1F5D2, 0xFE0F, + 3, 0x1F5D3, 0xFE0F, + 3, 0x1F5DC, 0xFE0F, + 3, 0x1F5DD, 0xFE0F, + 3, 0x1F5DE, 0xFE0F, + 3, 0x1F5E1, 0xFE0F, + 3, 0x1F5E3, 0xFE0F, + 3, 0x1F5E8, 0xFE0F, + 3, 0x1F5EF, 0xFE0F, + 3, 0x1F5F3, 0xFE0F, + 3, 0x1F5FA, 0xFE0F, + 3, 0x1F6CB, 0xFE0F, + 3, 0x1F6CD, 0xFE0F, + 3, 0x1F6CE, 0xFE0F, + 3, 0x1F6CF, 0xFE0F, + 3, 0x1F6E0, 0xFE0F, + 3, 0x1F6E1, 0xFE0F, + 3, 0x1F6E2, 0xFE0F, + 3, 0x1F6E3, 0xFE0F, + 3, 0x1F6E4, 0xFE0F, + 3, 0x1F6E5, 0xFE0F, + 3, 0x1F6E9, 0xFE0F, + 3, 0x1F6F0, 0xFE0F, + 3, 0x1F6F3, 0xFE0F, + 0, // Padding. + // #374 (19584+48/2): bp=Emoji_Keycap_Sequence + 4, 0x0023, 0xFE0F, 0x20E3, + 4, 0x002A, 0xFE0F, 0x20E3, + 4, 0x0030, 0xFE0F, 0x20E3, + 4, 0x0031, 0xFE0F, 0x20E3, + 4, 0x0032, 0xFE0F, 0x20E3, + 4, 0x0033, 0xFE0F, 0x20E3, + 4, 0x0034, 0xFE0F, 0x20E3, + 4, 0x0035, 0xFE0F, 0x20E3, + 4, 0x0036, 0xFE0F, 0x20E3, + 4, 0x0037, 0xFE0F, 0x20E3, + 4, 0x0038, 0xFE0F, 0x20E3, + 4, 0x0039, 0xFE0F, 0x20E3, + // #375 (19608+1996/2): bp=RGI_Emoji_Modifier_Sequence + 3, 0x261D, 0x1F3FB, + 3, 0x261D, 0x1F3FC, + 3, 0x261D, 0x1F3FD, + 3, 0x261D, 0x1F3FE, + 3, 0x261D, 0x1F3FF, + 3, 0x26F9, 0x1F3FB, + 3, 0x26F9, 0x1F3FC, + 3, 0x26F9, 0x1F3FD, + 3, 0x26F9, 0x1F3FE, + 3, 0x26F9, 0x1F3FF, + 3, 0x270A, 0x1F3FB, + 3, 0x270A, 0x1F3FC, + 3, 0x270A, 0x1F3FD, + 3, 0x270A, 0x1F3FE, + 3, 0x270A, 0x1F3FF, + 3, 0x270B, 0x1F3FB, + 3, 0x270B, 0x1F3FC, + 3, 0x270B, 0x1F3FD, + 3, 0x270B, 0x1F3FE, + 3, 0x270B, 0x1F3FF, + 3, 0x270C, 0x1F3FB, + 3, 0x270C, 0x1F3FC, + 3, 0x270C, 0x1F3FD, + 3, 0x270C, 0x1F3FE, + 3, 0x270C, 0x1F3FF, + 3, 0x270D, 0x1F3FB, + 3, 0x270D, 0x1F3FC, + 3, 0x270D, 0x1F3FD, + 3, 0x270D, 0x1F3FE, + 3, 0x270D, 0x1F3FF, + 3, 0x1F385, 0x1F3FB, + 3, 0x1F385, 0x1F3FC, + 3, 0x1F385, 0x1F3FD, + 3, 0x1F385, 0x1F3FE, + 3, 0x1F385, 0x1F3FF, + 3, 0x1F3C2, 0x1F3FB, + 3, 0x1F3C2, 0x1F3FC, + 3, 0x1F3C2, 0x1F3FD, + 3, 0x1F3C2, 0x1F3FE, + 3, 0x1F3C2, 0x1F3FF, + 3, 0x1F3C3, 0x1F3FB, + 3, 0x1F3C3, 0x1F3FC, + 3, 0x1F3C3, 0x1F3FD, + 3, 0x1F3C3, 0x1F3FE, + 3, 0x1F3C3, 0x1F3FF, + 3, 0x1F3C4, 0x1F3FB, + 3, 0x1F3C4, 0x1F3FC, + 3, 0x1F3C4, 0x1F3FD, + 3, 0x1F3C4, 0x1F3FE, + 3, 0x1F3C4, 0x1F3FF, + 3, 0x1F3C7, 0x1F3FB, + 3, 0x1F3C7, 0x1F3FC, + 3, 0x1F3C7, 0x1F3FD, + 3, 0x1F3C7, 0x1F3FE, + 3, 0x1F3C7, 0x1F3FF, + 3, 0x1F3CA, 0x1F3FB, + 3, 0x1F3CA, 0x1F3FC, + 3, 0x1F3CA, 0x1F3FD, + 3, 0x1F3CA, 0x1F3FE, + 3, 0x1F3CA, 0x1F3FF, + 3, 0x1F3CB, 0x1F3FB, + 3, 0x1F3CB, 0x1F3FC, + 3, 0x1F3CB, 0x1F3FD, + 3, 0x1F3CB, 0x1F3FE, + 3, 0x1F3CB, 0x1F3FF, + 3, 0x1F3CC, 0x1F3FB, + 3, 0x1F3CC, 0x1F3FC, + 3, 0x1F3CC, 0x1F3FD, + 3, 0x1F3CC, 0x1F3FE, + 3, 0x1F3CC, 0x1F3FF, + 3, 0x1F442, 0x1F3FB, + 3, 0x1F442, 0x1F3FC, + 3, 0x1F442, 0x1F3FD, + 3, 0x1F442, 0x1F3FE, + 3, 0x1F442, 0x1F3FF, + 3, 0x1F443, 0x1F3FB, + 3, 0x1F443, 0x1F3FC, + 3, 0x1F443, 0x1F3FD, + 3, 0x1F443, 0x1F3FE, + 3, 0x1F443, 0x1F3FF, + 3, 0x1F446, 0x1F3FB, + 3, 0x1F446, 0x1F3FC, + 3, 0x1F446, 0x1F3FD, + 3, 0x1F446, 0x1F3FE, + 3, 0x1F446, 0x1F3FF, + 3, 0x1F447, 0x1F3FB, + 3, 0x1F447, 0x1F3FC, + 3, 0x1F447, 0x1F3FD, + 3, 0x1F447, 0x1F3FE, + 3, 0x1F447, 0x1F3FF, + 3, 0x1F448, 0x1F3FB, + 3, 0x1F448, 0x1F3FC, + 3, 0x1F448, 0x1F3FD, + 3, 0x1F448, 0x1F3FE, + 3, 0x1F448, 0x1F3FF, + 3, 0x1F449, 0x1F3FB, + 3, 0x1F449, 0x1F3FC, + 3, 0x1F449, 0x1F3FD, + 3, 0x1F449, 0x1F3FE, + 3, 0x1F449, 0x1F3FF, + 3, 0x1F44A, 0x1F3FB, + 3, 0x1F44A, 0x1F3FC, + 3, 0x1F44A, 0x1F3FD, + 3, 0x1F44A, 0x1F3FE, + 3, 0x1F44A, 0x1F3FF, + 3, 0x1F44B, 0x1F3FB, + 3, 0x1F44B, 0x1F3FC, + 3, 0x1F44B, 0x1F3FD, + 3, 0x1F44B, 0x1F3FE, + 3, 0x1F44B, 0x1F3FF, + 3, 0x1F44C, 0x1F3FB, + 3, 0x1F44C, 0x1F3FC, + 3, 0x1F44C, 0x1F3FD, + 3, 0x1F44C, 0x1F3FE, + 3, 0x1F44C, 0x1F3FF, + 3, 0x1F44D, 0x1F3FB, + 3, 0x1F44D, 0x1F3FC, + 3, 0x1F44D, 0x1F3FD, + 3, 0x1F44D, 0x1F3FE, + 3, 0x1F44D, 0x1F3FF, + 3, 0x1F44E, 0x1F3FB, + 3, 0x1F44E, 0x1F3FC, + 3, 0x1F44E, 0x1F3FD, + 3, 0x1F44E, 0x1F3FE, + 3, 0x1F44E, 0x1F3FF, + 3, 0x1F44F, 0x1F3FB, + 3, 0x1F44F, 0x1F3FC, + 3, 0x1F44F, 0x1F3FD, + 3, 0x1F44F, 0x1F3FE, + 3, 0x1F44F, 0x1F3FF, + 3, 0x1F450, 0x1F3FB, + 3, 0x1F450, 0x1F3FC, + 3, 0x1F450, 0x1F3FD, + 3, 0x1F450, 0x1F3FE, + 3, 0x1F450, 0x1F3FF, + 3, 0x1F466, 0x1F3FB, + 3, 0x1F466, 0x1F3FC, + 3, 0x1F466, 0x1F3FD, + 3, 0x1F466, 0x1F3FE, + 3, 0x1F466, 0x1F3FF, + 3, 0x1F467, 0x1F3FB, + 3, 0x1F467, 0x1F3FC, + 3, 0x1F467, 0x1F3FD, + 3, 0x1F467, 0x1F3FE, + 3, 0x1F467, 0x1F3FF, + 3, 0x1F468, 0x1F3FB, + 3, 0x1F468, 0x1F3FC, + 3, 0x1F468, 0x1F3FD, + 3, 0x1F468, 0x1F3FE, + 3, 0x1F468, 0x1F3FF, + 3, 0x1F469, 0x1F3FB, + 3, 0x1F469, 0x1F3FC, + 3, 0x1F469, 0x1F3FD, + 3, 0x1F469, 0x1F3FE, + 3, 0x1F469, 0x1F3FF, + 3, 0x1F46B, 0x1F3FB, + 3, 0x1F46B, 0x1F3FC, + 3, 0x1F46B, 0x1F3FD, + 3, 0x1F46B, 0x1F3FE, + 3, 0x1F46B, 0x1F3FF, + 3, 0x1F46C, 0x1F3FB, + 3, 0x1F46C, 0x1F3FC, + 3, 0x1F46C, 0x1F3FD, + 3, 0x1F46C, 0x1F3FE, + 3, 0x1F46C, 0x1F3FF, + 3, 0x1F46D, 0x1F3FB, + 3, 0x1F46D, 0x1F3FC, + 3, 0x1F46D, 0x1F3FD, + 3, 0x1F46D, 0x1F3FE, + 3, 0x1F46D, 0x1F3FF, + 3, 0x1F46E, 0x1F3FB, + 3, 0x1F46E, 0x1F3FC, + 3, 0x1F46E, 0x1F3FD, + 3, 0x1F46E, 0x1F3FE, + 3, 0x1F46E, 0x1F3FF, + 3, 0x1F46F, 0x1F3FB, + 3, 0x1F46F, 0x1F3FC, + 3, 0x1F46F, 0x1F3FD, + 3, 0x1F46F, 0x1F3FE, + 3, 0x1F46F, 0x1F3FF, + 3, 0x1F470, 0x1F3FB, + 3, 0x1F470, 0x1F3FC, + 3, 0x1F470, 0x1F3FD, + 3, 0x1F470, 0x1F3FE, + 3, 0x1F470, 0x1F3FF, + 3, 0x1F471, 0x1F3FB, + 3, 0x1F471, 0x1F3FC, + 3, 0x1F471, 0x1F3FD, + 3, 0x1F471, 0x1F3FE, + 3, 0x1F471, 0x1F3FF, + 3, 0x1F472, 0x1F3FB, + 3, 0x1F472, 0x1F3FC, + 3, 0x1F472, 0x1F3FD, + 3, 0x1F472, 0x1F3FE, + 3, 0x1F472, 0x1F3FF, + 3, 0x1F473, 0x1F3FB, + 3, 0x1F473, 0x1F3FC, + 3, 0x1F473, 0x1F3FD, + 3, 0x1F473, 0x1F3FE, + 3, 0x1F473, 0x1F3FF, + 3, 0x1F474, 0x1F3FB, + 3, 0x1F474, 0x1F3FC, + 3, 0x1F474, 0x1F3FD, + 3, 0x1F474, 0x1F3FE, + 3, 0x1F474, 0x1F3FF, + 3, 0x1F475, 0x1F3FB, + 3, 0x1F475, 0x1F3FC, + 3, 0x1F475, 0x1F3FD, + 3, 0x1F475, 0x1F3FE, + 3, 0x1F475, 0x1F3FF, + 3, 0x1F476, 0x1F3FB, + 3, 0x1F476, 0x1F3FC, + 3, 0x1F476, 0x1F3FD, + 3, 0x1F476, 0x1F3FE, + 3, 0x1F476, 0x1F3FF, + 3, 0x1F477, 0x1F3FB, + 3, 0x1F477, 0x1F3FC, + 3, 0x1F477, 0x1F3FD, + 3, 0x1F477, 0x1F3FE, + 3, 0x1F477, 0x1F3FF, + 3, 0x1F478, 0x1F3FB, + 3, 0x1F478, 0x1F3FC, + 3, 0x1F478, 0x1F3FD, + 3, 0x1F478, 0x1F3FE, + 3, 0x1F478, 0x1F3FF, + 3, 0x1F47C, 0x1F3FB, + 3, 0x1F47C, 0x1F3FC, + 3, 0x1F47C, 0x1F3FD, + 3, 0x1F47C, 0x1F3FE, + 3, 0x1F47C, 0x1F3FF, + 3, 0x1F481, 0x1F3FB, + 3, 0x1F481, 0x1F3FC, + 3, 0x1F481, 0x1F3FD, + 3, 0x1F481, 0x1F3FE, + 3, 0x1F481, 0x1F3FF, + 3, 0x1F482, 0x1F3FB, + 3, 0x1F482, 0x1F3FC, + 3, 0x1F482, 0x1F3FD, + 3, 0x1F482, 0x1F3FE, + 3, 0x1F482, 0x1F3FF, + 3, 0x1F483, 0x1F3FB, + 3, 0x1F483, 0x1F3FC, + 3, 0x1F483, 0x1F3FD, + 3, 0x1F483, 0x1F3FE, + 3, 0x1F483, 0x1F3FF, + 3, 0x1F485, 0x1F3FB, + 3, 0x1F485, 0x1F3FC, + 3, 0x1F485, 0x1F3FD, + 3, 0x1F485, 0x1F3FE, + 3, 0x1F485, 0x1F3FF, + 3, 0x1F486, 0x1F3FB, + 3, 0x1F486, 0x1F3FC, + 3, 0x1F486, 0x1F3FD, + 3, 0x1F486, 0x1F3FE, + 3, 0x1F486, 0x1F3FF, + 3, 0x1F487, 0x1F3FB, + 3, 0x1F487, 0x1F3FC, + 3, 0x1F487, 0x1F3FD, + 3, 0x1F487, 0x1F3FE, + 3, 0x1F487, 0x1F3FF, + 3, 0x1F48F, 0x1F3FB, + 3, 0x1F48F, 0x1F3FC, + 3, 0x1F48F, 0x1F3FD, + 3, 0x1F48F, 0x1F3FE, + 3, 0x1F48F, 0x1F3FF, + 3, 0x1F491, 0x1F3FB, + 3, 0x1F491, 0x1F3FC, + 3, 0x1F491, 0x1F3FD, + 3, 0x1F491, 0x1F3FE, + 3, 0x1F491, 0x1F3FF, + 3, 0x1F4AA, 0x1F3FB, + 3, 0x1F4AA, 0x1F3FC, + 3, 0x1F4AA, 0x1F3FD, + 3, 0x1F4AA, 0x1F3FE, + 3, 0x1F4AA, 0x1F3FF, + 3, 0x1F574, 0x1F3FB, + 3, 0x1F574, 0x1F3FC, + 3, 0x1F574, 0x1F3FD, + 3, 0x1F574, 0x1F3FE, + 3, 0x1F574, 0x1F3FF, + 3, 0x1F575, 0x1F3FB, + 3, 0x1F575, 0x1F3FC, + 3, 0x1F575, 0x1F3FD, + 3, 0x1F575, 0x1F3FE, + 3, 0x1F575, 0x1F3FF, + 3, 0x1F57A, 0x1F3FB, + 3, 0x1F57A, 0x1F3FC, + 3, 0x1F57A, 0x1F3FD, + 3, 0x1F57A, 0x1F3FE, + 3, 0x1F57A, 0x1F3FF, + 3, 0x1F590, 0x1F3FB, + 3, 0x1F590, 0x1F3FC, + 3, 0x1F590, 0x1F3FD, + 3, 0x1F590, 0x1F3FE, + 3, 0x1F590, 0x1F3FF, + 3, 0x1F595, 0x1F3FB, + 3, 0x1F595, 0x1F3FC, + 3, 0x1F595, 0x1F3FD, + 3, 0x1F595, 0x1F3FE, + 3, 0x1F595, 0x1F3FF, + 3, 0x1F596, 0x1F3FB, + 3, 0x1F596, 0x1F3FC, + 3, 0x1F596, 0x1F3FD, + 3, 0x1F596, 0x1F3FE, + 3, 0x1F596, 0x1F3FF, + 3, 0x1F645, 0x1F3FB, + 3, 0x1F645, 0x1F3FC, + 3, 0x1F645, 0x1F3FD, + 3, 0x1F645, 0x1F3FE, + 3, 0x1F645, 0x1F3FF, + 3, 0x1F646, 0x1F3FB, + 3, 0x1F646, 0x1F3FC, + 3, 0x1F646, 0x1F3FD, + 3, 0x1F646, 0x1F3FE, + 3, 0x1F646, 0x1F3FF, + 3, 0x1F647, 0x1F3FB, + 3, 0x1F647, 0x1F3FC, + 3, 0x1F647, 0x1F3FD, + 3, 0x1F647, 0x1F3FE, + 3, 0x1F647, 0x1F3FF, + 3, 0x1F64B, 0x1F3FB, + 3, 0x1F64B, 0x1F3FC, + 3, 0x1F64B, 0x1F3FD, + 3, 0x1F64B, 0x1F3FE, + 3, 0x1F64B, 0x1F3FF, + 3, 0x1F64C, 0x1F3FB, + 3, 0x1F64C, 0x1F3FC, + 3, 0x1F64C, 0x1F3FD, + 3, 0x1F64C, 0x1F3FE, + 3, 0x1F64C, 0x1F3FF, + 3, 0x1F64D, 0x1F3FB, + 3, 0x1F64D, 0x1F3FC, + 3, 0x1F64D, 0x1F3FD, + 3, 0x1F64D, 0x1F3FE, + 3, 0x1F64D, 0x1F3FF, + 3, 0x1F64E, 0x1F3FB, + 3, 0x1F64E, 0x1F3FC, + 3, 0x1F64E, 0x1F3FD, + 3, 0x1F64E, 0x1F3FE, + 3, 0x1F64E, 0x1F3FF, + 3, 0x1F64F, 0x1F3FB, + 3, 0x1F64F, 0x1F3FC, + 3, 0x1F64F, 0x1F3FD, + 3, 0x1F64F, 0x1F3FE, + 3, 0x1F64F, 0x1F3FF, + 3, 0x1F6A3, 0x1F3FB, + 3, 0x1F6A3, 0x1F3FC, + 3, 0x1F6A3, 0x1F3FD, + 3, 0x1F6A3, 0x1F3FE, + 3, 0x1F6A3, 0x1F3FF, + 3, 0x1F6B4, 0x1F3FB, + 3, 0x1F6B4, 0x1F3FC, + 3, 0x1F6B4, 0x1F3FD, + 3, 0x1F6B4, 0x1F3FE, + 3, 0x1F6B4, 0x1F3FF, + 3, 0x1F6B5, 0x1F3FB, + 3, 0x1F6B5, 0x1F3FC, + 3, 0x1F6B5, 0x1F3FD, + 3, 0x1F6B5, 0x1F3FE, + 3, 0x1F6B5, 0x1F3FF, + 3, 0x1F6B6, 0x1F3FB, + 3, 0x1F6B6, 0x1F3FC, + 3, 0x1F6B6, 0x1F3FD, + 3, 0x1F6B6, 0x1F3FE, + 3, 0x1F6B6, 0x1F3FF, + 3, 0x1F6C0, 0x1F3FB, + 3, 0x1F6C0, 0x1F3FC, + 3, 0x1F6C0, 0x1F3FD, + 3, 0x1F6C0, 0x1F3FE, + 3, 0x1F6C0, 0x1F3FF, + 3, 0x1F6CC, 0x1F3FB, + 3, 0x1F6CC, 0x1F3FC, + 3, 0x1F6CC, 0x1F3FD, + 3, 0x1F6CC, 0x1F3FE, + 3, 0x1F6CC, 0x1F3FF, + 3, 0x1F90C, 0x1F3FB, + 3, 0x1F90C, 0x1F3FC, + 3, 0x1F90C, 0x1F3FD, + 3, 0x1F90C, 0x1F3FE, + 3, 0x1F90C, 0x1F3FF, + 3, 0x1F90F, 0x1F3FB, + 3, 0x1F90F, 0x1F3FC, + 3, 0x1F90F, 0x1F3FD, + 3, 0x1F90F, 0x1F3FE, + 3, 0x1F90F, 0x1F3FF, + 3, 0x1F918, 0x1F3FB, + 3, 0x1F918, 0x1F3FC, + 3, 0x1F918, 0x1F3FD, + 3, 0x1F918, 0x1F3FE, + 3, 0x1F918, 0x1F3FF, + 3, 0x1F919, 0x1F3FB, + 3, 0x1F919, 0x1F3FC, + 3, 0x1F919, 0x1F3FD, + 3, 0x1F919, 0x1F3FE, + 3, 0x1F919, 0x1F3FF, + 3, 0x1F91A, 0x1F3FB, + 3, 0x1F91A, 0x1F3FC, + 3, 0x1F91A, 0x1F3FD, + 3, 0x1F91A, 0x1F3FE, + 3, 0x1F91A, 0x1F3FF, + 3, 0x1F91B, 0x1F3FB, + 3, 0x1F91B, 0x1F3FC, + 3, 0x1F91B, 0x1F3FD, + 3, 0x1F91B, 0x1F3FE, + 3, 0x1F91B, 0x1F3FF, + 3, 0x1F91C, 0x1F3FB, + 3, 0x1F91C, 0x1F3FC, + 3, 0x1F91C, 0x1F3FD, + 3, 0x1F91C, 0x1F3FE, + 3, 0x1F91C, 0x1F3FF, + 3, 0x1F91D, 0x1F3FB, + 3, 0x1F91D, 0x1F3FC, + 3, 0x1F91D, 0x1F3FD, + 3, 0x1F91D, 0x1F3FE, + 3, 0x1F91D, 0x1F3FF, + 3, 0x1F91E, 0x1F3FB, + 3, 0x1F91E, 0x1F3FC, + 3, 0x1F91E, 0x1F3FD, + 3, 0x1F91E, 0x1F3FE, + 3, 0x1F91E, 0x1F3FF, + 3, 0x1F91F, 0x1F3FB, + 3, 0x1F91F, 0x1F3FC, + 3, 0x1F91F, 0x1F3FD, + 3, 0x1F91F, 0x1F3FE, + 3, 0x1F91F, 0x1F3FF, + 3, 0x1F926, 0x1F3FB, + 3, 0x1F926, 0x1F3FC, + 3, 0x1F926, 0x1F3FD, + 3, 0x1F926, 0x1F3FE, + 3, 0x1F926, 0x1F3FF, + 3, 0x1F930, 0x1F3FB, + 3, 0x1F930, 0x1F3FC, + 3, 0x1F930, 0x1F3FD, + 3, 0x1F930, 0x1F3FE, + 3, 0x1F930, 0x1F3FF, + 3, 0x1F931, 0x1F3FB, + 3, 0x1F931, 0x1F3FC, + 3, 0x1F931, 0x1F3FD, + 3, 0x1F931, 0x1F3FE, + 3, 0x1F931, 0x1F3FF, + 3, 0x1F932, 0x1F3FB, + 3, 0x1F932, 0x1F3FC, + 3, 0x1F932, 0x1F3FD, + 3, 0x1F932, 0x1F3FE, + 3, 0x1F932, 0x1F3FF, + 3, 0x1F933, 0x1F3FB, + 3, 0x1F933, 0x1F3FC, + 3, 0x1F933, 0x1F3FD, + 3, 0x1F933, 0x1F3FE, + 3, 0x1F933, 0x1F3FF, + 3, 0x1F934, 0x1F3FB, + 3, 0x1F934, 0x1F3FC, + 3, 0x1F934, 0x1F3FD, + 3, 0x1F934, 0x1F3FE, + 3, 0x1F934, 0x1F3FF, + 3, 0x1F935, 0x1F3FB, + 3, 0x1F935, 0x1F3FC, + 3, 0x1F935, 0x1F3FD, + 3, 0x1F935, 0x1F3FE, + 3, 0x1F935, 0x1F3FF, + 3, 0x1F936, 0x1F3FB, + 3, 0x1F936, 0x1F3FC, + 3, 0x1F936, 0x1F3FD, + 3, 0x1F936, 0x1F3FE, + 3, 0x1F936, 0x1F3FF, + 3, 0x1F937, 0x1F3FB, + 3, 0x1F937, 0x1F3FC, + 3, 0x1F937, 0x1F3FD, + 3, 0x1F937, 0x1F3FE, + 3, 0x1F937, 0x1F3FF, + 3, 0x1F938, 0x1F3FB, + 3, 0x1F938, 0x1F3FC, + 3, 0x1F938, 0x1F3FD, + 3, 0x1F938, 0x1F3FE, + 3, 0x1F938, 0x1F3FF, + 3, 0x1F939, 0x1F3FB, + 3, 0x1F939, 0x1F3FC, + 3, 0x1F939, 0x1F3FD, + 3, 0x1F939, 0x1F3FE, + 3, 0x1F939, 0x1F3FF, + 3, 0x1F93C, 0x1F3FB, + 3, 0x1F93C, 0x1F3FC, + 3, 0x1F93C, 0x1F3FD, + 3, 0x1F93C, 0x1F3FE, + 3, 0x1F93C, 0x1F3FF, + 3, 0x1F93D, 0x1F3FB, + 3, 0x1F93D, 0x1F3FC, + 3, 0x1F93D, 0x1F3FD, + 3, 0x1F93D, 0x1F3FE, + 3, 0x1F93D, 0x1F3FF, + 3, 0x1F93E, 0x1F3FB, + 3, 0x1F93E, 0x1F3FC, + 3, 0x1F93E, 0x1F3FD, + 3, 0x1F93E, 0x1F3FE, + 3, 0x1F93E, 0x1F3FF, + 3, 0x1F977, 0x1F3FB, + 3, 0x1F977, 0x1F3FC, + 3, 0x1F977, 0x1F3FD, + 3, 0x1F977, 0x1F3FE, + 3, 0x1F977, 0x1F3FF, + 3, 0x1F9B5, 0x1F3FB, + 3, 0x1F9B5, 0x1F3FC, + 3, 0x1F9B5, 0x1F3FD, + 3, 0x1F9B5, 0x1F3FE, + 3, 0x1F9B5, 0x1F3FF, + 3, 0x1F9B6, 0x1F3FB, + 3, 0x1F9B6, 0x1F3FC, + 3, 0x1F9B6, 0x1F3FD, + 3, 0x1F9B6, 0x1F3FE, + 3, 0x1F9B6, 0x1F3FF, + 3, 0x1F9B8, 0x1F3FB, + 3, 0x1F9B8, 0x1F3FC, + 3, 0x1F9B8, 0x1F3FD, + 3, 0x1F9B8, 0x1F3FE, + 3, 0x1F9B8, 0x1F3FF, + 3, 0x1F9B9, 0x1F3FB, + 3, 0x1F9B9, 0x1F3FC, + 3, 0x1F9B9, 0x1F3FD, + 3, 0x1F9B9, 0x1F3FE, + 3, 0x1F9B9, 0x1F3FF, + 3, 0x1F9BB, 0x1F3FB, + 3, 0x1F9BB, 0x1F3FC, + 3, 0x1F9BB, 0x1F3FD, + 3, 0x1F9BB, 0x1F3FE, + 3, 0x1F9BB, 0x1F3FF, + 3, 0x1F9CD, 0x1F3FB, + 3, 0x1F9CD, 0x1F3FC, + 3, 0x1F9CD, 0x1F3FD, + 3, 0x1F9CD, 0x1F3FE, + 3, 0x1F9CD, 0x1F3FF, + 3, 0x1F9CE, 0x1F3FB, + 3, 0x1F9CE, 0x1F3FC, + 3, 0x1F9CE, 0x1F3FD, + 3, 0x1F9CE, 0x1F3FE, + 3, 0x1F9CE, 0x1F3FF, + 3, 0x1F9CF, 0x1F3FB, + 3, 0x1F9CF, 0x1F3FC, + 3, 0x1F9CF, 0x1F3FD, + 3, 0x1F9CF, 0x1F3FE, + 3, 0x1F9CF, 0x1F3FF, + 3, 0x1F9D1, 0x1F3FB, + 3, 0x1F9D1, 0x1F3FC, + 3, 0x1F9D1, 0x1F3FD, + 3, 0x1F9D1, 0x1F3FE, + 3, 0x1F9D1, 0x1F3FF, + 3, 0x1F9D2, 0x1F3FB, + 3, 0x1F9D2, 0x1F3FC, + 3, 0x1F9D2, 0x1F3FD, + 3, 0x1F9D2, 0x1F3FE, + 3, 0x1F9D2, 0x1F3FF, + 3, 0x1F9D3, 0x1F3FB, + 3, 0x1F9D3, 0x1F3FC, + 3, 0x1F9D3, 0x1F3FD, + 3, 0x1F9D3, 0x1F3FE, + 3, 0x1F9D3, 0x1F3FF, + 3, 0x1F9D4, 0x1F3FB, + 3, 0x1F9D4, 0x1F3FC, + 3, 0x1F9D4, 0x1F3FD, + 3, 0x1F9D4, 0x1F3FE, + 3, 0x1F9D4, 0x1F3FF, + 3, 0x1F9D5, 0x1F3FB, + 3, 0x1F9D5, 0x1F3FC, + 3, 0x1F9D5, 0x1F3FD, + 3, 0x1F9D5, 0x1F3FE, + 3, 0x1F9D5, 0x1F3FF, + 3, 0x1F9D6, 0x1F3FB, + 3, 0x1F9D6, 0x1F3FC, + 3, 0x1F9D6, 0x1F3FD, + 3, 0x1F9D6, 0x1F3FE, + 3, 0x1F9D6, 0x1F3FF, + 3, 0x1F9D7, 0x1F3FB, + 3, 0x1F9D7, 0x1F3FC, + 3, 0x1F9D7, 0x1F3FD, + 3, 0x1F9D7, 0x1F3FE, + 3, 0x1F9D7, 0x1F3FF, + 3, 0x1F9D8, 0x1F3FB, + 3, 0x1F9D8, 0x1F3FC, + 3, 0x1F9D8, 0x1F3FD, + 3, 0x1F9D8, 0x1F3FE, + 3, 0x1F9D8, 0x1F3FF, + 3, 0x1F9D9, 0x1F3FB, + 3, 0x1F9D9, 0x1F3FC, + 3, 0x1F9D9, 0x1F3FD, + 3, 0x1F9D9, 0x1F3FE, + 3, 0x1F9D9, 0x1F3FF, + 3, 0x1F9DA, 0x1F3FB, + 3, 0x1F9DA, 0x1F3FC, + 3, 0x1F9DA, 0x1F3FD, + 3, 0x1F9DA, 0x1F3FE, + 3, 0x1F9DA, 0x1F3FF, + 3, 0x1F9DB, 0x1F3FB, + 3, 0x1F9DB, 0x1F3FC, + 3, 0x1F9DB, 0x1F3FD, + 3, 0x1F9DB, 0x1F3FE, + 3, 0x1F9DB, 0x1F3FF, + 3, 0x1F9DC, 0x1F3FB, + 3, 0x1F9DC, 0x1F3FC, + 3, 0x1F9DC, 0x1F3FD, + 3, 0x1F9DC, 0x1F3FE, + 3, 0x1F9DC, 0x1F3FF, + 3, 0x1F9DD, 0x1F3FB, + 3, 0x1F9DD, 0x1F3FC, + 3, 0x1F9DD, 0x1F3FD, + 3, 0x1F9DD, 0x1F3FE, + 3, 0x1F9DD, 0x1F3FF, + 3, 0x1FAC3, 0x1F3FB, + 3, 0x1FAC3, 0x1F3FC, + 3, 0x1FAC3, 0x1F3FD, + 3, 0x1FAC3, 0x1F3FE, + 3, 0x1FAC3, 0x1F3FF, + 3, 0x1FAC4, 0x1F3FB, + 3, 0x1FAC4, 0x1F3FC, + 3, 0x1FAC4, 0x1F3FD, + 3, 0x1FAC4, 0x1F3FE, + 3, 0x1FAC4, 0x1F3FF, + 3, 0x1FAC5, 0x1F3FB, + 3, 0x1FAC5, 0x1F3FC, + 3, 0x1FAC5, 0x1F3FD, + 3, 0x1FAC5, 0x1F3FE, + 3, 0x1FAC5, 0x1F3FF, + 3, 0x1FAF0, 0x1F3FB, + 3, 0x1FAF0, 0x1F3FC, + 3, 0x1FAF0, 0x1F3FD, + 3, 0x1FAF0, 0x1F3FE, + 3, 0x1FAF0, 0x1F3FF, + 3, 0x1FAF1, 0x1F3FB, + 3, 0x1FAF1, 0x1F3FC, + 3, 0x1FAF1, 0x1F3FD, + 3, 0x1FAF1, 0x1F3FE, + 3, 0x1FAF1, 0x1F3FF, + 3, 0x1FAF2, 0x1F3FB, + 3, 0x1FAF2, 0x1F3FC, + 3, 0x1FAF2, 0x1F3FD, + 3, 0x1FAF2, 0x1F3FE, + 3, 0x1FAF2, 0x1F3FF, + 3, 0x1FAF3, 0x1F3FB, + 3, 0x1FAF3, 0x1F3FC, + 3, 0x1FAF3, 0x1F3FD, + 3, 0x1FAF3, 0x1F3FE, + 3, 0x1FAF3, 0x1F3FF, + 3, 0x1FAF4, 0x1F3FB, + 3, 0x1FAF4, 0x1F3FC, + 3, 0x1FAF4, 0x1F3FD, + 3, 0x1FAF4, 0x1F3FE, + 3, 0x1FAF4, 0x1F3FF, + 3, 0x1FAF5, 0x1F3FB, + 3, 0x1FAF5, 0x1F3FC, + 3, 0x1FAF5, 0x1F3FD, + 3, 0x1FAF5, 0x1F3FE, + 3, 0x1FAF5, 0x1F3FF, + 3, 0x1FAF6, 0x1F3FB, + 3, 0x1FAF6, 0x1F3FC, + 3, 0x1FAF6, 0x1F3FD, + 3, 0x1FAF6, 0x1F3FE, + 3, 0x1FAF6, 0x1F3FF, + 3, 0x1FAF7, 0x1F3FB, + 3, 0x1FAF7, 0x1F3FC, + 3, 0x1FAF7, 0x1F3FD, + 3, 0x1FAF7, 0x1F3FE, + 3, 0x1FAF7, 0x1F3FF, + 3, 0x1FAF8, 0x1F3FB, + 3, 0x1FAF8, 0x1F3FC, + 3, 0x1FAF8, 0x1F3FD, + 3, 0x1FAF8, 0x1F3FE, + 3, 0x1FAF8, 0x1F3FF, + 0, // Padding. + // #376 (20606+778/2): bp=RGI_Emoji_Flag_Sequence + 3, 0x1F1E6, 0x1F1E8, + 3, 0x1F1E6, 0x1F1E9, + 3, 0x1F1E6, 0x1F1EA, + 3, 0x1F1E6, 0x1F1EB, + 3, 0x1F1E6, 0x1F1EC, + 3, 0x1F1E6, 0x1F1EE, + 3, 0x1F1E6, 0x1F1F1, + 3, 0x1F1E6, 0x1F1F2, + 3, 0x1F1E6, 0x1F1F4, + 3, 0x1F1E6, 0x1F1F6, + 3, 0x1F1E6, 0x1F1F7, + 3, 0x1F1E6, 0x1F1F8, + 3, 0x1F1E6, 0x1F1F9, + 3, 0x1F1E6, 0x1F1FA, + 3, 0x1F1E6, 0x1F1FC, + 3, 0x1F1E6, 0x1F1FD, + 3, 0x1F1E6, 0x1F1FF, + 3, 0x1F1E7, 0x1F1E6, + 3, 0x1F1E7, 0x1F1E7, + 3, 0x1F1E7, 0x1F1E9, + 3, 0x1F1E7, 0x1F1EA, + 3, 0x1F1E7, 0x1F1EB, + 3, 0x1F1E7, 0x1F1EC, + 3, 0x1F1E7, 0x1F1ED, + 3, 0x1F1E7, 0x1F1EE, + 3, 0x1F1E7, 0x1F1EF, + 3, 0x1F1E7, 0x1F1F1, + 3, 0x1F1E7, 0x1F1F2, + 3, 0x1F1E7, 0x1F1F3, + 3, 0x1F1E7, 0x1F1F4, + 3, 0x1F1E7, 0x1F1F6, + 3, 0x1F1E7, 0x1F1F7, + 3, 0x1F1E7, 0x1F1F8, + 3, 0x1F1E7, 0x1F1F9, + 3, 0x1F1E7, 0x1F1FB, + 3, 0x1F1E7, 0x1F1FC, + 3, 0x1F1E7, 0x1F1FE, + 3, 0x1F1E7, 0x1F1FF, + 3, 0x1F1E8, 0x1F1E6, + 3, 0x1F1E8, 0x1F1E8, + 3, 0x1F1E8, 0x1F1E9, + 3, 0x1F1E8, 0x1F1EB, + 3, 0x1F1E8, 0x1F1EC, + 3, 0x1F1E8, 0x1F1ED, + 3, 0x1F1E8, 0x1F1EE, + 3, 0x1F1E8, 0x1F1F0, + 3, 0x1F1E8, 0x1F1F1, + 3, 0x1F1E8, 0x1F1F2, + 3, 0x1F1E8, 0x1F1F3, + 3, 0x1F1E8, 0x1F1F4, + 3, 0x1F1E8, 0x1F1F5, + 3, 0x1F1E8, 0x1F1F6, + 3, 0x1F1E8, 0x1F1F7, + 3, 0x1F1E8, 0x1F1FA, + 3, 0x1F1E8, 0x1F1FB, + 3, 0x1F1E8, 0x1F1FC, + 3, 0x1F1E8, 0x1F1FD, + 3, 0x1F1E8, 0x1F1FE, + 3, 0x1F1E8, 0x1F1FF, + 3, 0x1F1E9, 0x1F1EA, + 3, 0x1F1E9, 0x1F1EC, + 3, 0x1F1E9, 0x1F1EF, + 3, 0x1F1E9, 0x1F1F0, + 3, 0x1F1E9, 0x1F1F2, + 3, 0x1F1E9, 0x1F1F4, + 3, 0x1F1E9, 0x1F1FF, + 3, 0x1F1EA, 0x1F1E6, + 3, 0x1F1EA, 0x1F1E8, + 3, 0x1F1EA, 0x1F1EA, + 3, 0x1F1EA, 0x1F1EC, + 3, 0x1F1EA, 0x1F1ED, + 3, 0x1F1EA, 0x1F1F7, + 3, 0x1F1EA, 0x1F1F8, + 3, 0x1F1EA, 0x1F1F9, + 3, 0x1F1EA, 0x1F1FA, + 3, 0x1F1EB, 0x1F1EE, + 3, 0x1F1EB, 0x1F1EF, + 3, 0x1F1EB, 0x1F1F0, + 3, 0x1F1EB, 0x1F1F2, + 3, 0x1F1EB, 0x1F1F4, + 3, 0x1F1EB, 0x1F1F7, + 3, 0x1F1EC, 0x1F1E6, + 3, 0x1F1EC, 0x1F1E7, + 3, 0x1F1EC, 0x1F1E9, + 3, 0x1F1EC, 0x1F1EA, + 3, 0x1F1EC, 0x1F1EB, + 3, 0x1F1EC, 0x1F1EC, + 3, 0x1F1EC, 0x1F1ED, + 3, 0x1F1EC, 0x1F1EE, + 3, 0x1F1EC, 0x1F1F1, + 3, 0x1F1EC, 0x1F1F2, + 3, 0x1F1EC, 0x1F1F3, + 3, 0x1F1EC, 0x1F1F5, + 3, 0x1F1EC, 0x1F1F6, + 3, 0x1F1EC, 0x1F1F7, + 3, 0x1F1EC, 0x1F1F8, + 3, 0x1F1EC, 0x1F1F9, + 3, 0x1F1EC, 0x1F1FA, + 3, 0x1F1EC, 0x1F1FC, + 3, 0x1F1EC, 0x1F1FE, + 3, 0x1F1ED, 0x1F1F0, + 3, 0x1F1ED, 0x1F1F2, + 3, 0x1F1ED, 0x1F1F3, + 3, 0x1F1ED, 0x1F1F7, + 3, 0x1F1ED, 0x1F1F9, + 3, 0x1F1ED, 0x1F1FA, + 3, 0x1F1EE, 0x1F1E8, + 3, 0x1F1EE, 0x1F1E9, + 3, 0x1F1EE, 0x1F1EA, + 3, 0x1F1EE, 0x1F1F1, + 3, 0x1F1EE, 0x1F1F2, + 3, 0x1F1EE, 0x1F1F3, + 3, 0x1F1EE, 0x1F1F4, + 3, 0x1F1EE, 0x1F1F6, + 3, 0x1F1EE, 0x1F1F7, + 3, 0x1F1EE, 0x1F1F8, + 3, 0x1F1EE, 0x1F1F9, + 3, 0x1F1EF, 0x1F1EA, + 3, 0x1F1EF, 0x1F1F2, + 3, 0x1F1EF, 0x1F1F4, + 3, 0x1F1EF, 0x1F1F5, + 3, 0x1F1F0, 0x1F1EA, + 3, 0x1F1F0, 0x1F1EC, + 3, 0x1F1F0, 0x1F1ED, + 3, 0x1F1F0, 0x1F1EE, + 3, 0x1F1F0, 0x1F1F2, + 3, 0x1F1F0, 0x1F1F3, + 3, 0x1F1F0, 0x1F1F5, + 3, 0x1F1F0, 0x1F1F7, + 3, 0x1F1F0, 0x1F1FC, + 3, 0x1F1F0, 0x1F1FE, + 3, 0x1F1F0, 0x1F1FF, + 3, 0x1F1F1, 0x1F1E6, + 3, 0x1F1F1, 0x1F1E7, + 3, 0x1F1F1, 0x1F1E8, + 3, 0x1F1F1, 0x1F1EE, + 3, 0x1F1F1, 0x1F1F0, + 3, 0x1F1F1, 0x1F1F7, + 3, 0x1F1F1, 0x1F1F8, + 3, 0x1F1F1, 0x1F1F9, + 3, 0x1F1F1, 0x1F1FA, + 3, 0x1F1F1, 0x1F1FB, + 3, 0x1F1F1, 0x1F1FE, + 3, 0x1F1F2, 0x1F1E6, + 3, 0x1F1F2, 0x1F1E8, + 3, 0x1F1F2, 0x1F1E9, + 3, 0x1F1F2, 0x1F1EA, + 3, 0x1F1F2, 0x1F1EB, + 3, 0x1F1F2, 0x1F1EC, + 3, 0x1F1F2, 0x1F1ED, + 3, 0x1F1F2, 0x1F1F0, + 3, 0x1F1F2, 0x1F1F1, + 3, 0x1F1F2, 0x1F1F2, + 3, 0x1F1F2, 0x1F1F3, + 3, 0x1F1F2, 0x1F1F4, + 3, 0x1F1F2, 0x1F1F5, + 3, 0x1F1F2, 0x1F1F6, + 3, 0x1F1F2, 0x1F1F7, + 3, 0x1F1F2, 0x1F1F8, + 3, 0x1F1F2, 0x1F1F9, + 3, 0x1F1F2, 0x1F1FA, + 3, 0x1F1F2, 0x1F1FB, + 3, 0x1F1F2, 0x1F1FC, + 3, 0x1F1F2, 0x1F1FD, + 3, 0x1F1F2, 0x1F1FE, + 3, 0x1F1F2, 0x1F1FF, + 3, 0x1F1F3, 0x1F1E6, + 3, 0x1F1F3, 0x1F1E8, + 3, 0x1F1F3, 0x1F1EA, + 3, 0x1F1F3, 0x1F1EB, + 3, 0x1F1F3, 0x1F1EC, + 3, 0x1F1F3, 0x1F1EE, + 3, 0x1F1F3, 0x1F1F1, + 3, 0x1F1F3, 0x1F1F4, + 3, 0x1F1F3, 0x1F1F5, + 3, 0x1F1F3, 0x1F1F7, + 3, 0x1F1F3, 0x1F1FA, + 3, 0x1F1F3, 0x1F1FF, + 3, 0x1F1F4, 0x1F1F2, + 3, 0x1F1F5, 0x1F1E6, + 3, 0x1F1F5, 0x1F1EA, + 3, 0x1F1F5, 0x1F1EB, + 3, 0x1F1F5, 0x1F1EC, + 3, 0x1F1F5, 0x1F1ED, + 3, 0x1F1F5, 0x1F1F0, + 3, 0x1F1F5, 0x1F1F1, + 3, 0x1F1F5, 0x1F1F2, + 3, 0x1F1F5, 0x1F1F3, + 3, 0x1F1F5, 0x1F1F7, + 3, 0x1F1F5, 0x1F1F8, + 3, 0x1F1F5, 0x1F1F9, + 3, 0x1F1F5, 0x1F1FC, + 3, 0x1F1F5, 0x1F1FE, + 3, 0x1F1F6, 0x1F1E6, + 3, 0x1F1F7, 0x1F1EA, + 3, 0x1F1F7, 0x1F1F4, + 3, 0x1F1F7, 0x1F1F8, + 3, 0x1F1F7, 0x1F1FA, + 3, 0x1F1F7, 0x1F1FC, + 3, 0x1F1F8, 0x1F1E6, + 3, 0x1F1F8, 0x1F1E7, + 3, 0x1F1F8, 0x1F1E8, + 3, 0x1F1F8, 0x1F1E9, + 3, 0x1F1F8, 0x1F1EA, + 3, 0x1F1F8, 0x1F1EC, + 3, 0x1F1F8, 0x1F1ED, + 3, 0x1F1F8, 0x1F1EE, + 3, 0x1F1F8, 0x1F1EF, + 3, 0x1F1F8, 0x1F1F0, + 3, 0x1F1F8, 0x1F1F1, + 3, 0x1F1F8, 0x1F1F2, + 3, 0x1F1F8, 0x1F1F3, + 3, 0x1F1F8, 0x1F1F4, + 3, 0x1F1F8, 0x1F1F7, + 3, 0x1F1F8, 0x1F1F8, + 3, 0x1F1F8, 0x1F1F9, + 3, 0x1F1F8, 0x1F1FB, + 3, 0x1F1F8, 0x1F1FD, + 3, 0x1F1F8, 0x1F1FE, + 3, 0x1F1F8, 0x1F1FF, + 3, 0x1F1F9, 0x1F1E6, + 3, 0x1F1F9, 0x1F1E8, + 3, 0x1F1F9, 0x1F1E9, + 3, 0x1F1F9, 0x1F1EB, + 3, 0x1F1F9, 0x1F1EC, + 3, 0x1F1F9, 0x1F1ED, + 3, 0x1F1F9, 0x1F1EF, + 3, 0x1F1F9, 0x1F1F0, + 3, 0x1F1F9, 0x1F1F1, + 3, 0x1F1F9, 0x1F1F2, + 3, 0x1F1F9, 0x1F1F3, + 3, 0x1F1F9, 0x1F1F4, + 3, 0x1F1F9, 0x1F1F7, + 3, 0x1F1F9, 0x1F1F9, + 3, 0x1F1F9, 0x1F1FB, + 3, 0x1F1F9, 0x1F1FC, + 3, 0x1F1F9, 0x1F1FF, + 3, 0x1F1FA, 0x1F1E6, + 3, 0x1F1FA, 0x1F1EC, + 3, 0x1F1FA, 0x1F1F2, + 3, 0x1F1FA, 0x1F1F3, + 3, 0x1F1FA, 0x1F1F8, + 3, 0x1F1FA, 0x1F1FE, + 3, 0x1F1FA, 0x1F1FF, + 3, 0x1F1FB, 0x1F1E6, + 3, 0x1F1FB, 0x1F1E8, + 3, 0x1F1FB, 0x1F1EA, + 3, 0x1F1FB, 0x1F1EC, + 3, 0x1F1FB, 0x1F1EE, + 3, 0x1F1FB, 0x1F1F3, + 3, 0x1F1FB, 0x1F1FA, + 3, 0x1F1FC, 0x1F1EB, + 3, 0x1F1FC, 0x1F1F8, + 3, 0x1F1FD, 0x1F1F0, + 3, 0x1F1FE, 0x1F1EA, + 3, 0x1F1FE, 0x1F1F9, + 3, 0x1F1FF, 0x1F1E6, + 3, 0x1F1FF, 0x1F1F2, + 3, 0x1F1FF, 0x1F1FC, + 0, // Padding. + // #377 (20995+24/2): bp=RGI_Emoji_Tag_Sequence + 8, 0x1F3F4, 0xE0067, 0xE0062, 0xE0065, 0xE006E, 0xE0067, 0xE007F, + 8, 0x1F3F4, 0xE0067, 0xE0062, 0xE0073, 0xE0063, 0xE0074, 0xE007F, + 8, 0x1F3F4, 0xE0067, 0xE0062, 0xE0077, 0xE006C, 0xE0073, 0xE007F, + // #378 (21007+10492/2): bp=RGI_Emoji_ZWJ_Sequence + 7, 0x1F468, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, + 9, 0x1F468, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, + 4, 0x1F468, 0x200D, 0x1F466, + 6, 0x1F468, 0x200D, 0x1F466, 0x200D, 0x1F466, + 4, 0x1F468, 0x200D, 0x1F467, + 6, 0x1F468, 0x200D, 0x1F467, 0x200D, 0x1F466, + 6, 0x1F468, 0x200D, 0x1F467, 0x200D, 0x1F467, + 6, 0x1F468, 0x200D, 0x1F468, 0x200D, 0x1F466, + 8, 0x1F468, 0x200D, 0x1F468, 0x200D, 0x1F466, 0x200D, 0x1F466, + 6, 0x1F468, 0x200D, 0x1F468, 0x200D, 0x1F467, + 8, 0x1F468, 0x200D, 0x1F468, 0x200D, 0x1F467, 0x200D, 0x1F466, + 8, 0x1F468, 0x200D, 0x1F468, 0x200D, 0x1F467, 0x200D, 0x1F467, + 6, 0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F466, + 8, 0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F466, 0x200D, 0x1F466, + 6, 0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F467, + 8, 0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F466, + 8, 0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F467, + 9, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F468, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FF, + 9, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F468, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FF, + 9, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F468, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FF, + 9, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F468, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FF, + 9, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F468, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F468, 0x1F3FE, + 7, 0x1F469, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, + 7, 0x1F469, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, + 9, 0x1F469, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, + 9, 0x1F469, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, + 4, 0x1F469, 0x200D, 0x1F466, + 6, 0x1F469, 0x200D, 0x1F466, 0x200D, 0x1F466, + 4, 0x1F469, 0x200D, 0x1F467, + 6, 0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F466, + 6, 0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F467, + 6, 0x1F469, 0x200D, 0x1F469, 0x200D, 0x1F466, + 8, 0x1F469, 0x200D, 0x1F469, 0x200D, 0x1F466, 0x200D, 0x1F466, + 6, 0x1F469, 0x200D, 0x1F469, 0x200D, 0x1F467, + 8, 0x1F469, 0x200D, 0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F466, + 8, 0x1F469, 0x200D, 0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F467, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FB, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FC, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FD, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FE, + 9, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FF, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FB, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FC, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FD, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FE, + 11, 0x1F469, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FF, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FB, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FC, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FD, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FE, + 9, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FF, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FB, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FC, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FD, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FE, + 11, 0x1F469, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FF, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FB, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FC, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FD, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FE, + 9, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FF, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FB, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FC, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FD, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FE, + 11, 0x1F469, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FF, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FB, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FC, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FD, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FE, + 9, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FF, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FB, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FC, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FD, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FE, + 11, 0x1F469, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FF, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FF, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FB, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FC, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FD, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FE, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F468, 0x1F3FF, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FB, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FC, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FD, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FE, + 9, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F469, 0x1F3FF, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FB, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FC, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FD, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FE, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F468, 0x1F3FF, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FB, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FC, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FD, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FE, + 11, 0x1F469, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F469, 0x1F3FF, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FB, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FC, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FD, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F468, 0x1F3FE, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F469, 0x1F3FE, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FB, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FC, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FD, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F469, 0x1F3FE, + 6, 0x1F9D1, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, + 6, 0x1F9D1, 0x200D, 0x1F9D1, 0x200D, 0x1F9D2, + 8, 0x1F9D1, 0x200D, 0x1F9D1, 0x200D, 0x1F9D2, 0x200D, 0x1F9D2, + 4, 0x1F9D1, 0x200D, 0x1F9D2, + 6, 0x1F9D1, 0x200D, 0x1F9D2, 0x200D, 0x1F9D2, + 11, 0x1F9D1, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FC, + 11, 0x1F9D1, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FD, + 11, 0x1F9D1, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FE, + 11, 0x1F9D1, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FF, + 9, 0x1F9D1, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FC, + 9, 0x1F9D1, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FD, + 9, 0x1F9D1, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FE, + 9, 0x1F9D1, 0x1F3FB, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FF, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FF, + 11, 0x1F9D1, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FB, + 11, 0x1F9D1, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FD, + 11, 0x1F9D1, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FE, + 11, 0x1F9D1, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FF, + 9, 0x1F9D1, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FB, + 9, 0x1F9D1, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FD, + 9, 0x1F9D1, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FE, + 9, 0x1F9D1, 0x1F3FC, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FF, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FF, + 11, 0x1F9D1, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FB, + 11, 0x1F9D1, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FC, + 11, 0x1F9D1, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FE, + 11, 0x1F9D1, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FF, + 9, 0x1F9D1, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FB, + 9, 0x1F9D1, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FC, + 9, 0x1F9D1, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FE, + 9, 0x1F9D1, 0x1F3FD, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FF, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FF, + 11, 0x1F9D1, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FB, + 11, 0x1F9D1, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FC, + 11, 0x1F9D1, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FD, + 11, 0x1F9D1, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FF, + 9, 0x1F9D1, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FB, + 9, 0x1F9D1, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FC, + 9, 0x1F9D1, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FD, + 9, 0x1F9D1, 0x1F3FE, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FF, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FF, + 11, 0x1F9D1, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FB, + 11, 0x1F9D1, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FC, + 11, 0x1F9D1, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FD, + 11, 0x1F9D1, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F48B, 0x200D, 0x1F9D1, 0x1F3FE, + 9, 0x1F9D1, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FB, + 9, 0x1F9D1, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FC, + 9, 0x1F9D1, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FD, + 9, 0x1F9D1, 0x1F3FF, 0x200D, 0x2764, 0xFE0F, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, 0x1F3FF, + 6, 0x1FAF1, 0x1F3FB, 0x200D, 0x1FAF2, 0x1F3FC, + 6, 0x1FAF1, 0x1F3FB, 0x200D, 0x1FAF2, 0x1F3FD, + 6, 0x1FAF1, 0x1F3FB, 0x200D, 0x1FAF2, 0x1F3FE, + 6, 0x1FAF1, 0x1F3FB, 0x200D, 0x1FAF2, 0x1F3FF, + 6, 0x1FAF1, 0x1F3FC, 0x200D, 0x1FAF2, 0x1F3FB, + 6, 0x1FAF1, 0x1F3FC, 0x200D, 0x1FAF2, 0x1F3FD, + 6, 0x1FAF1, 0x1F3FC, 0x200D, 0x1FAF2, 0x1F3FE, + 6, 0x1FAF1, 0x1F3FC, 0x200D, 0x1FAF2, 0x1F3FF, + 6, 0x1FAF1, 0x1F3FD, 0x200D, 0x1FAF2, 0x1F3FB, + 6, 0x1FAF1, 0x1F3FD, 0x200D, 0x1FAF2, 0x1F3FC, + 6, 0x1FAF1, 0x1F3FD, 0x200D, 0x1FAF2, 0x1F3FE, + 6, 0x1FAF1, 0x1F3FD, 0x200D, 0x1FAF2, 0x1F3FF, + 6, 0x1FAF1, 0x1F3FE, 0x200D, 0x1FAF2, 0x1F3FB, + 6, 0x1FAF1, 0x1F3FE, 0x200D, 0x1FAF2, 0x1F3FC, + 6, 0x1FAF1, 0x1F3FE, 0x200D, 0x1FAF2, 0x1F3FD, + 6, 0x1FAF1, 0x1F3FE, 0x200D, 0x1FAF2, 0x1F3FF, + 6, 0x1FAF1, 0x1F3FF, 0x200D, 0x1FAF2, 0x1F3FB, + 6, 0x1FAF1, 0x1F3FF, 0x200D, 0x1FAF2, 0x1F3FC, + 6, 0x1FAF1, 0x1F3FF, 0x200D, 0x1FAF2, 0x1F3FD, + 6, 0x1FAF1, 0x1F3FF, 0x200D, 0x1FAF2, 0x1F3FE, + 5, 0x1F3C3, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FB, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FC, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FE, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x200D, 0x2695, 0xFE0F, + 5, 0x1F468, 0x200D, 0x2696, 0xFE0F, + 5, 0x1F468, 0x200D, 0x2708, 0xFE0F, + 4, 0x1F468, 0x200D, 0x1F33E, + 4, 0x1F468, 0x200D, 0x1F373, + 4, 0x1F468, 0x200D, 0x1F37C, + 4, 0x1F468, 0x200D, 0x1F393, + 4, 0x1F468, 0x200D, 0x1F3A4, + 4, 0x1F468, 0x200D, 0x1F3A8, + 4, 0x1F468, 0x200D, 0x1F3EB, + 4, 0x1F468, 0x200D, 0x1F3ED, + 4, 0x1F468, 0x200D, 0x1F4BB, + 4, 0x1F468, 0x200D, 0x1F4BC, + 4, 0x1F468, 0x200D, 0x1F527, + 4, 0x1F468, 0x200D, 0x1F52C, + 4, 0x1F468, 0x200D, 0x1F680, + 4, 0x1F468, 0x200D, 0x1F692, + 4, 0x1F468, 0x200D, 0x1F9AF, + 7, 0x1F468, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 4, 0x1F468, 0x200D, 0x1F9BC, + 7, 0x1F468, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 4, 0x1F468, 0x200D, 0x1F9BD, + 7, 0x1F468, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F468, 0x1F3FB, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F468, 0x1F3FB, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F468, 0x1F3FB, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F33E, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F373, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F37C, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F393, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F3A4, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F3A8, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F3EB, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F3ED, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F4BB, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F4BC, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F527, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F52C, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F680, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F692, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F9AF, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F9BC, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F9BD, + 8, 0x1F468, 0x1F3FB, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F468, 0x1F3FC, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F468, 0x1F3FC, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F468, 0x1F3FC, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F33E, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F373, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F37C, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F393, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F3A4, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F3A8, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F3EB, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F3ED, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F4BB, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F4BC, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F527, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F52C, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F680, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F692, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F9AF, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F9BC, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F9BD, + 8, 0x1F468, 0x1F3FC, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F468, 0x1F3FD, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F468, 0x1F3FD, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F468, 0x1F3FD, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F33E, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F373, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F37C, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F393, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F3A4, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F3A8, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F3EB, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F3ED, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F4BB, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F4BC, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F527, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F52C, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F680, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F692, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F9AF, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F9BC, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F9BD, + 8, 0x1F468, 0x1F3FD, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F468, 0x1F3FE, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F468, 0x1F3FE, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F468, 0x1F3FE, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F33E, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F373, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F37C, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F393, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F3A4, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F3A8, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F3EB, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F3ED, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F4BB, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F4BC, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F527, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F52C, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F680, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F692, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F9AF, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F9BC, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F9BD, + 8, 0x1F468, 0x1F3FE, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F468, 0x1F3FF, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F468, 0x1F3FF, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F468, 0x1F3FF, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F33E, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F373, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F37C, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F393, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F3A4, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F3A8, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F3EB, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F3ED, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F4BB, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F4BC, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F527, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F52C, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F680, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F692, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F9AF, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F9BC, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F9BD, + 8, 0x1F468, 0x1F3FF, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x200D, 0x2695, 0xFE0F, + 5, 0x1F469, 0x200D, 0x2696, 0xFE0F, + 5, 0x1F469, 0x200D, 0x2708, 0xFE0F, + 4, 0x1F469, 0x200D, 0x1F33E, + 4, 0x1F469, 0x200D, 0x1F373, + 4, 0x1F469, 0x200D, 0x1F37C, + 4, 0x1F469, 0x200D, 0x1F393, + 4, 0x1F469, 0x200D, 0x1F3A4, + 4, 0x1F469, 0x200D, 0x1F3A8, + 4, 0x1F469, 0x200D, 0x1F3EB, + 4, 0x1F469, 0x200D, 0x1F3ED, + 4, 0x1F469, 0x200D, 0x1F4BB, + 4, 0x1F469, 0x200D, 0x1F4BC, + 4, 0x1F469, 0x200D, 0x1F527, + 4, 0x1F469, 0x200D, 0x1F52C, + 4, 0x1F469, 0x200D, 0x1F680, + 4, 0x1F469, 0x200D, 0x1F692, + 4, 0x1F469, 0x200D, 0x1F9AF, + 7, 0x1F469, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 4, 0x1F469, 0x200D, 0x1F9BC, + 7, 0x1F469, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 4, 0x1F469, 0x200D, 0x1F9BD, + 7, 0x1F469, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F469, 0x1F3FB, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F469, 0x1F3FB, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F469, 0x1F3FB, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F33E, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F373, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F37C, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F393, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F3A4, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F3A8, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F3EB, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F3ED, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F4BB, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F4BC, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F527, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F52C, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F680, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F692, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F9AF, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F9BC, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F9BD, + 8, 0x1F469, 0x1F3FB, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F469, 0x1F3FC, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F469, 0x1F3FC, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F469, 0x1F3FC, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F33E, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F373, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F37C, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F393, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F3A4, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F3A8, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F3EB, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F3ED, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F4BB, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F4BC, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F527, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F52C, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F680, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F692, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F9AF, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F9BC, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F9BD, + 8, 0x1F469, 0x1F3FC, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F469, 0x1F3FD, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F469, 0x1F3FD, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F469, 0x1F3FD, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F33E, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F373, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F37C, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F393, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F3A4, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F3A8, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F3EB, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F3ED, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F4BB, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F4BC, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F527, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F52C, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F680, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F692, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F9AF, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F9BC, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F9BD, + 8, 0x1F469, 0x1F3FD, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F469, 0x1F3FE, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F469, 0x1F3FE, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F469, 0x1F3FE, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F33E, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F373, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F37C, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F393, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F3A4, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F3A8, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F3EB, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F3ED, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F4BB, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F4BC, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F527, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F52C, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F680, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F692, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F9AF, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F9BC, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F9BD, + 8, 0x1F469, 0x1F3FE, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F469, 0x1F3FF, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F469, 0x1F3FF, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F469, 0x1F3FF, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F33E, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F373, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F37C, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F393, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F3A4, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F3A8, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F3EB, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F3ED, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F4BB, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F4BC, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F527, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F52C, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F680, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F692, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F9AF, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F9BC, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F9BD, + 8, 0x1F469, 0x1F3FF, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F6B6, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FB, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FC, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FE, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9CE, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FB, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FC, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FE, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x200D, 0x2695, 0xFE0F, + 5, 0x1F9D1, 0x200D, 0x2696, 0xFE0F, + 5, 0x1F9D1, 0x200D, 0x2708, 0xFE0F, + 4, 0x1F9D1, 0x200D, 0x1F33E, + 4, 0x1F9D1, 0x200D, 0x1F373, + 4, 0x1F9D1, 0x200D, 0x1F37C, + 4, 0x1F9D1, 0x200D, 0x1F384, + 4, 0x1F9D1, 0x200D, 0x1F393, + 4, 0x1F9D1, 0x200D, 0x1F3A4, + 4, 0x1F9D1, 0x200D, 0x1F3A8, + 4, 0x1F9D1, 0x200D, 0x1F3EB, + 4, 0x1F9D1, 0x200D, 0x1F3ED, + 4, 0x1F9D1, 0x200D, 0x1F4BB, + 4, 0x1F9D1, 0x200D, 0x1F4BC, + 4, 0x1F9D1, 0x200D, 0x1F527, + 4, 0x1F9D1, 0x200D, 0x1F52C, + 4, 0x1F9D1, 0x200D, 0x1F680, + 4, 0x1F9D1, 0x200D, 0x1F692, + 4, 0x1F9D1, 0x200D, 0x1F9AF, + 7, 0x1F9D1, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 4, 0x1F9D1, 0x200D, 0x1F9BC, + 7, 0x1F9D1, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 4, 0x1F9D1, 0x200D, 0x1F9BD, + 7, 0x1F9D1, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9D1, 0x1F3FB, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F9D1, 0x1F3FB, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F9D1, 0x1F3FB, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F33E, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F373, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F37C, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F384, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F393, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F3A4, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F3A8, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F3EB, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F3ED, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F4BB, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F4BC, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F527, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F52C, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F680, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F692, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9AF, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9BC, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9BD, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9D1, 0x1F3FC, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F9D1, 0x1F3FC, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F9D1, 0x1F3FC, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F33E, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F373, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F37C, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F384, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F393, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F3A4, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F3A8, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F3EB, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F3ED, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F4BB, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F4BC, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F527, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F52C, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F680, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F692, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9AF, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9BC, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9BD, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9D1, 0x1F3FD, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F9D1, 0x1F3FD, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F9D1, 0x1F3FD, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F33E, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F373, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F37C, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F384, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F393, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F3A4, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F3A8, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F3EB, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F3ED, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F4BB, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F4BC, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F527, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F52C, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F680, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F692, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9AF, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9BC, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9BD, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9D1, 0x1F3FE, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F9D1, 0x1F3FE, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F9D1, 0x1F3FE, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F33E, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F373, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F37C, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F384, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F393, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F3A4, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F3A8, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F3EB, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F3ED, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F4BB, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F4BC, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F527, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F52C, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F680, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F692, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9AF, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9BC, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9BD, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9D1, 0x1F3FF, 0x200D, 0x2695, 0xFE0F, + 6, 0x1F9D1, 0x1F3FF, 0x200D, 0x2696, 0xFE0F, + 6, 0x1F9D1, 0x1F3FF, 0x200D, 0x2708, 0xFE0F, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F33E, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F373, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F37C, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F384, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F393, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F3A4, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F3A8, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F3EB, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F3ED, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F4BB, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F4BC, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F527, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F52C, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F680, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F692, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9AF, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9AF, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9BC, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9BC, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9BD, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9BD, 0x200D, 0x27A1, 0xFE0F, + 6, 0x26F9, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x26F9, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x26F9, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x26F9, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x26F9, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x26F9, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x26F9, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x26F9, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x26F9, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x26F9, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 6, 0x26F9, 0xFE0F, 0x200D, 0x2640, 0xFE0F, + 6, 0x26F9, 0xFE0F, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F3C3, 0x200D, 0x2640, 0xFE0F, + 8, 0x1F3C3, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F3C3, 0x200D, 0x2642, 0xFE0F, + 8, 0x1F3C3, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F3C3, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F3C3, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F3C3, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F3C3, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F3C3, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F3C3, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F3C3, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F3C3, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F3C3, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F3C3, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F3C3, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F3C4, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F3C4, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3C4, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3C4, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3C4, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3C4, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3C4, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3C4, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3C4, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3C4, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3C4, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3C4, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F3CA, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F3CA, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CA, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CA, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CA, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CA, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CA, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CA, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CA, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CA, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CA, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CA, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CB, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CB, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CB, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CB, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CB, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CB, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CB, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CB, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CB, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CB, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CB, 0xFE0F, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CB, 0xFE0F, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CC, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CC, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CC, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CC, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CC, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CC, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CC, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CC, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CC, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CC, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F3CC, 0xFE0F, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F3CC, 0xFE0F, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F46E, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F46E, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46E, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46E, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46E, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46E, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46E, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46E, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46E, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46E, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46E, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46E, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F46F, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F46F, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46F, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46F, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46F, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46F, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46F, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46F, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46F, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46F, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F46F, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F46F, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F470, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F470, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F470, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F470, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F470, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F470, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F470, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F470, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F470, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F470, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F470, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F470, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F471, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F471, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F471, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F471, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F471, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F471, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F471, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F471, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F471, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F471, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F471, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F471, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F473, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F473, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F473, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F473, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F473, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F473, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F473, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F473, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F473, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F473, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F473, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F473, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F477, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F477, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F477, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F477, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F477, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F477, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F477, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F477, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F477, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F477, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F477, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F477, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F481, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F481, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F481, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F481, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F481, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F481, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F481, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F481, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F481, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F481, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F481, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F481, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F482, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F482, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F482, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F482, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F482, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F482, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F482, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F482, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F482, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F482, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F482, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F482, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F486, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F486, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F486, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F486, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F486, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F486, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F486, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F486, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F486, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F486, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F486, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F486, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F487, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F487, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F487, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F487, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F487, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F487, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F487, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F487, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F487, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F487, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F487, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F487, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F575, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F575, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F575, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F575, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F575, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F575, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F575, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F575, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F575, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F575, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F575, 0xFE0F, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F575, 0xFE0F, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F645, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F645, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F645, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F645, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F645, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F645, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F645, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F645, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F645, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F645, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F645, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F645, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F646, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F646, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F646, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F646, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F646, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F646, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F646, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F646, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F646, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F646, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F646, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F646, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F647, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F647, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F647, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F647, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F647, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F647, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F647, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F647, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F647, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F647, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F647, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F647, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F64B, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F64B, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64B, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64B, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64B, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64B, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64B, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64B, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64B, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64B, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64B, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64B, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F64D, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F64D, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64D, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64D, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64D, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64D, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64D, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64D, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64D, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64D, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64D, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64D, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F64E, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F64E, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64E, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64E, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64E, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64E, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64E, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64E, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64E, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64E, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F64E, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F64E, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F6A3, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F6A3, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6A3, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6A3, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6A3, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6A3, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6A3, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6A3, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6A3, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6A3, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6A3, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6A3, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F6B4, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F6B4, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B4, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B4, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B4, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B4, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B4, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B4, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B4, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B4, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B4, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B4, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F6B5, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F6B5, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B5, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B5, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B5, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B5, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B5, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B5, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B5, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B5, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F6B5, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F6B5, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F6B6, 0x200D, 0x2640, 0xFE0F, + 8, 0x1F6B6, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F6B6, 0x200D, 0x2642, 0xFE0F, + 8, 0x1F6B6, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F6B6, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F6B6, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F6B6, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F6B6, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F6B6, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F6B6, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F6B6, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F6B6, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F6B6, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F6B6, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F6B6, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F926, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F926, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F926, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F926, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F926, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F926, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F926, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F926, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F926, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F926, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F926, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F926, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F935, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F935, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F935, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F935, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F935, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F935, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F935, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F935, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F935, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F935, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F935, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F935, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F937, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F937, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F937, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F937, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F937, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F937, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F937, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F937, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F937, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F937, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F937, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F937, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F938, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F938, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F938, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F938, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F938, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F938, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F938, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F938, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F938, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F938, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F938, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F938, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F939, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F939, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F939, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F939, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F939, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F939, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F939, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F939, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F939, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F939, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F939, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F939, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F93C, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F93C, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93C, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93C, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93C, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93C, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93C, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93C, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93C, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93C, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93C, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93C, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F93D, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F93D, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93D, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93D, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93D, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93D, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93D, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93D, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93D, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93D, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93D, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93D, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F93E, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F93E, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93E, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93E, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93E, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93E, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93E, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93E, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93E, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93E, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F93E, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F93E, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9B8, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9B8, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B8, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B8, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B8, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B8, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B8, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B8, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B8, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B8, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B8, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B8, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9B9, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9B9, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B9, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B9, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B9, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B9, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B9, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B9, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B9, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B9, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9B9, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9B9, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9CD, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9CD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CD, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CD, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CD, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CD, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CD, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CD, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CD, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CD, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CD, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CD, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9CE, 0x200D, 0x2640, 0xFE0F, + 8, 0x1F9CE, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9CE, 0x200D, 0x2642, 0xFE0F, + 8, 0x1F9CE, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F9CE, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F9CE, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F9CE, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F9CE, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F9CE, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F9CE, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F9CE, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F9CE, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 9, 0x1F9CE, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 6, 0x1F9CE, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 9, 0x1F9CE, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, 0x200D, 0x27A1, 0xFE0F, + 5, 0x1F9CF, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9CF, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CF, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CF, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CF, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CF, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CF, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CF, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CF, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CF, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9CF, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9CF, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9D4, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9D4, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D4, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D4, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D4, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D4, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D4, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D4, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D4, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D4, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D4, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D4, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9D6, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9D6, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D6, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D6, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D6, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D6, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D6, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D6, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D6, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D6, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D6, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D6, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9D7, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9D7, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D7, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D7, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D7, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D7, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D7, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D7, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D7, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D7, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D7, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D7, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9D8, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9D8, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D8, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D8, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D8, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D8, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D8, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D8, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D8, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D8, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D8, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D8, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9D9, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9D9, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D9, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D9, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D9, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D9, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D9, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D9, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D9, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D9, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9D9, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9D9, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9DA, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9DA, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DA, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DA, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DA, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DA, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DA, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DA, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DA, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DA, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DA, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DA, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9DB, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9DB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DB, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DB, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DB, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DB, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DB, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DB, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DB, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DB, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DB, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DB, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9DC, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9DC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DC, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DC, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DC, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DC, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DC, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DC, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DC, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DC, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DC, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DC, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9DD, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9DD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DD, 0x1F3FB, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DD, 0x1F3FB, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DD, 0x1F3FC, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DD, 0x1F3FC, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DD, 0x1F3FD, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DD, 0x1F3FD, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DD, 0x1F3FE, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DD, 0x1F3FE, 0x200D, 0x2642, 0xFE0F, + 6, 0x1F9DD, 0x1F3FF, 0x200D, 0x2640, 0xFE0F, + 6, 0x1F9DD, 0x1F3FF, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9DE, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9DE, 0x200D, 0x2642, 0xFE0F, + 5, 0x1F9DF, 0x200D, 0x2640, 0xFE0F, + 5, 0x1F9DF, 0x200D, 0x2642, 0xFE0F, + 4, 0x1F468, 0x200D, 0x1F9B0, + 4, 0x1F468, 0x200D, 0x1F9B1, + 4, 0x1F468, 0x200D, 0x1F9B2, + 4, 0x1F468, 0x200D, 0x1F9B3, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F9B0, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F9B1, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F9B2, + 5, 0x1F468, 0x1F3FB, 0x200D, 0x1F9B3, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F9B0, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F9B1, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F9B2, + 5, 0x1F468, 0x1F3FC, 0x200D, 0x1F9B3, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F9B0, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F9B1, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F9B2, + 5, 0x1F468, 0x1F3FD, 0x200D, 0x1F9B3, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F9B0, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F9B1, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F9B2, + 5, 0x1F468, 0x1F3FE, 0x200D, 0x1F9B3, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F9B0, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F9B1, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F9B2, + 5, 0x1F468, 0x1F3FF, 0x200D, 0x1F9B3, + 4, 0x1F469, 0x200D, 0x1F9B0, + 4, 0x1F469, 0x200D, 0x1F9B1, + 4, 0x1F469, 0x200D, 0x1F9B2, + 4, 0x1F469, 0x200D, 0x1F9B3, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F9B0, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F9B1, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F9B2, + 5, 0x1F469, 0x1F3FB, 0x200D, 0x1F9B3, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F9B0, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F9B1, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F9B2, + 5, 0x1F469, 0x1F3FC, 0x200D, 0x1F9B3, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F9B0, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F9B1, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F9B2, + 5, 0x1F469, 0x1F3FD, 0x200D, 0x1F9B3, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F9B0, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F9B1, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F9B2, + 5, 0x1F469, 0x1F3FE, 0x200D, 0x1F9B3, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F9B0, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F9B1, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F9B2, + 5, 0x1F469, 0x1F3FF, 0x200D, 0x1F9B3, + 4, 0x1F9D1, 0x200D, 0x1F9B0, + 4, 0x1F9D1, 0x200D, 0x1F9B1, + 4, 0x1F9D1, 0x200D, 0x1F9B2, + 4, 0x1F9D1, 0x200D, 0x1F9B3, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9B0, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9B1, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9B2, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F9B3, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9B0, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9B1, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9B2, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F9B3, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9B0, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9B1, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9B2, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F9B3, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9B0, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9B1, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9B2, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F9B3, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9B0, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9B1, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9B2, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F9B3, + 5, 0x26D3, 0xFE0F, 0x200D, 0x1F4A5, + 5, 0x2764, 0xFE0F, 0x200D, 0x1F525, + 5, 0x2764, 0xFE0F, 0x200D, 0x1FA79, + 4, 0x1F344, 0x200D, 0x1F7EB, + 4, 0x1F34B, 0x200D, 0x1F7E9, + 6, 0x1F3F3, 0xFE0F, 0x200D, 0x26A7, 0xFE0F, + 5, 0x1F3F3, 0xFE0F, 0x200D, 0x1F308, + 5, 0x1F3F4, 0x200D, 0x2620, 0xFE0F, + 4, 0x1F408, 0x200D, 0x2B1B, + 4, 0x1F415, 0x200D, 0x1F9BA, + 4, 0x1F426, 0x200D, 0x2B1B, + 4, 0x1F426, 0x200D, 0x1F525, + 5, 0x1F43B, 0x200D, 0x2744, 0xFE0F, + 6, 0x1F441, 0xFE0F, 0x200D, 0x1F5E8, 0xFE0F, + 4, 0x1F62E, 0x200D, 0x1F4A8, + 4, 0x1F635, 0x200D, 0x1F4AB, + 5, 0x1F636, 0x200D, 0x1F32B, 0xFE0F, + 5, 0x1F642, 0x200D, 0x2194, 0xFE0F, + 5, 0x1F642, 0x200D, 0x2195, 0xFE0F, + 4, 0x1F9D1, 0x200D, 0x1FA70, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FF, + 5, 0x1F9D1, 0x1F3FB, 0x200D, 0x1FA70, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FB, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FF, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FF, + 5, 0x1F9D1, 0x1F3FC, 0x200D, 0x1FA70, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FC, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FF, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FF, + 5, 0x1F9D1, 0x1F3FD, 0x200D, 0x1FA70, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FE, + 8, 0x1F9D1, 0x1F3FD, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FF, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FF, + 5, 0x1F9D1, 0x1F3FE, 0x200D, 0x1FA70, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FE, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FF, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1F430, 0x200D, 0x1F9D1, 0x1F3FE, + 5, 0x1F9D1, 0x1F3FF, 0x200D, 0x1FA70, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FB, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FC, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FD, + 8, 0x1F9D1, 0x1F3FF, 0x200D, 0x1FAEF, 0x200D, 0x1F9D1, 0x1F3FE, + 0 // Padding. +#endif // !defined(SRELL_NO_UNICODE_POS) +}; +#define SRELL_UPDATA_VERSION 301 diff --git a/pjsontest/CMakeLists.txt b/pjsontest/CMakeLists.txt index 80dad3c..b6bb6f5 100644 --- a/pjsontest/CMakeLists.txt +++ b/pjsontest/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.21) set (TARGET_NAME pjsontest) project (${TARGET_NAME}) +find_package(Threads REQUIRED) # ---- Test sources ------------------------------------------------------- @@ -34,6 +35,14 @@ ${SRC_DIR}/tests_allocator.cpp ${SRC_DIR}/tests_streaming.cpp ${SRC_DIR}/tests_serialize_access.cpp ${SRC_DIR}/tests_pointer_patch.cpp +${SRC_DIR}/tests_embedded_nul.cpp +${SRC_DIR}/tests_aliasing.cpp +${SRC_DIR}/tests_numbers.cpp +${SRC_DIR}/tests_depth_frontends.cpp +${SRC_DIR}/tests_error_model.cpp +${SRC_DIR}/tests_dom_api.cpp +${SRC_DIR}/tests_serialize_limits.cpp +${SRC_DIR}/tests_schema_2020.cpp ) # ---- Test target -------------------------------------------------------- @@ -59,32 +68,36 @@ endif() # Single assertion-based executable returns non-zero on any failing case. add_executable(${TARGET_NAME} ${TEST_SRC_FILES}) target_include_directories(${TARGET_NAME} PUBLIC ${INC_DIRS}) -target_link_libraries(${TARGET_NAME} ${${TARGET_NAME}_libs}) +target_link_libraries(${TARGET_NAME} ${${TARGET_NAME}_libs} Threads::Threads) target_compile_options(${TARGET_NAME} PRIVATE ${PJSON_TEST_WARN_FLAGS}) target_compile_definitions(${TARGET_NAME} PRIVATE PJSON_TEST_DEFAULT_JSONTESTSUITE_DIR="${CMAKE_SOURCE_DIR}/.test-corpora/JSONTestSuite" PJSON_TEST_DEFAULT_JSON_SCHEMA_TEST_SUITE_DIR="${CMAKE_SOURCE_DIR}/.test-corpora/JSON-Schema-Test-Suite") enable_testing() +find_package(Python3 COMPONENTS Interpreter QUIET) +if(Python3_Interpreter_FOUND) + add_test(NAME pjson.benchmark_report_tools + COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tests/test_benchmark_tools.py) +endif() # ---- CTest case discovery ---------------------------------------------- -# Keep one test executable, but expose each self-registered TEST() separately to -# CTest. Extracting TEST(name) declarations at configure time lets CMake resolve -# the executable correctly for single- and multi-config generators on every OS. -set(PJSON_REGISTERED_TESTS) -foreach(test_source IN LISTS TEST_SRC_FILES) - file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/${test_source}" test_declarations - REGEX "TEST[(][A-Za-z0-9_]+[)]") - foreach(declaration IN LISTS test_declarations) - string(REGEX MATCH "TEST[(]([A-Za-z0-9_]+)[)]" unused "${declaration}") - set(test_name "${CMAKE_MATCH_1}") - if(test_name IN_LIST PJSON_REGISTERED_TESTS) - message(FATAL_ERROR "Duplicate pjson test name: ${test_name}") - endif() - list(APPEND PJSON_REGISTERED_TESTS "${test_name}") - add_test(NAME "pjson.${test_name}" COMMAND ${TARGET_NAME} --run-test "${test_name}") - endforeach() -endforeach() -list(LENGTH PJSON_REGISTERED_TESTS PJSON_TEST_COUNT) -message(STATUS "Registered ${PJSON_TEST_COUNT} pjson test cases with CTest") +# Generate CTest registrations from the executable's actual self-registered +# test list after linking. This cannot drift when TEST() is wrapped, generated, +# or conditionally compiled. Seed an empty include so configure-only workflows +# remain valid; every successful build atomically replaces it. +set(PJSON_DISCOVERED_TESTS "${CMAKE_CURRENT_BINARY_DIR}/pjsontest_tests.cmake") +if(NOT EXISTS "${PJSON_DISCOVERED_TESTS}") + file(WRITE "${PJSON_DISCOVERED_TESTS}" + "# Build pjsontest to discover its compiled test registry.\n") +endif() +set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${PJSON_DISCOVERED_TESTS}") +add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} + -DPJSON_TEST_EXECUTABLE=$ + -DPJSON_TEST_OUTPUT=${PJSON_DISCOVERED_TESTS} + -P ${CMAKE_SOURCE_DIR}/cmake/DiscoverTests.cmake + BYPRODUCTS "${PJSON_DISCOVERED_TESTS}" + VERBATIM + COMMENT "Discovering pjson tests from the compiled registry") diff --git a/pjsontest/src/test_harness.h b/pjsontest/src/test_harness.h index 9b7e443..c5ce88f 100644 --- a/pjsontest/src/test_harness.h +++ b/pjsontest/src/test_harness.h @@ -193,15 +193,15 @@ namespace pjson_test { } \ } while (0) -// Asserts that parsing aStr yields an empty result (invalid input) without -// throwing. +// Asserts that parsing aStr fails (returns an error) without throwing. #define CHECK_PARSE_FAILS(aStr) \ do { \ ::pjson_test::current().checks += 1; \ - ByteDance::pjson::unique_ptr _p = ByteDance::pjson::parse(aStr); \ - if (_p != nullptr) { \ - ::pjson_test::report_failure(__FILE__, __LINE__, "parse(" #aStr ") == nullptr", \ - "parsed to: " + _p->toString()); \ + ByteDance::pJsonParser::Error _e; \ + ByteDance::pjson _p = ByteDance::pJsonParser().parse(aStr, _e); \ + if (_e.ok) { \ + ::pjson_test::report_failure(__FILE__, __LINE__, "parse(" #aStr ") should fail", \ + "parsed to: " + _p.toString()); \ } \ } while (0) diff --git a/pjsontest/src/test_util.h b/pjsontest/src/test_util.h index 5f8b7ce..f60d284 100644 --- a/pjsontest/src/test_util.h +++ b/pjsontest/src/test_util.h @@ -15,25 +15,182 @@ //===----------------------------------------------------------------------===// // Shared helpers for the pjson test suite. // +// The public parse() API returns a pjson value plus a pJsonParser::Error (no smart +// pointer). Parsed is a TEST-ONLY owning wrapper that adapts that value+error +// pair to a pointer-like handle so the many existing cases can keep reading as +// `if (p)`, `p->`, `*p`, and `p == nullptr` where those meant "parse +// succeeded". It is not part of the library API. +// #ifndef PJSON_TEST_UTIL_H #define PJSON_TEST_UTIL_H #include "pjson.h" +#include "pjson_parser.h" +#include "pjson_schema.h" #include "test_harness.h" +#include +#include #include +#include +#include namespace pjson_test { - // Parses via the public API and returns the owning unique_ptr, so tests read - // naturally and never leak even on a failed assertion. - inline ByteDance::pjson::unique_ptr parse(const std::string& s) { - return ByteDance::pjson::parse(s); + using ByteDance::pjson; + using ByteDance::pJsonParser; + using ByteDance::pJsonSchemaValidator; + + // Short aliases for the validator's vocabulary types. Schema validation is + // no longer a pjson member; it lives in the external pJsonSchemaValidator, + // which carries its own Error/Options types. These aliases keep the many + // existing schema tests concise. + typedef pJsonSchemaValidator::Error SchemaError; + typedef pJsonSchemaValidator::Options SchemaOptions; + + // Convenience wrappers around the construct-once/validate API. The bulk of + // the schema suite only checks pass/fail (optionally collecting errors and + // supplying options) and does not care about reusing a compiled validator, + // so these adapt the external validator to a single call. Tests that + // exercise construction, reuse, or introspection use pJsonSchemaValidator + // directly. + inline bool schemaValidate(const pjson& aInstance, const pjson& aSchema) { + pJsonSchemaValidator validator(aSchema); + return validator.validate(aInstance); + } + inline bool schemaValidate(const pjson& aInstance, const pjson& aSchema, + std::vector& aErrors) { + pJsonSchemaValidator validator(aSchema); + return validator.validate(aInstance, aErrors); + } + inline bool schemaValidate(const pjson& aInstance, const pjson& aSchema, + const SchemaOptions& aOptions) { + pJsonSchemaValidator validator(aSchema, aOptions); + return validator.validate(aInstance); + } + inline bool schemaValidate(const pjson& aInstance, const pjson& aSchema, + std::vector& aErrors, const SchemaOptions& aOptions) { + pJsonSchemaValidator validator(aSchema, aOptions); + return validator.validate(aInstance, aErrors); + } + + // Owning, pointer-like parse result. `ok()` (and the bool/nullptr operators) + // reflect pJsonParser::Error::ok, so a successfully parsed literal `null` is truthy, + // while only an actual failure compares equal to nullptr. + struct Parsed { + pjson value; + pJsonParser::Error error; + + Parsed() {} // error defaults to ok == true + // Wraps an already-built value (e.g. a hand-constructed document) as a + // successful result. + Parsed(pjson aValue) // NOLINT(runtime/explicit): intentional convenience + : value(std::move(aValue)) {} + // Binds the held value to a specific allocator so allocator-provenance + // tests observe the intended allocator after a same-allocator move. + explicit Parsed(pjson::Allocator& aAlloc) + : value(aAlloc) {} + + bool ok() const { return error.ok; } + explicit operator bool() const { return error.ok; } + bool operator==(std::nullptr_t) const { return !error.ok; } + bool operator!=(std::nullptr_t) const { return error.ok; } + + pjson* operator->() { return &value; } + const pjson* operator->() const { return &value; } + pjson& operator*() { return value; } + const pjson& operator*() const { return value; } + }; + + inline bool operator==(std::nullptr_t, const Parsed& aParsed) { + return !aParsed.error.ok; + } + inline bool operator!=(std::nullptr_t, const Parsed& aParsed) { + return aParsed.error.ok; + } + + //== Default-allocator parse helpers ===================================== + inline Parsed parse(const std::string& s, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r; + r.value = pJsonParser(o).parse(s, r.error); + return r; + } + inline Parsed parse(const std::string& s, pJsonParser::Error& e, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r; + r.value = pJsonParser(o).parse(s, e); + r.error = e; + return r; + } + inline Parsed parse(const char* s, size_t n, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r; + r.value = pJsonParser(o).parse(s, n, r.error); + return r; + } + inline Parsed parse(const char* s, size_t n, pJsonParser::Error& e, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r; + r.value = pJsonParser(o).parse(s, n, e); + r.error = e; + return r; } - // Length-aware counterpart used for embedded-NUL and truncated-buffer cases. - inline ByteDance::pjson::unique_ptr parse(const char* s, size_t n) { - return ByteDance::pjson::parse(s, n); + //== Allocator-aware parse helpers (preserve provenance) ================= + inline Parsed parse(const std::string& s, pjson::Allocator& a, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r(a); + r.value = pJsonParser(a, o).parse(s, r.error); + return r; + } + inline Parsed parse(const std::string& s, pJsonParser::Error& e, pjson::Allocator& a, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r(a); + r.value = pJsonParser(a, o).parse(s, e); + r.error = e; + return r; + } + inline Parsed parse(const char* s, size_t n, pjson::Allocator& a, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r(a); + r.value = pJsonParser(a, o).parse(s, n, r.error); + return r; + } + inline Parsed parse(const char* s, size_t n, pJsonParser::Error& e, pjson::Allocator& a, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r(a); + r.value = pJsonParser(a, o).parse(s, n, e); + r.error = e; + return r; + } + + //== Stream parse helpers ================================================ + inline Parsed parseStream(std::istream& in, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r; + r.value = pJsonParser(o).parseStream(in, r.error); + return r; + } + inline Parsed parseStream(std::istream& in, pJsonParser::Error& e, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r; + r.value = pJsonParser(o).parseStream(in, e); + r.error = e; + return r; + } + inline Parsed parseStream(std::istream& in, pjson::Allocator& a, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r(a); + r.value = pJsonParser(a, o).parseStream(in, r.error); + return r; + } + inline Parsed parseStream(std::istream& in, pJsonParser::Error& e, pjson::Allocator& a, + const pJsonParser::Options& o = pJsonParser::Options()) { + Parsed r(a); + r.value = pJsonParser(a, o).parseStream(in, e); + r.error = e; + return r; } inline int64_t valueInt(const ByteDance::pjson& value) { diff --git a/pjsontest/src/tests_aliasing.cpp b/pjsontest/src/tests_aliasing.cpp new file mode 100644 index 0000000..2cb7692 --- /dev/null +++ b/pjsontest/src/tests_aliasing.cpp @@ -0,0 +1,221 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// PJSON-COR-002 regression matrix: every public copy/move/swap/assignment must +// be memory-safe when the source aliases the destination, including ancestor and +// descendant relationships. These cases are intended to run clean under +// AddressSanitizer, UndefinedBehaviorSanitizer, and leak checking. +// +#include "pjson.h" +#include "test_harness.h" + +#include +#include + +using namespace ByteDance; + +//===----------------------------------------------------------------------===// +// The canonical descendant move-assignment case from the requirements doc. +//===----------------------------------------------------------------------===// +TEST(aliasing_move_assign_from_descendant) { + pjson root; + root["child"]["value"] = std::int64_t{7}; + pjson& child = root["child"]; + root = std::move(child); // must not use freed storage + // After the move, root became the former child object. + CHECK(root.isObject()); + int64_t v = 0; + CHECK(root.tryGet("value", v)); + CHECK_EQ(v, int64_t(7)); +} + +//===----------------------------------------------------------------------===// +// Copy-assigning a root from one of its descendants. +//===----------------------------------------------------------------------===// +TEST(aliasing_copy_assign_from_descendant) { + pjson root; + root["child"]["value"] = std::int64_t{11}; + root = root["child"]; // copy-and-swap keeps the deep copy alive + CHECK(root.isObject()); + int64_t v = 0; + CHECK(root.tryGet("value", v)); + CHECK_EQ(v, int64_t(11)); +} + +//===----------------------------------------------------------------------===// +// Assigning a descendant from its root (destination inside the source subtree). +//===----------------------------------------------------------------------===// +TEST(aliasing_copy_assign_descendant_from_root) { + pjson root; + root["a"] = std::int64_t{1}; + root["child"]["value"] = std::int64_t{2}; + root["child"] = root; // descendant becomes a copy of the whole document + // root still valid and internally consistent. + CHECK(root.isObject()); + CHECK(root.hasKey("a")); + CHECK(root.hasKey("child")); + const pjson* child = root.find("child"); + CHECK(child != nullptr); + if (child) + CHECK(child->hasKey("a")); +} + +//===----------------------------------------------------------------------===// +// Move-assigning a descendant from its root. +//===----------------------------------------------------------------------===// +TEST(aliasing_move_assign_descendant_from_root) { + pjson root; + root["a"] = std::int64_t{1}; + root["child"]["value"] = std::int64_t{2}; + root["child"] = std::move(root); // snapshots the ancestor; cannot steal it + CHECK(root.isObject()); + CHECK(root.hasKey("a")); + const pjson* child = root.find("child"); + CHECK(child != nullptr); + if (child != nullptr) { + CHECK(child->isObject()); + CHECK(child->hasKey("a")); + CHECK(child->hasKey("child")); + } +} + +TEST(aliasing_generic_insertions_snapshot_ancestors) { + pjson appended; + appended["value"] = int64_t(7); + appended.pushBack(std::move(appended)); + CHECK(appended.isArray()); + CHECK_EQ(appended.size(), size_t(1)); + int64_t value = 0; + CHECK(appended.find(0) != nullptr); + CHECK(appended.find(0)->tryGet("value", value)); + CHECK_EQ(value, int64_t(7)); + + pjson inserted; + inserted["before"] = true; + inserted.insertOrAssign("snapshot", std::move(inserted)); + CHECK(inserted.isObject()); + const pjson* snapshot = inserted.find("snapshot"); + CHECK(snapshot != nullptr); + CHECK(snapshot->hasKey("before")); + CHECK(!snapshot->hasKey("snapshot")); + + pjson siblings; + siblings["source"]["value"] = int64_t(13); + pjson& source = siblings["source"]; + siblings.insertOrAssign("destination", std::move(source)); + CHECK(siblings.find("source") != nullptr); + CHECK(siblings.find("source")->isNull()); + CHECK(siblings.find("destination") != nullptr); + int64_t siblingValue = 0; + CHECK(siblings.find("destination")->tryGet("value", siblingValue)); + CHECK_EQ(siblingValue, int64_t(13)); + + pjson arraySiblings; + arraySiblings[0]["value"] = int64_t(17); + pjson& first = arraySiblings[0]; + arraySiblings.pushBack(std::move(first)); + CHECK_EQ(arraySiblings.size(), size_t(2)); + CHECK(arraySiblings.find(0)->isNull()); + int64_t arrayValue = 0; + CHECK(arraySiblings.find(1)->tryGet("value", arrayValue)); + CHECK_EQ(arrayValue, int64_t(17)); +} + +TEST(aliasing_generic_insertions_survive_container_promotion) { + pjson arraySource; + arraySource[0]["value"] = int64_t(9); + pjson& arrayChild = arraySource[0]; + arrayChild.insertOrAssign("root", std::move(arraySource)); + CHECK(arraySource.isArray()); + const pjson* promoted = arraySource.find(0); + CHECK(promoted != nullptr); + const pjson* rootSnapshot = promoted == nullptr ? nullptr : promoted->find("root"); + CHECK(rootSnapshot != nullptr); + CHECK(rootSnapshot->isArray()); + + pjson objectSource; + objectSource["child"]["value"] = int64_t(11); + pjson& objectChild = objectSource["child"]; + objectChild.pushBack(std::move(objectSource)); + CHECK(objectSource.isObject()); + const pjson* childArray = objectSource.find("child"); + CHECK(childArray != nullptr); + CHECK(childArray->isArray()); + CHECK_EQ(childArray->size(), size_t(1)); +} + +//===----------------------------------------------------------------------===// +// Sibling-to-sibling assignment. +//===----------------------------------------------------------------------===// +TEST(aliasing_sibling_assignment) { + pjson root; + root["x"]["v"] = std::int64_t{100}; + root["y"] = std::string("old"); + root["y"] = root["x"]; + const pjson* y = root.find("y"); + CHECK(y != nullptr); + if (y) { + int64_t v = 0; + CHECK(y->tryGet("v", v)); + CHECK_EQ(v, int64_t(100)); + } + // Original sibling unaffected. + const pjson* x = root.find("x"); + CHECK(x != nullptr); + if (x) { + int64_t v = 0; + CHECK(x->tryGet("v", v)); + CHECK_EQ(v, int64_t(100)); + } +} + +//===----------------------------------------------------------------------===// +// Self copy and self move. +//===----------------------------------------------------------------------===// +TEST(aliasing_self_copy_and_move) { + pjson a; + a["k"] = std::string("v"); + pjson& ref = a; + a = ref; // self copy + int64_t unused = 0; + (void)unused; + std::string s; + CHECK(a.tryGet("k", s)); + CHECK_EQ(s, std::string("v")); + + pjson& mref = a; + a = std::move(mref); // self move + CHECK(a.tryGet("k", s)); + CHECK_EQ(s, std::string("v")); +} + +//===----------------------------------------------------------------------===// +// Swapping a root with a descendant is rejected as a safe no-op: they must be in +// the same allocator domain (they are) but swap of overlapping storage would be +// unsound, so pjson leaves both operands valid. We assert no crash and a valid +// tree afterward. +//===----------------------------------------------------------------------===// +TEST(aliasing_swap_root_and_descendant_is_safe) { + pjson root; + root["child"]["value"] = std::int64_t{5}; + pjson& child = root["child"]; + CHECK(!root.canSwap(child)); + CHECK(!child.canSwap(root)); + root.swap(child); // overlapping swap; must not corrupt memory + // The tree must remain traversable and destructible without error. + CHECK(root.isObject()); + const std::string serialized = root.toString(); + CHECK(!serialized.empty()); +} diff --git a/pjsontest/src/tests_allocator.cpp b/pjsontest/src/tests_allocator.cpp index 7fc1f1e..529cad0 100644 --- a/pjsontest/src/tests_allocator.cpp +++ b/pjsontest/src/tests_allocator.cpp @@ -31,32 +31,27 @@ // AllocationKind aKind) noexcept = 0; // }; // -// struct pjson::ValueDeleter { -// void operator()(pjson* aValue) const noexcept; -// }; -// typedef std::unique_ptr unique_ptr; -// // explicit pjson(Allocator& aAlloc) noexcept; // pjson(const pjson& aFrom, Allocator& aAlloc); // pjson(pjson&& aFrom, Allocator& aAlloc); // Allocator& getAllocator() const noexcept; // bool canSwap(const pjson& aOther) const noexcept; // -// static unique_ptr parse(const std::string& aStr, Allocator& aAlloc, -// const ParseOptions& aOpts = ParseOptions()); -// static unique_ptr parse(const char* aSrc, size_t aSize, Allocator& aAlloc, -// const ParseOptions& aOpts = ParseOptions()); -// static unique_ptr parse(const std::string& aStr, ParseError& aError, -// Allocator& aAlloc, -// const ParseOptions& aOpts = ParseOptions()); -// static unique_ptr parse(const char* aSrc, size_t aSize, ParseError& aError, -// Allocator& aAlloc, -// const ParseOptions& aOpts = ParseOptions()); -// static unique_ptr parseStream(std::istream& aIn, Allocator& aAlloc, -// const ParseOptions& aOpts = ParseOptions()); -// static unique_ptr parseStream(std::istream& aIn, ParseError& aError, -// Allocator& aAlloc, -// const ParseOptions& aOpts = ParseOptions()); +// static pjson parse(const std::string& aStr, Allocator& aAlloc, +// const pJsonParser::Options& aOpts = pJsonParser::Options()); +// static pjson parse(const char* aSrc, size_t aSize, Allocator& aAlloc, +// const pJsonParser::Options& aOpts = pJsonParser::Options()); +// static pjson parse(const std::string& aStr, pJsonParser::Error& aError, +// Allocator& aAlloc, +// const pJsonParser::Options& aOpts = pJsonParser::Options()); +// static pjson parse(const char* aSrc, size_t aSize, pJsonParser::Error& aError, +// Allocator& aAlloc, +// const pJsonParser::Options& aOpts = pJsonParser::Options()); +// static pjson parseStream(std::istream& aIn, Allocator& aAlloc, +// const pJsonParser::Options& aOpts = pJsonParser::Options()); +// static pjson parseStream(std::istream& aIn, pJsonParser::Error& aError, +// Allocator& aAlloc, +// const pJsonParser::Options& aOpts = pJsonParser::Options()); // // Semantics covered here: // - every node stores allocator provenance and children inherit it @@ -67,7 +62,9 @@ // - cross-allocator swap is explicitly rejected via canSwap()==false // #include "pjson.h" +#include "pjson_parser.h" #include "test_harness.h" +#include "test_util.h" #include #include @@ -165,7 +162,8 @@ namespace { // Disarms every failure point without disturbing lifetime counters. void clearFailures() { - for (int i = 0; i < 4; ++i) { + for (int i = 0; i <= static_cast(pjson::Allocator::ImplementationAllocation); + ++i) { _armedFailures[static_cast(i)] = -1; } } @@ -201,6 +199,7 @@ namespace { CHECK_EQ(aAlloc.stats(pjson::Allocator::StringAllocation).liveBlocks, size_t(0)); CHECK_EQ(aAlloc.stats(pjson::Allocator::ArrayAllocation).liveBlocks, size_t(0)); CHECK_EQ(aAlloc.stats(pjson::Allocator::ObjectAllocation).liveBlocks, size_t(0)); + CHECK_EQ(aAlloc.stats(pjson::Allocator::ImplementationAllocation).liveBlocks, size_t(0)); } // Walks iteratively so allocator-provenance checks remain safe for deeply nested values. @@ -213,7 +212,7 @@ namespace { CHECK_EQ(&cur->getAllocator(), &aExpected); if (cur->isArray()) { for (size_t i = 0; i < cur->size(); ++i) { - const pjson* child = cur->find(static_cast(i)); + const pjson* child = cur->findIndex(i); CHECK(child != nullptr); if (child != nullptr) work.push_back(child); @@ -305,14 +304,14 @@ namespace { } // Preserve the custom-deleter return type while keeping parse-overload tests concise. - static pjson::unique_ptr parseWithAllocator(const std::string& aText, - TrackingAllocator& aAlloc) { - return pjson::parse(aText, aAlloc); + static pjson_test::Parsed parseWithAllocator(const std::string& aText, + TrackingAllocator& aAlloc) { + return pjson_test::parse(aText, aAlloc); } - static pjson::unique_ptr parseWithAllocator(const std::string& aText, pjson::ParseError& aErr, - TrackingAllocator& aAlloc) { - return pjson::parse(aText, aErr, aAlloc); + static pjson_test::Parsed parseWithAllocator(const std::string& aText, pJsonParser::Error& aErr, + TrackingAllocator& aAlloc) { + return pjson_test::parse(aText, aErr, aAlloc); } } // namespace @@ -370,6 +369,34 @@ TEST(allocator_mutation_tracks_nodes_strings_arrays_and_objects) { checkAllocatorHealth(alloc); } +TEST(allocator_null_is_allocation_free_and_impl_failure_is_transactional) { + TrackingAllocator alloc("impl-lifetime"); + { + pjson value(alloc); + CHECK(value.isNull()); + CHECK_EQ(alloc.stats(pjson::Allocator::ImplementationAllocation).allocations, size_t(0)); + + alloc.failAfter(pjson::Allocator::ImplementationAllocation, 0); + bool threw = false; + try { + value = int64_t(42); + } catch (const std::bad_alloc&) { + threw = true; + } + CHECK(threw); + CHECK(value.isNull()); + CHECK_EQ(&value.getAllocator(), &alloc); + + alloc.clearFailures(); + value = int64_t(42); + CHECK_EQ(alloc.stats(pjson::Allocator::ImplementationAllocation).liveBlocks, size_t(1)); + value.reset(); + CHECK(value.isNull()); + CHECK_EQ(alloc.stats(pjson::Allocator::ImplementationAllocation).liveBlocks, size_t(0)); + } + checkAllocatorHealth(alloc); +} + //===----------------------------------------------------------------------===// // DOM parsing, teardown, and allocator-aware erase/reset //===----------------------------------------------------------------------===// @@ -377,7 +404,7 @@ TEST(allocator_mutation_tracks_nodes_strings_arrays_and_objects) { TEST(allocator_parse_success_uses_supplied_allocator_for_dom) { TrackingAllocator alloc("parse-ok"); { - pjson::unique_ptr doc = + pjson_test::Parsed doc = parseWithAllocator(R"({"name":"ada","list":[1,2,3],"obj":{"flag":true}})", alloc); CHECK(doc != nullptr); std::string name; @@ -402,8 +429,8 @@ TEST(allocator_parse_success_uses_supplied_allocator_for_dom) { TEST(allocator_parse_failure_unwinds_partials_and_keeps_balance) { TrackingAllocator alloc("parse-fail"); - pjson::ParseError err; - pjson::unique_ptr doc = parseWithAllocator(R"({"a":[1,2,{"b":[3,4,})", err, alloc); + pJsonParser::Error err; + pjson_test::Parsed doc = parseWithAllocator(R"({"a":[1,2,{"b":[3,4,})", err, alloc); CHECK(doc == nullptr); CHECK(!err.ok); CHECK(!err.message.empty()); @@ -415,8 +442,8 @@ TEST(allocator_parse_bad_alloc_returns_null_and_reports_error) { TrackingAllocator alloc("parse-oom"); alloc.failAfter(pjson::Allocator::NodeAllocation, 2); - pjson::ParseError err; - pjson::unique_ptr doc = parseWithAllocator(R"({"a":[1,2,3],"b":{"c":"text"}})", err, alloc); + pJsonParser::Error err; + pjson_test::Parsed doc = parseWithAllocator(R"({"a":[1,2,3],"b":{"c":"text"}})", err, alloc); CHECK(doc == nullptr); CHECK(!err.ok); CHECK(err.message.find("memory") != std::string::npos || @@ -707,17 +734,17 @@ TEST(allocator_string_assignment_failure_keeps_old_value) { TEST(allocator_default_and_custom_root_deleters_match_allocation_origin) { TrackingAllocator alloc("root-delete"); { - pjson::unique_ptr doc = pjson::parse(R"({"default":[1,2]})"); + pjson_test::Parsed doc = pjson_test::parse(R"({"default":[1,2]})"); CHECK(doc != nullptr); CHECK(&doc->getAllocator() != &alloc); } { - pjson::unique_ptr ordinaryNode(new pjson()); + std::unique_ptr ordinaryNode(new pjson()); (*ordinaryNode)["value"] = int64_t(1); } { - pjson::unique_ptr doc = pjson::parse(R"({"custom":[1,2]})", alloc); + pjson_test::Parsed doc = pjson_test::parse(R"({"custom":[1,2]})", alloc); CHECK(doc != nullptr); CHECK_EQ(&doc->getAllocator(), &alloc); checkTreeAllocator(*doc, alloc); @@ -757,26 +784,27 @@ TEST(allocator_all_dom_parse_overloads_use_custom_root_deletion) { TrackingAllocator alloc("parse-overloads"); { const std::string text = R"({"value":[1,2,3]})"; - pjson::ParseOptions opts; - pjson::ParseError error; + pJsonParser::Options opts; + pJsonParser::Error error; - pjson::unique_ptr fromBuffer = pjson::parse(text.data(), text.size(), alloc, opts); + pjson_test::Parsed fromBuffer = pjson_test::parse(text.data(), text.size(), alloc, opts); CHECK(fromBuffer != nullptr); checkTreeAllocator(*fromBuffer, alloc); - pjson::unique_ptr fromBufferError = - pjson::parse(text.data(), text.size(), error, alloc, opts); + pjson_test::Parsed fromBufferError = + pjson_test::parse(text.data(), text.size(), error, alloc, opts); CHECK(fromBufferError != nullptr); CHECK(error.ok); checkTreeAllocator(*fromBufferError, alloc); std::istringstream firstStream(text); - pjson::unique_ptr fromStream = pjson::parseStream(firstStream, alloc, opts); + pjson_test::Parsed fromStream = pjson_test::parseStream(firstStream, alloc, opts); CHECK(fromStream != nullptr); checkTreeAllocator(*fromStream, alloc); std::istringstream secondStream(text); - pjson::unique_ptr fromStreamError = pjson::parseStream(secondStream, error, alloc, opts); + pjson_test::Parsed fromStreamError = + pjson_test::parseStream(secondStream, error, alloc, opts); CHECK(fromStreamError != nullptr); CHECK(error.ok); checkTreeAllocator(*fromStreamError, alloc); @@ -784,6 +812,27 @@ TEST(allocator_all_dom_parse_overloads_use_custom_root_deletion) { checkAllocatorHealth(alloc); } +TEST(allocator_parser_exposes_and_reuses_selected_allocator) { + TrackingAllocator alloc("parser-instance"); + { + pJsonParser::Options options; + options.maxNodes = 8; + pJsonParser parser(alloc, options); + CHECK(&parser.allocator() == &alloc); + CHECK_EQ(parser.options().maxNodes, size_t(8)); + + pJsonParser::Error error; + pjson first = parser.parse(R"({"a":[1,2]})", error); + CHECK(error.ok); + checkTreeAllocator(first, alloc); + + pjson second = parser.parse(R"({"b":true})", error); + CHECK(error.ok); + checkTreeAllocator(second, alloc); + } + checkAllocatorHealth(alloc); +} + TEST(allocator_patch_and_merge_patch_oom_leave_destination_unchanged) { TrackingAllocator destination("patch-oom-destination"); TrackingAllocator source("patch-oom-source"); diff --git a/pjsontest/src/tests_api_edge.cpp b/pjsontest/src/tests_api_edge.cpp index ddab923..7b378c1 100644 --- a/pjsontest/src/tests_api_edge.cpp +++ b/pjsontest/src/tests_api_edge.cpp @@ -18,7 +18,9 @@ // output, parse ownership, and deep equality/copy behavior. // #include "pjson.h" +#include "pjson_parser.h" #include "test_harness.h" +#include "test_util.h" #include #include @@ -61,8 +63,8 @@ TEST(api_int64_boundaries_round_trip) { CHECK_EQ(mx.toString(), std::string("9223372036854775807")); CHECK_EQ(mn.toString(), std::string("-9223372036854775808")); - pjson::unique_ptr pmx = pjson::parse("9223372036854775807"); - pjson::unique_ptr pmn = pjson::parse("-9223372036854775808"); + pjson_test::Parsed pmx = pjson_test::parse("9223372036854775807"); + pjson_test::Parsed pmn = pjson_test::parse("-9223372036854775808"); CHECK(pmx != nullptr); CHECK(pmn != nullptr); if (pmx) @@ -89,7 +91,7 @@ TEST(api_double_formatting_edges) { for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { pjson j; j = cases[i].v; - pjson::unique_ptr rt = pjson::parse(j.toString()); + pjson_test::Parsed rt = pjson_test::parse(j.toString()); CHECK(rt != nullptr); if (rt) CHECK_EQ(mustGetDouble(*rt), cases[i].v); @@ -110,7 +112,7 @@ TEST(api_large_string_round_trip) { pjson j; j["blob"] = big; - pjson::unique_ptr rt = pjson::parse(j.toString()); + pjson_test::Parsed rt = pjson_test::parse(j.toString()); CHECK(rt != nullptr); if (!rt) return; @@ -133,7 +135,7 @@ TEST(api_string_with_every_escape) { pjson j; j["s"] = s; - pjson::unique_ptr rt = pjson::parse(j.toString()); + pjson_test::Parsed rt = pjson_test::parse(j.toString()); CHECK(rt != nullptr); if (rt) { const pjson* field = rt->find("s"); @@ -156,7 +158,7 @@ TEST(api_empty_string_and_empty_key) { CHECK(view.empty()); } - pjson::unique_ptr rt = pjson::parse(j.toString()); + pjson_test::Parsed rt = pjson_test::parse(j.toString()); CHECK(rt != nullptr); if (rt) CHECK(*rt == j); @@ -309,19 +311,21 @@ TEST(api_find_haskey_on_non_object) { CHECK(num.isInt()); } -TEST(api_keys_sorted_and_empty) { +TEST(api_keys_complete_and_empty) { pjson j; j["z"] = static_cast(1); j["a"] = static_cast(2); j["m"] = static_cast(3); std::vector k = j.keys(); CHECK_EQ(k.size(), size_t(3)); - CHECK_EQ(k[0], std::string("a")); - CHECK_EQ(k[1], std::string("m")); - CHECK_EQ(k[2], std::string("z")); + for (size_t i = 0; i < k.size(); ++i) { + CHECK(j.hasKey(k[i])); + for (size_t other = i + 1; other < k.size(); ++other) + CHECK(k[i] != k[other]); + } - pjson::unique_ptr array = pjson::parse("[1,2]"); - pjson::unique_ptr scalar = pjson::parse("5"); + pjson_test::Parsed array = pjson_test::parse("[1,2]"); + pjson_test::Parsed scalar = pjson_test::parse("5"); CHECK(array != nullptr); CHECK(scalar != nullptr); if (array) @@ -331,13 +335,13 @@ TEST(api_keys_sorted_and_empty) { } TEST(api_size_empty_all_types) { - pjson::unique_ptr array = pjson::parse("[1,2,3]"); - pjson::unique_ptr object = pjson::parse(R"({"a":1,"b":2})"); - pjson::unique_ptr emptyArray = pjson::parse("[]"); - pjson::unique_ptr emptyObject = pjson::parse("{}"); - pjson::unique_ptr scalar = pjson::parse("5"); - pjson::unique_ptr string = pjson::parse("\"hello\""); - pjson::unique_ptr nullValue = pjson::parse("null"); + pjson_test::Parsed array = pjson_test::parse("[1,2,3]"); + pjson_test::Parsed object = pjson_test::parse(R"({"a":1,"b":2})"); + pjson_test::Parsed emptyArray = pjson_test::parse("[]"); + pjson_test::Parsed emptyObject = pjson_test::parse("{}"); + pjson_test::Parsed scalar = pjson_test::parse("5"); + pjson_test::Parsed string = pjson_test::parse("\"hello\""); + pjson_test::Parsed nullValue = pjson_test::parse("null"); CHECK(array != nullptr); CHECK(object != nullptr); CHECK(emptyArray != nullptr); @@ -379,15 +383,15 @@ TEST(api_serialization_forms_agree) { } TEST(api_pretty_reparses_to_same_data) { - pjson::unique_ptr value = - pjson::parse(R"({ "nested": { "arr": [1, 2, {"x": true}] }, "s": "v" })"); + pjson_test::Parsed value = + pjson_test::parse(R"({ "nested": { "arr": [1, 2, {"x": true}] }, "s": "v" })"); CHECK(value != nullptr); if (!value) return; pjson::SerializeOptions pretty = pjson::SerializeOptions::prettyPrinted(); std::string text = value->toString(pretty); - pjson::unique_ptr rt = pjson::parse(text); + pjson_test::Parsed rt = pjson_test::parse(text); CHECK(rt != nullptr); if (rt) CHECK(*rt == *value); @@ -398,7 +402,7 @@ TEST(api_pretty_reparses_to_same_data) { //===----------------------------------------------------------------------===// TEST(api_parse_stream_success_and_failure) { std::istringstream good(R"({ "k": [1,2,3] })"); - pjson::unique_ptr p = pjson::parseStream(good); + pjson_test::Parsed p = pjson_test::parseStream(good); CHECK(p != nullptr); if (p) { const pjson* field = p->find("k"); @@ -408,52 +412,52 @@ TEST(api_parse_stream_success_and_failure) { } std::istringstream bad("{not valid"); - pjson::ParseError err; - pjson::unique_ptr q = pjson::parseStream(bad, err); + pJsonParser::Error err; + pjson_test::Parsed q = pjson_test::parseStream(bad, err); CHECK(q == nullptr); CHECK(!err.ok); CHECK(!err.message.empty()); } TEST(api_parse_ptr_size_edges) { - pjson::unique_ptr p = pjson::parse("12345xyz", 3); + pjson_test::Parsed p = pjson_test::parse("12345xyz", 3); CHECK(p != nullptr); if (p) CHECK_EQ(mustGetInt(*p), int64_t(123)); const char raw[] = {'"', 'a', '\0', 'b', '"'}; - CHECK(pjson::parse(raw, sizeof(raw)) == nullptr); + CHECK(pjson_test::parse(raw, sizeof(raw)) == nullptr); - CHECK(pjson::parse(nullptr, 5) == nullptr); - CHECK(pjson::parse("x", 0) == nullptr); + CHECK(pjson_test::parse(nullptr, 5) == nullptr); + CHECK(pjson_test::parse("x", 0) == nullptr); } TEST(api_parse_resource_budgets) { - const pjson::ParseOptions defaults; + const pJsonParser::Options defaults; CHECK_EQ(defaults.maxDepth, 512); CHECK_EQ(defaults.maxNodes, size_t(1000000)); CHECK_EQ(defaults.maxInputBytes, size_t(64) * 1024U * 1024U); - CHECK_EQ(defaults.duplicateKeys, pjson::ParseOptions::RejectDuplicateKeys); + CHECK_EQ(defaults.duplicateKeys, pJsonParser::Options::RejectDuplicateKeys); - pjson::ParseOptions nodes; + pJsonParser::Options nodes; nodes.maxNodes = 3; - pjson::ParseError err; - CHECK(pjson::parse("[1,2]", err, nodes) != nullptr); + pJsonParser::Error err; + CHECK(pjson_test::parse("[1,2]", err, nodes) != nullptr); CHECK(err.ok); - CHECK(pjson::parse("[1,2,3]", err, nodes) == nullptr); + CHECK(pjson_test::parse("[1,2,3]", err, nodes) == nullptr); CHECK(!err.ok); CHECK(err.message.find("node budget") != std::string::npos); - pjson::ParseOptions bytes; + pJsonParser::Options bytes; bytes.maxInputBytes = 4; - CHECK(pjson::parse("null", err, bytes) != nullptr); - CHECK(pjson::parse("false", err, bytes) == nullptr); + CHECK(pjson_test::parse("null", err, bytes) != nullptr); + CHECK(pjson_test::parse("false", err, bytes) == nullptr); CHECK(!err.ok); CHECK(err.message.find("maxInputBytes") != std::string::npos); std::istringstream oversizedStream("false"); - CHECK(pjson::parseStream(oversizedStream, err, bytes) == nullptr); + CHECK(pjson_test::parseStream(oversizedStream, err, bytes) == nullptr); CHECK(!err.ok); CHECK_EQ(err.offset, size_t(4)); CHECK_EQ(err.line, size_t(1)); @@ -501,17 +505,17 @@ TEST(api_move_leaves_source_null) { // Equality: cross-type numeric cases, including above-2^53 exactness. //===----------------------------------------------------------------------===// TEST(api_equality_rules) { - CHECK(*pjson::parse("1") == *pjson::parse("1.0")); - CHECK(*pjson::parse("1.5") == *pjson::parse("1.5")); - CHECK(*pjson::parse("[1,2]") != *pjson::parse("[2,1]")); - CHECK(*pjson::parse(R"({"a":1,"b":2})") == *pjson::parse(R"({"b":2,"a":1})")); - CHECK(*pjson::parse("true") != *pjson::parse("1")); - CHECK(*pjson::parse("null") == *pjson::parse("null")); - CHECK(*pjson::parse("\"\"") != *pjson::parse("null")); - CHECK(*pjson::parse("{}") != *pjson::parse("[]")); - - CHECK(*pjson::parse("9007199254740994") == *pjson::parse("9007199254740994.0")); - CHECK(*pjson::parse("9007199254740993") != *pjson::parse("9007199254740992.0")); + CHECK(*pjson_test::parse("1") == *pjson_test::parse("1.0")); + CHECK(*pjson_test::parse("1.5") == *pjson_test::parse("1.5")); + CHECK(*pjson_test::parse("[1,2]") != *pjson_test::parse("[2,1]")); + CHECK(*pjson_test::parse(R"({"a":1,"b":2})") == *pjson_test::parse(R"({"b":2,"a":1})")); + CHECK(*pjson_test::parse("true") != *pjson_test::parse("1")); + CHECK(*pjson_test::parse("null") == *pjson_test::parse("null")); + CHECK(*pjson_test::parse("\"\"") != *pjson_test::parse("null")); + CHECK(*pjson_test::parse("{}") != *pjson_test::parse("[]")); + + CHECK(*pjson_test::parse("9007199254740994") == *pjson_test::parse("9007199254740994.0")); + CHECK(*pjson_test::parse("9007199254740993") != *pjson_test::parse("9007199254740992.0")); } //===----------------------------------------------------------------------===// @@ -598,15 +602,25 @@ TEST(api_extreme_builder_indexes_are_safe_and_preserve_state_on_failure) { CHECK(populatedThrew); CHECK_EQ(array.toString(), before); - array[INT_MIN] = int64_t(11); + bool negativeThrew = false; + try { + array[INT_MIN] = int64_t(11); + } catch (const std::out_of_range&) { + negativeThrew = true; + } + CHECK(negativeThrew); CHECK_EQ(array.size(), size_t(1)); - CHECK_EQ(mustGetInt(array[0]), int64_t(11)); + CHECK_EQ(mustGetInt(array[0]), int64_t(7)); pjson empty; - empty[INT_MIN] = int64_t(3); - CHECK(empty.isArray()); - CHECK_EQ(empty.size(), size_t(1)); - CHECK_EQ(mustGetInt(empty[0]), int64_t(3)); + bool emptyNegativeThrew = false; + try { + empty[INT_MIN] = int64_t(3); + } catch (const std::out_of_range&) { + emptyNegativeThrew = true; + } + CHECK(emptyNegativeThrew); + CHECK(empty.isNull()); } TEST(api_null_cstring_mutations_throw_and_preserve_prior_value) { diff --git a/pjsontest/src/tests_build.cpp b/pjsontest/src/tests_build.cpp index 32de88c..3e35ee9 100644 --- a/pjsontest/src/tests_build.cpp +++ b/pjsontest/src/tests_build.cpp @@ -19,6 +19,7 @@ #include "pjson.h" #include "test_harness.h" +#include #include #include @@ -213,21 +214,54 @@ TEST(negative_index_from_end) { expectInt(arr[-3], int64_t(10)); } -TEST(negative_index_past_start_clamps) { +TEST(negative_index_past_start_throws_without_mutation) { pjson arr; arr[0] = static_cast(10); arr[1] = static_cast(20); arr[2] = static_cast(30); - expectInt(arr[-4], int64_t(10)); - expectInt(arr[-100], int64_t(10)); + const std::string before = arr.toString(); + bool threw = false; + try { + (void)arr[-4]; + } catch (const std::out_of_range&) { + threw = true; + } + CHECK(threw); + CHECK_EQ(arr.toString(), before); } -TEST(negative_index_on_empty_array) { +TEST(negative_index_on_empty_array_throws_without_growth) { pjson arr; arr.resetTo(pjson::jsonArray); - pjson& element = arr[-1]; - CHECK_EQ(element.getType(), pjson::jsonNull); - CHECK_EQ(arr.size(), size_t(1)); + bool threw = false; + try { + (void)arr[-1]; + } catch (const std::out_of_range&) { + threw = true; + } + CHECK(threw); + CHECK(arr.empty()); +} + +TEST(negative_index_on_scalar_throws_without_type_change) { + pjson value; + value = int64_t(7); + bool threw = false; + try { + (void)value[-1]; + } catch (const std::out_of_range&) { + threw = true; + } + CHECK(threw); + CHECK(value.isInt()); +} + +TEST(size_t_index_supports_positive_builder_access) { + pjson arr; + const size_t index = 2; + arr[index] = int64_t(9); + CHECK_EQ(arr.size(), size_t(3)); + expectInt(arr[2], int64_t(9)); } TEST(find_returns_pointer_or_null) { @@ -321,7 +355,7 @@ TEST(find_index_is_non_mutating) { CHECK_EQ(j.size(), size_t(2)); } -TEST(keys_are_sorted_and_read_only_iteration_uses_find) { +TEST(keys_are_complete_and_read_only_iteration_uses_find) { pjson j; j["b"] = static_cast(2); j["a"] = static_cast(1); @@ -329,9 +363,11 @@ TEST(keys_are_sorted_and_read_only_iteration_uses_find) { const std::vector keys = j.keys(); CHECK_EQ(keys.size(), size_t(3)); - CHECK_EQ(keys[0], std::string("a")); - CHECK_EQ(keys[1], std::string("b")); - CHECK_EQ(keys[2], std::string("c")); + for (size_t i = 0; i < keys.size(); ++i) { + CHECK(j.hasKey(keys[i])); + for (size_t k = i + 1; k < keys.size(); ++k) + CHECK(keys[i] != keys[k]); + } int64_t sum = 0; for (size_t i = 0; i < keys.size(); ++i) { diff --git a/pjsontest/src/tests_conformance.cpp b/pjsontest/src/tests_conformance.cpp index 72c5a90..7f4f368 100644 --- a/pjsontest/src/tests_conformance.cpp +++ b/pjsontest/src/tests_conformance.cpp @@ -19,7 +19,9 @@ // configured or fetched locally // #include "pjson.h" +#include "pjson_parser.h" #include "test_harness.h" +#include "test_util.h" #include #include @@ -55,8 +57,8 @@ namespace { // Uses unbounded size/node budgets so the conformance corpus measures grammar rather than // deployment limits; the production recursion guard remains active for stack safety. - pjson::ParseOptions conformanceOptions() { - pjson::ParseOptions opts; + pJsonParser::Options conformanceOptions() { + pJsonParser::Options opts; // Keep the production recursion guard. Some implementation-defined // corpus files intentionally contain extreme nesting; they are skipped // below, while y_/n_ files remain bounded by the safe default. @@ -66,7 +68,7 @@ namespace { // RFC 8259 says object names SHOULD be unique but does not make // duplicates a grammar error. Use keep-last for the external syntax // corpus while the public default policy rejects duplicates. - opts.duplicateKeys = pjson::ParseOptions::KeepLastDuplicate; + opts.duplicateKeys = pJsonParser::Options::KeepLastDuplicate; return opts; } @@ -79,8 +81,8 @@ namespace { void expectConformanceParse(const Expectation& tc) { ::pjson_test::current().checks += 1; - pjson::ParseError err; - pjson::unique_ptr parsed = pjson::parse(tc.document, err, conformanceOptions()); + pJsonParser::Error err; + pjson_test::Parsed parsed = pjson_test::parse(tc.document, err, conformanceOptions()); if (tc.shouldParse) { if (parsed == nullptr) { @@ -235,8 +237,8 @@ namespace { const std::string payload = readFile(path); ::pjson_test::current().checks += 1; - pjson::ParseError err; - pjson::unique_ptr parsed = pjson::parse(payload, err, conformanceOptions()); + pJsonParser::Error err; + pjson_test::Parsed parsed = pjson_test::parse(payload, err, conformanceOptions()); if (shouldParse && parsed == nullptr) { std::ostringstream detail; @@ -412,11 +414,11 @@ TEST(conformance_json_test_suite_optional) { // every one and require deterministic behavior: if accepted, the // normalized output must itself be strict JSON and round-trip. const std::string payload = readFile(files[i]); - pjson::unique_ptr parsed = pjson::parse(payload, conformanceOptions()); + pjson_test::Parsed parsed = pjson_test::parse(payload, conformanceOptions()); CHECK(true); // parsing the corpus entry terminated safely if (parsed) { const std::string normalized = parsed->toString(); - CHECK(pjson::parse(normalized, conformanceOptions()) != nullptr); + CHECK(pjson_test::parse(normalized, conformanceOptions()) != nullptr); } implementationDefined += 1; continue; diff --git a/pjsontest/src/tests_core.cpp b/pjsontest/src/tests_core.cpp index ed06380..2ea79ac 100644 --- a/pjsontest/src/tests_core.cpp +++ b/pjsontest/src/tests_core.cpp @@ -248,7 +248,7 @@ TEST(copy_construct_is_deep_and_independent) { a["nested"]["deep"] = static_cast(9); pjson b(a); - CHECK_EQ(b.toString(), a.toString()); + CHECK(b == a); b["name"] = std::string("changed"); b["nested"]["deep"] = static_cast(100); @@ -264,7 +264,7 @@ TEST(copy_assign_is_deep) { pjson b; b = static_cast(12345); b = a; - CHECK_EQ(b.toString(), a.toString()); + CHECK(b == a); b["x"][0] = std::string("z"); expectString(a["x"][0], "p"); } @@ -314,13 +314,16 @@ TEST(copyfrom_deep_copies) { a["arr"] = std::vector({1.5, 2.5}); pjson b; b.copyFrom(a); - CHECK_EQ(b.toString(), a.toString()); + CHECK(b == a); b["arr"][0] = double(9.9); expectDouble(a["arr"][0], 1.5); } -TEST(unique_ptr_owns_ordinary_root_values) { - pjson::unique_ptr owned(new pjson()); +TEST(ordinary_new_root_frees_correctly) { + // A `new pjson()` root is freed correctly by a std::unique_ptr with the + // default deleter: an ordinary (non-allocator-owned) node destructs and + // returns its storage through operator delete. + std::unique_ptr owned(new pjson()); CHECK(owned != nullptr); (*owned)["value"] = static_cast(1); expectInt((*owned)["value"], int64_t(1)); diff --git a/pjsontest/src/tests_depth_frontends.cpp b/pjsontest/src/tests_depth_frontends.cpp new file mode 100644 index 0000000..17e0a47 --- /dev/null +++ b/pjsontest/src/tests_depth_frontends.cpp @@ -0,0 +1,172 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// PJSON-SEC-001 and PJSON-PARSE-001: an arbitrarily large configured maxDepth +// (up to INT_MAX) must not exhaust the native stack, and all parser front ends +// (string, byte span, DOM stream, buffered SAX, incremental SAX) must agree on +// acceptance and rejection for the same input and options. +// +#include "pjson.h" +#include "pjson_parser.h" +#include "test_harness.h" +#include "test_util.h" + +#include +#include +#include + +using namespace ByteDance; + +namespace { + + // Builds N nested arrays: "[[[...]]]" with matching closers. + std::string nestedArrays(size_t depth) { + return std::string(depth, '[') + std::string(depth, ']'); + } + + // Counts container-start events so we can compare SAX front ends. + struct CountingHandler : pJsonParser::SaxHandler { + size_t starts = 0; + bool onStartArray() override { + ++starts; + return true; + } + }; + +} // namespace + +//===----------------------------------------------------------------------===// +// A huge configured maxDepth is clamped to a stack-safe hard ceiling: extreme +// nesting returns a resource-limit error rather than overflowing the stack. +//===----------------------------------------------------------------------===// +TEST(depth_limit_intmax_is_clamped_and_safe) { + pJsonParser::Options opt; + opt.maxDepth = INT_MAX; // caller requests effectively unlimited depth + + // 100,000 levels is far beyond any safe native-recursion ceiling. With the + // clamp in place this must fail cleanly (empty result) instead of crashing. + const std::string doc = nestedArrays(100000); + pJsonParser::Error err; + pjson_test::Parsed p = pjson_test::parse(doc, err, opt); + CHECK(p == nullptr); + CHECK(!err.ok); +} + +//===----------------------------------------------------------------------===// +// The same clamp protects the SAX front end. +//===----------------------------------------------------------------------===// +TEST(depth_limit_intmax_is_clamped_for_sax) { + pJsonParser::Options opt; + opt.maxDepth = INT_MAX; + + const std::string doc = nestedArrays(100000); + CountingHandler handler; + pJsonParser::Error err; + const bool ok = pJsonParser(opt).parseSax(doc, handler, err); + CHECK(!ok); + CHECK(!err.ok); +} + +//===----------------------------------------------------------------------===// +// A streaming SAX parse over the same input is also protected. +//===----------------------------------------------------------------------===// +TEST(depth_limit_intmax_is_clamped_for_stream_sax) { + pJsonParser::Options opt; + opt.maxDepth = INT_MAX; + + const std::string doc = nestedArrays(100000); + std::istringstream in(doc); + CountingHandler handler; + pJsonParser::Error err; + const bool ok = pJsonParser(opt).parseSaxStream(in, handler, err); + CHECK(!ok); + CHECK(!err.ok); +} + +//===----------------------------------------------------------------------===// +// Front-end equivalence: string, byte span, DOM stream, buffered SAX, and +// streaming SAX must agree on accepting a representative document. +//===----------------------------------------------------------------------===// +TEST(parser_front_ends_agree_on_acceptance) { + const std::string doc = "{\"a\":[1,2,3],\"b\":{\"c\":true},\"n\":18446744073709551615}"; + + pjson_test::Parsed fromString = pjson_test::parse(doc); + pjson_test::Parsed fromSpan = pjson_test::parse(doc.data(), doc.size()); + std::istringstream in(doc); + pjson_test::Parsed fromStream = pjson_test::parseStream(in); + + CHECK(fromString != nullptr); + CHECK(fromSpan != nullptr); + CHECK(fromStream != nullptr); + if (fromString && fromSpan) + CHECK(*fromString == *fromSpan); + if (fromString && fromStream) + CHECK(*fromString == *fromStream); + + CountingHandler bufferHandler; + CHECK(pJsonParser().parseSax(doc, bufferHandler)); + std::istringstream saxStream(doc); + CountingHandler streamHandler; + CHECK(pJsonParser().parseSaxStream(saxStream, streamHandler)); + // Both SAX front ends see the same array/object structure. + CHECK_EQ(bufferHandler.starts, streamHandler.starts); +} + +//===----------------------------------------------------------------------===// +// Front-end equivalence on rejection: an out-of-range integer token is rejected +// by every front end under the default number policy. +//===----------------------------------------------------------------------===// +TEST(parser_front_ends_agree_on_rejection) { + const std::string doc = "18446744073709551616"; // UINT64_MAX + 1 + + CHECK(pjson_test::parse(doc) == nullptr); + CHECK(pjson_test::parse(doc.data(), doc.size()) == nullptr); + std::istringstream in(doc); + CHECK(pjson_test::parseStream(in) == nullptr); + + CountingHandler h1; + CHECK(!pJsonParser().parseSax(doc, h1)); + std::istringstream saxStream(doc); + CountingHandler h2; + CHECK(!pJsonParser().parseSaxStream(saxStream, h2)); +} + +TEST(parser_front_ends_agree_on_nonzero_underflow_policy) { + const std::string doc = "-1e-400"; + pJsonParser::Error error; + (void)pJsonParser().parse(doc, error); + CHECK(!error.ok); + CHECK_EQ(error.code, pJsonParser::Error::NumberRange); + CHECK(pjson_test::parse(doc.data(), doc.size()) == nullptr); + std::istringstream in(doc); + CHECK(pjson_test::parseStream(in) == nullptr); + + CountingHandler h1; + CHECK(!pJsonParser().parseSax(doc, h1)); + std::istringstream saxStream(doc); + CountingHandler h2; + CHECK(!pJsonParser().parseSaxStream(saxStream, h2)); + + pJsonParser::Options lossy; + lossy.numberPolicy = pJsonParser::Options::AllowLossyNumbers; + CHECK(pjson_test::parse(doc, lossy) != nullptr); + std::istringstream lossyStream(doc); + CHECK(pjson_test::parseStream(lossyStream, lossy) != nullptr); + CountingHandler h3; + CHECK(pJsonParser(lossy).parseSax(doc, h3)); + std::istringstream lossySaxStream(doc); + CountingHandler h4; + CHECK(pJsonParser(lossy).parseSaxStream(lossySaxStream, h4)); +} diff --git a/pjsontest/src/tests_dom_api.cpp b/pjsontest/src/tests_dom_api.cpp new file mode 100644 index 0000000..eda6740 --- /dev/null +++ b/pjsontest/src/tests_dom_api.cpp @@ -0,0 +1,351 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// PJSON-API-001/002/003: non-allocating traversal, construction/mutation +// primitives (factories, nullptr, pushBack, insertOrAssign, reserve), and safe +// checked access (at/contains) separated from vivifying operator[]. +// +#include "pjson.h" +#include "test_harness.h" + +#include +#include +#include +#include + +using namespace ByteDance; + +//===----------------------------------------------------------------------===// +// Ordinary C++ integer expressions must remain usable by the builder API. +// The explicit int64_t/uint64_t overload pair otherwise makes common literals +// ambiguous against bool and double, defeating the library's primary syntax. +//===----------------------------------------------------------------------===// +TEST(native_numeric_builder_types_are_unambiguous) { + pjson object; + const short signedShort = -2; + const unsigned short unsignedShort = 2; + const unsigned int unsignedInt = 3U; + const long signedLong = -4L; + const unsigned long unsignedLong = 5UL; + const long long signedLongLong = -6LL; + const unsigned long long unsignedLongLong = 7ULL; + + object["literal"] = 42; + object["signedShort"] = signedShort; + object["unsignedShort"] = unsignedShort; + object["unsignedInt"] = unsignedInt; + object["signedLong"] = signedLong; + object["unsignedLong"] = unsignedLong; + object["signedLongLong"] = signedLongLong; + object["unsignedLongLong"] = unsignedLongLong; + object["bool"] = true; + + int64_t signedValue = 0; + uint64_t unsignedValue = 0; + bool boolValue = false; + CHECK(object.tryGet("literal", signedValue)); + CHECK_EQ(signedValue, int64_t(42)); + CHECK(object.tryGet("signedShort", signedValue)); + CHECK_EQ(signedValue, int64_t(-2)); + CHECK(object.tryGet("unsignedShort", unsignedValue)); + CHECK_EQ(unsignedValue, uint64_t(2)); + CHECK(object.tryGet("unsignedInt", unsignedValue)); + CHECK_EQ(unsignedValue, uint64_t(3)); + CHECK(object.at("unsignedInt").isUInt()); + CHECK(object.tryGet("signedLong", signedValue)); + CHECK_EQ(signedValue, int64_t(-4)); + CHECK(object.tryGet("unsignedLong", unsignedValue)); + CHECK_EQ(unsignedValue, uint64_t(5)); + CHECK(object.tryGet("signedLongLong", signedValue)); + CHECK_EQ(signedValue, int64_t(-6)); + CHECK(object.tryGet("unsignedLongLong", unsignedValue)); + CHECK_EQ(unsignedValue, uint64_t(7)); + CHECK(object.tryGet("bool", boolValue)); + CHECK(boolValue); + + pjson array; + array += 7; + array += 8U; + array += 9.5F; + array += signedShort; + array += unsignedShort; + array += 10L; + array += 11UL; + array += 12LL; + array += 13ULL; + array += static_cast(14.5); + CHECK(array.at(0).isInt()); + CHECK(array.at(1).isUInt()); + CHECK(array.tryGet(0, signedValue)); + CHECK_EQ(signedValue, int64_t(7)); + CHECK(array.tryGet(1, unsignedValue)); + CHECK_EQ(unsignedValue, uint64_t(8)); + double floatingValue = 0.0; + CHECK(array.tryGet(2, floatingValue)); + CHECK_EQ(floatingValue, 9.5); + CHECK(array.at(3).isInt()); + CHECK(array.at(4).isUInt()); + CHECK(array.at(5).isInt()); + CHECK(array.at(6).isUInt()); + CHECK(array.at(7).isInt()); + CHECK(array.at(8).isUInt()); + CHECK(array.tryGet(9, floatingValue)); + CHECK_EQ(floatingValue, 14.5); + + pjson vectors; + vectors = std::vector({-1, 2}); + vectors += std::vector({3U, 4U}); + vectors += std::vector({-5}); + vectors += std::vector({6}); + vectors += std::vector({5L}); + vectors += std::vector({6UL}); + vectors += std::vector({7LL}); + vectors += std::vector({8ULL}); + CHECK_EQ(vectors.size(), size_t(10)); + CHECK(vectors.at(0).isInt()); + CHECK(vectors.at(2).isUInt()); + CHECK(vectors.tryGet(0, signedValue)); + CHECK_EQ(signedValue, int64_t(-1)); + CHECK(vectors.tryGet(3, unsignedValue)); + CHECK_EQ(unsignedValue, uint64_t(4)); + + pjson floats; + floats = std::vector({1.25F}); + floats += std::vector({2.5F}); + floats += std::vector({3.75L}); + CHECK(floats.tryGet(0, floatingValue)); + CHECK_EQ(floatingValue, 1.25); + CHECK(floats.tryGet(1, floatingValue)); + CHECK_EQ(floatingValue, 2.5); + CHECK(floats.tryGet(2, floatingValue)); + CHECK_EQ(floatingValue, 3.75); +} + +//===----------------------------------------------------------------------===// +// Factories build each JSON kind without relying on default construction. +//===----------------------------------------------------------------------===// +TEST(factories_build_each_kind) { + CHECK(pjson::null().isNull()); + CHECK(pjson::object().isObject()); + CHECK(pjson::object().empty()); + CHECK(pjson::array().isArray()); + CHECK(pjson::array().empty()); + + pjson v; + v = pjson::object(); + v = nullptr; // std::nullptr_t assignment resets to null + CHECK(v.isNull()); +} + +//===----------------------------------------------------------------------===// +// pushBack accepts arbitrary pjson values by copy and by move. +//===----------------------------------------------------------------------===// +TEST(pushback_copy_and_move) { + pjson child = pjson::object(); + child["k"] = int64_t(1); + + pjson arr; + arr.pushBack(child); // copy: child stays valid + CHECK(child.isObject()); + CHECK(arr.isArray()); + CHECK_EQ(arr.size(), size_t(1)); + + pjson moved = pjson::object(); + moved["k"] = int64_t(2); + arr.pushBack(std::move(moved)); // move + CHECK_EQ(arr.size(), size_t(2)); + + const pjson* first = arr.find(0); + const pjson* second = arr.find(1); + CHECK(first != nullptr); + CHECK(second != nullptr); + int64_t v = 0; + if (first) + CHECK(first->tryGet("k", v)); + CHECK_EQ(v, int64_t(1)); + if (second) + CHECK(second->tryGet("k", v)); + CHECK_EQ(v, int64_t(2)); +} + +//===----------------------------------------------------------------------===// +// insertOrAssign inserts a new member and replaces an existing one. +//===----------------------------------------------------------------------===// +TEST(insert_or_assign_semantics) { + pjson obj = pjson::object(); + pjson valueA; + valueA = std::string("a"); + obj.insertOrAssign("k", valueA); + std::string s; + CHECK(obj.tryGet("k", s)); + CHECK_EQ(s, std::string("a")); + + pjson valueB; + valueB = std::string("b"); + obj.insertOrAssign("k", std::move(valueB)); // replaces + CHECK(obj.tryGet("k", s)); + CHECK_EQ(s, std::string("b")); + CHECK_EQ(obj.size(), size_t(1)); +} + +//===----------------------------------------------------------------------===// +// reserve() promotes to an array and does not change logical size. +//===----------------------------------------------------------------------===// +TEST(reserve_promotes_and_preserves_size) { + pjson arr; + arr.reserve(128); + CHECK(arr.isArray()); + CHECK_EQ(arr.size(), size_t(0)); + arr.pushBack(pjson::null()); + CHECK_EQ(arr.size(), size_t(1)); +} + +//===----------------------------------------------------------------------===// +// at() is checked and non-vivifying; operator[] vivifies. +//===----------------------------------------------------------------------===// +TEST(checked_at_does_not_vivify) { + pjson obj = pjson::object(); + obj["present"] = int64_t(1); + + bool threw = false; + try { + (void)obj.at("absent"); + } catch (const std::out_of_range&) { + threw = true; + } + CHECK(threw); + // at() did not create the missing key. + CHECK(!obj.contains("absent")); + CHECK_EQ(obj.size(), size_t(1)); + + // Present key resolves. + int64_t v = 0; + CHECK(obj.at("present").tryGet(v)); + CHECK_EQ(v, int64_t(1)); +} + +//===----------------------------------------------------------------------===// +// at(index) is bounds-checked. +//===----------------------------------------------------------------------===// +TEST(checked_at_index_bounds) { + pjson arr; + arr.pushBack(int64_t(0) == 0 ? pjson::null() : pjson::null()); + arr += int64_t(10); + CHECK(arr.isArray()); + + bool threw = false; + try { + (void)arr.at(size_t(999)); + } catch (const std::out_of_range&) { + threw = true; + } + CHECK(threw); +} + +//===----------------------------------------------------------------------===// +// contains() mirrors hasKey(). +//===----------------------------------------------------------------------===// +TEST(contains_matches_haskey) { + pjson obj = pjson::object(); + obj["a"] = int64_t(1); + CHECK(obj.contains("a")); + CHECK(obj.contains(std::string("a"))); + CHECK(!obj.contains("b")); + CHECK_EQ(obj.contains("a"), obj.hasKey("a")); +} + +//===----------------------------------------------------------------------===// +// forEachMember visits every member in unspecified storage order. +//===----------------------------------------------------------------------===// +namespace { + struct MemberSum { + std::string keysConcat; + int64_t sum = 0; + }; + bool accumulateMember(pjson::StringView key, const pjson& value, void* ctx) { + MemberSum& state = *static_cast(ctx); + state.keysConcat.append(key.data(), key.size()); + int64_t v = 0; + value.tryGet(v); + state.sum += v; + return true; + } +} // namespace + +TEST(for_each_member_visits_all) { + pjson obj = pjson::object(); + obj["b"] = int64_t(2); + obj["a"] = int64_t(1); + obj["c"] = int64_t(3); + + MemberSum state; + const bool completed = obj.forEachMember(&accumulateMember, &state); + CHECK(completed); + std::sort(state.keysConcat.begin(), state.keysConcat.end()); + CHECK_EQ(state.keysConcat, std::string("abc")); + CHECK_EQ(state.sum, int64_t(6)); +} + +//===----------------------------------------------------------------------===// +// forEachElement visits array elements in order and supports early stop. +//===----------------------------------------------------------------------===// +namespace { + struct StopAtTwo { + int64_t visited = 0; + }; + bool countUntilTwo(const pjson& value, void* ctx) { + StopAtTwo& state = *static_cast(ctx); + int64_t v = 0; + value.tryGet(v); + ++state.visited; + return v < 2; // stop after visiting value 2 + } +} // namespace + +TEST(for_each_element_order_and_early_stop) { + pjson arr; + for (int64_t i = 0; i < 5; ++i) + arr += i; + + StopAtTwo state; + const bool completed = arr.forEachElement(&countUntilTwo, &state); + CHECK(!completed); // stopped early + CHECK_EQ(state.visited, int64_t(3)); // 0, 1, 2 +} + +//===----------------------------------------------------------------------===// +// Mutable forEachMember can edit values in place. +//===----------------------------------------------------------------------===// +namespace { + bool multiplyByTen(pjson::StringView, pjson& value, void*) { + int64_t v = 0; + if (value.tryGet(v)) + value = int64_t(v * 10); + return true; + } +} // namespace + +TEST(for_each_member_mutable_edit) { + pjson obj = pjson::object(); + obj["x"] = int64_t(1); + obj["y"] = int64_t(2); + + obj.forEachMember(&multiplyByTen, nullptr); + int64_t v = 0; + CHECK(obj.tryGet("x", v)); + CHECK_EQ(v, int64_t(10)); + CHECK(obj.tryGet("y", v)); + CHECK_EQ(v, int64_t(20)); +} diff --git a/pjsontest/src/tests_embedded_nul.cpp b/pjsontest/src/tests_embedded_nul.cpp new file mode 100644 index 0000000..1f305e4 --- /dev/null +++ b/pjsontest/src/tests_embedded_nul.cpp @@ -0,0 +1,175 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// PJSON-COR-001 regression matrix: object names must be preserved byte-for-byte, +// including embedded U+0000, through every length-aware std::string API. The +// const char* overloads intentionally keep NUL-terminated semantics. +// +#include "pjson.h" +#include "test_harness.h" +#include "test_util.h" + +#include +#include + +using namespace ByteDance; + +namespace { + + // Builds a std::string with an explicit embedded NUL so the byte length is + // preserved regardless of C-string truncation. + std::string nulKey(const char* aPrefix, const char* aSuffix) { + std::string key(aPrefix); + key.push_back('\0'); + key += aSuffix; + return key; + } + +} // namespace + +//===----------------------------------------------------------------------===// +// "a" and "a\u0000b" remain distinct through build, read, update, and erase. +//===----------------------------------------------------------------------===// +TEST(embedded_nul_keys_are_distinct) { + const std::string shortKey = "a"; + const std::string longKey = nulKey("a", "b"); // "a\0b", length 3 + + pjson root; + root[shortKey] = int64_t(1); + root[longKey] = int64_t(2); + + // Two distinct members, not one aliased through c_str(). + CHECK_EQ(root.size(), size_t(2)); + CHECK(root.hasKey(shortKey)); + CHECK(root.hasKey(longKey)); + + int64_t v = 0; + CHECK(root.tryGet(shortKey, v)); + CHECK_EQ(v, int64_t(1)); + CHECK(root.tryGet(longKey, v)); + CHECK_EQ(v, int64_t(2)); + + // Update each independently. + root[longKey] = int64_t(20); + CHECK(root.tryGet(shortKey, v)); + CHECK_EQ(v, int64_t(1)); + CHECK(root.tryGet(longKey, v)); + CHECK_EQ(v, int64_t(20)); + + // Erase the long key; the short key survives. + CHECK(root.erase(longKey)); + CHECK_EQ(root.size(), size_t(1)); + CHECK(root.hasKey(shortKey)); + CHECK(!root.hasKey(longKey)); + CHECK(root.find(longKey) == nullptr); +} + +//===----------------------------------------------------------------------===// +// Parsing an object with both "a" and "a\u0000b" preserves both members. +//===----------------------------------------------------------------------===// +TEST(embedded_nul_keys_round_trip_through_parse) { + // {"a":1,"a\u0000b":2} + const std::string doc = "{\"a\":1,\"a\\u0000b\":2}"; + pjson_test::Parsed parsed = pjson_test::parse(doc); + CHECK(parsed != nullptr); + if (!parsed) + return; + + CHECK_EQ(parsed->size(), size_t(2)); + + const std::string shortKey = "a"; + const std::string longKey = nulKey("a", "b"); + + int64_t v = 0; + CHECK(parsed->tryGet(shortKey, v)); + CHECK_EQ(v, int64_t(1)); + CHECK(parsed->tryGet(longKey, v)); + CHECK_EQ(v, int64_t(2)); + + // Serialization keeps both keys, so re-parsing recovers the same structure. + pjson_test::Parsed reparsed = pjson_test::parse(parsed->toString()); + CHECK(reparsed != nullptr); + if (reparsed) + CHECK(*reparsed == *parsed); +} + +//===----------------------------------------------------------------------===// +// Empty names and U+0000 at the beginning, middle, and end all stay distinct. +//===----------------------------------------------------------------------===// +TEST(embedded_nul_keys_position_matrix) { + std::vector keys; + keys.push_back(std::string()); // empty name + keys.push_back(nulKey("", "x")); // "\0x" (NUL at beginning) + keys.push_back(nulKey("x", "y")); // "x\0y" (NUL in middle) + keys.push_back(nulKey("z", "")); // "z\0" (NUL at end) + + pjson root; + for (size_t i = 0; i < keys.size(); ++i) + root[keys[i]] = int64_t(i); + + CHECK_EQ(root.size(), keys.size()); + for (size_t i = 0; i < keys.size(); ++i) { + int64_t v = -1; + CHECK(root.hasKey(keys[i])); + CHECK(root.tryGet(keys[i], v)); + CHECK_EQ(v, int64_t(i)); + } + + // Const find path resolves on full length too. + const pjson& cref = root; + const pjson* mid = cref.find(nulKey("x", "y")); + CHECK(mid != nullptr); +} + +//===----------------------------------------------------------------------===// +// JSON Pointer and equality preserve embedded-NUL names. +//===----------------------------------------------------------------------===// +TEST(embedded_nul_keys_pointer_and_equality) { + const std::string longKey = nulKey("a", "b"); + + pjson root; + root[longKey]["inner"] = int64_t(7); + + // Build the pointer with the escaping helper so the NUL byte survives. + const std::string pointer = "/" + pjson::escapePointerToken(longKey) + "/inner"; + const pjson* target = root.findPointer(pointer); + CHECK(target != nullptr); + int64_t v = 0; + if (target) + CHECK(target->tryGet(v)); + CHECK_EQ(v, int64_t(7)); + + // A deep copy keeps the key and compares equal. + pjson copy = root; + CHECK(copy == root); + CHECK(copy.hasKey(longKey)); +} + +//===----------------------------------------------------------------------===// +// The const char* overloads keep NUL-terminated behavior (documented contract). +//===----------------------------------------------------------------------===// +TEST(embedded_nul_cstring_overloads_truncate_by_contract) { + pjson root; + root[std::string("a")] = int64_t(1); + root[nulKey("a", "b")] = int64_t(2); + + // "a\0b" as a C string is seen as "a": the const char* lookup matches the + // short key, demonstrating the documented distinction from std::string. + const char* truncating = "a\0b"; // compiler treats as "a" + CHECK(root.hasKey(truncating)); + int64_t v = 0; + CHECK(root.tryGet(truncating, v)); + CHECK_EQ(v, int64_t(1)); +} diff --git a/pjsontest/src/tests_error_model.cpp b/pjsontest/src/tests_error_model.cpp new file mode 100644 index 0000000..9fb0068 --- /dev/null +++ b/pjsontest/src/tests_error_model.cpp @@ -0,0 +1,137 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// PJSON-API-005 and PJSON-PARSE-002: the structured pJsonParser::Error::Code categories +// and early, pre-allocation duplicate-key detection. +// +#include "pjson.h" +#include "pjson_parser.h" +#include "test_harness.h" +#include "test_util.h" + +#include +#include + +using namespace ByteDance; + +namespace { + + struct AcceptingSaxHandler : pJsonParser::SaxHandler {}; + + pJsonParser::Error::Code codeOf(const std::string& doc, + const pJsonParser::Options& opt = pJsonParser::Options()) { + pJsonParser::Error err; + pjson_test::parse(doc, err, opt); + return err.code; + } + +} // namespace + +//===----------------------------------------------------------------------===// +// Each failure class maps to its stable pJsonParser::Error::Code. +//===----------------------------------------------------------------------===// +TEST(error_codes_classify_failure_categories) { + CHECK_EQ(codeOf("[1,2,]"), pJsonParser::Error::Syntax); + CHECK_EQ(codeOf("\"\\uD800\""), pJsonParser::Error::InvalidEncoding); // lone surrogate + CHECK_EQ(codeOf("{\"a\":1,\"a\":2}"), pJsonParser::Error::DuplicateKey); + CHECK_EQ(codeOf("18446744073709551616"), pJsonParser::Error::NumberRange); // > UINT64_MAX + + pJsonParser::Options depth; + depth.maxDepth = 2; + CHECK_EQ(codeOf("[[[1]]]", depth), pJsonParser::Error::DepthLimit); + + pJsonParser::Options input; + input.maxInputBytes = 3; + CHECK_EQ(codeOf("[1, 2, 3]", input), pJsonParser::Error::InputLimit); + + pJsonParser::Options nodes; + nodes.maxNodes = 1; + CHECK_EQ(codeOf("[1, 2, 3]", nodes), pJsonParser::Error::NodeLimit); +} + +//===----------------------------------------------------------------------===// +// A successful parse leaves the success code and coordinates. +//===----------------------------------------------------------------------===// +TEST(error_code_success_state) { + pJsonParser::Error err; + pjson_test::Parsed p = pjson_test::parse("{\"a\":1}", err); + CHECK(p != nullptr); + CHECK(err.ok); + CHECK_EQ(err.code, pJsonParser::Error::None); + CHECK_EQ(err.line, size_t(1)); + CHECK_EQ(err.column, size_t(1)); +} + +//===----------------------------------------------------------------------===// +// A null input pointer is reported as an invalid-argument category. +//===----------------------------------------------------------------------===// +TEST(error_code_null_input_is_invalid_argument) { + pJsonParser::Error err; + pjson_test::parse(static_cast(nullptr), 5, err); + CHECK(!err.ok); + CHECK_EQ(err.code, pJsonParser::Error::InvalidArgument); + + AcceptingSaxHandler handler; + CHECK(!pJsonParser().parseSax(static_cast(nullptr), 5, handler, err)); + CHECK(!err.ok); + CHECK_EQ(err.code, pJsonParser::Error::InvalidArgument); +} + +//===----------------------------------------------------------------------===// +// PJSON-PARSE-002: a rejected duplicate is reported at the duplicate key's own +// offset, and its (potentially large) value subtree is never materialized. +//===----------------------------------------------------------------------===// +TEST(duplicate_key_reported_early_at_key_offset) { + // The duplicate "a" begins at byte offset 8: {"a":1,"a":[...]} + const std::string doc = "{\"a\":1,\"a\":[1,2,3,4,5,6,7,8,9,10]}"; + pJsonParser::Error err; + pjson_test::Parsed p = pjson_test::parse(doc, err); + CHECK(p == nullptr); + CHECK(!err.ok); + CHECK_EQ(err.code, pJsonParser::Error::DuplicateKey); + // Offset points at the opening quote of the second "a", before its value. + CHECK_EQ(err.offset, size_t(7)); +} + +//===----------------------------------------------------------------------===// +// Even a malformed duplicate value under keep-first is still validated (the +// duplicate value is grammar-checked, not silently skipped). +//===----------------------------------------------------------------------===// +TEST(duplicate_keep_first_still_validates_value) { + pJsonParser::Options keepFirst; + keepFirst.duplicateKeys = pJsonParser::Options::KeepFirstDuplicate; + // Second "a" has a malformed value; it must still fail. + pJsonParser::Error err; + pjson_test::Parsed p = pjson_test::parse("{\"a\":1,\"a\":}", err, keepFirst); + CHECK(p == nullptr); + CHECK(!err.ok); +} + +//===----------------------------------------------------------------------===// +// Embedded-NUL keys are compared on their full decoded bytes for duplicates. +//===----------------------------------------------------------------------===// +TEST(duplicate_key_uses_decoded_length_aware_names) { + // "a" and "a\u0000b" are distinct, so this is NOT a duplicate. + pjson_test::Parsed ok = pjson_test::parse("{\"a\":1,\"a\\u0000b\":2}"); + CHECK(ok != nullptr); + if (ok) + CHECK_EQ(ok->size(), size_t(2)); + + // Two identical embedded-NUL names ARE duplicates. + pJsonParser::Error err; + pjson_test::Parsed dup = pjson_test::parse("{\"a\\u0000b\":1,\"a\\u0000b\":2}", err); + CHECK(dup == nullptr); + CHECK_EQ(err.code, pJsonParser::Error::DuplicateKey); +} diff --git a/pjsontest/src/tests_features.cpp b/pjsontest/src/tests_features.cpp index fe7be85..b613595 100644 --- a/pjsontest/src/tests_features.cpp +++ b/pjsontest/src/tests_features.cpp @@ -14,13 +14,15 @@ // //===----------------------------------------------------------------------===// // Tests for higher-level library features on the settled surface: depth/resource -// guards, strict parse mode, pjson::unique_ptr ownership, equality, container +// guards, strict parse mode, pjson_test::Parsed ownership, equality, container // behavior, erase, and stream I/O. // #include "pjson.h" +#include "pjson_parser.h" #include "test_harness.h" #include "test_util.h" +#include #include #include #include @@ -52,11 +54,12 @@ namespace { // Library version. //===----------------------------------------------------------------------===// TEST(version_string) { - CHECK_EQ(std::string(pjson::getVersion()), std::string("1.0.0")); - CHECK_EQ(std::string(PJSON_VERSION), std::string("1.0.0")); - CHECK_EQ(PJSON_VERSION_MAJOR, 1); + CHECK_EQ(std::string(pjson::getVersion()), std::string("2.0.0")); + CHECK_EQ(std::string(PJSON_VERSION), std::string("2.0.0")); + CHECK_EQ(PJSON_VERSION_MAJOR, 2); CHECK_EQ(PJSON_VERSION_MINOR, 0); CHECK_EQ(PJSON_VERSION_PATCH, 0); + CHECK_EQ(PJSON_ABI_VERSION, 2); } //===----------------------------------------------------------------------===// @@ -69,7 +72,7 @@ TEST(depth_guard_rejects_deep_nesting) { const int depth = 100000; std::string s(depth, '['); s += std::string(depth, ']'); - CHECK(pjson::parse(s) == nullptr); + CHECK(pjson_test::parse(s) == nullptr); } TEST(depth_guard_allows_reasonable_nesting) { @@ -84,10 +87,10 @@ TEST(depth_guard_allows_reasonable_nesting) { TEST(depth_guard_boundary_is_configurable) { // maxDepth counts array/object frames. With maxDepth = 3, three nested // arrays are OK but four are not. - pjson::ParseOptions opt; + pJsonParser::Options opt; opt.maxDepth = 3; - CHECK(pjson::parse("[[[1]]]", opt) != nullptr); - CHECK(pjson::parse("[[[[1]]]]", opt) == nullptr); + CHECK(pjson_test::parse("[[[1]]]", opt) != nullptr); + CHECK(pjson_test::parse("[[[[1]]]]", opt) == nullptr); } //===----------------------------------------------------------------------===// @@ -101,11 +104,21 @@ TEST(number_overflow_rejected) { } TEST(number_underflow_is_zero) { - // Underflow to 0.0 is fine and finite. - auto p = parse("1e-400"); - CHECK(p != nullptr); - if (p) - CHECK_EQ(mustGetDouble(*p), 0.0); + // A nonzero token rounded to zero is rejected unless lossy conversion was requested. + CHECK(parse("1e-400") == nullptr); + CHECK(parse("-1e-400") == nullptr); + pJsonParser::Options lossy; + lossy.numberPolicy = pJsonParser::Options::AllowLossyNumbers; + auto positive = pjson_test::parse("1e-400", lossy); + auto negative = pjson_test::parse("-1e-400", lossy); + CHECK(positive != nullptr); + CHECK(negative != nullptr); + if (positive) + CHECK_EQ(mustGetDouble(*positive), 0.0); + if (negative) { + CHECK_EQ(mustGetDouble(*negative), 0.0); + CHECK(std::signbit(mustGetDouble(*negative))); + } } TEST(huge_but_finite_number_ok) { @@ -118,39 +131,39 @@ TEST(huge_but_finite_number_ok) { //===----------------------------------------------------------------------===// TEST(strict_rejects_raw_control_char) { const char raw[] = {'"', 'a', '\n', 'b', '"'}; - CHECK(pjson::parse(raw, sizeof(raw)) == nullptr); + CHECK(pjson_test::parse(raw, sizeof(raw)) == nullptr); } TEST(strict_rejects_unknown_escape) { - CHECK(pjson::parse("\"a\\qb\"") == nullptr); + CHECK(pjson_test::parse("\"a\\qb\"") == nullptr); } TEST(strict_rejects_lone_surrogate) { - CHECK(pjson::parse("\"\\uD800\"") == nullptr); + CHECK(pjson_test::parse("\"\\uD800\"") == nullptr); } TEST(strict_accepts_valid_surrogate_pair) { - CHECK(pjson::parse("\"\\uD83D\\uDE00\"") != nullptr); + CHECK(pjson_test::parse("\"\\uD83D\\uDE00\"") != nullptr); } TEST(strict_rejects_uppercase_keywords) { - CHECK(pjson::parse("NULL") == nullptr); - CHECK(pjson::parse("True") == nullptr); - CHECK(pjson::parse("null") != nullptr); - CHECK(pjson::parse("true") != nullptr); - CHECK(pjson::parse("false") != nullptr); + CHECK(pjson_test::parse("NULL") == nullptr); + CHECK(pjson_test::parse("True") == nullptr); + CHECK(pjson_test::parse("null") != nullptr); + CHECK(pjson_test::parse("true") != nullptr); + CHECK(pjson_test::parse("false") != nullptr); } TEST(strict_rejects_invalid_utf8) { // 0xFF is never valid UTF-8. const char bad[] = {'"', static_cast(0xFF), '"'}; - CHECK(pjson::parse(bad, sizeof(bad)) == nullptr); + CHECK(pjson_test::parse(bad, sizeof(bad)) == nullptr); } TEST(strict_accepts_valid_utf8) { // "é" as UTF-8 (0xC3 0xA9) between quotes. const char good[] = {'"', static_cast(0xC3), static_cast(0xA9), '"'}; - auto p = pjson::parse(good, sizeof(good)); + auto p = pjson_test::parse(good, sizeof(good)); CHECK(p != nullptr); if (!p) return; @@ -160,7 +173,7 @@ TEST(strict_accepts_valid_utf8) { } TEST(strict_still_parses_normal_documents) { - auto p = pjson::parse(R"({ "a": 1, "b": [true, null, "x"] })"); + auto p = pjson_test::parse(R"({ "a": 1, "b": [true, null, "x"] })"); CHECK(p != nullptr); if (!p) return; @@ -175,10 +188,10 @@ TEST(strict_still_parses_normal_documents) { } //===----------------------------------------------------------------------===// -// Ownership-safe parse API returning a unique_ptr. +// Value-returning parse API with pJsonParser::Error-based success detection. //===----------------------------------------------------------------------===// -TEST(parse_returns_unique_ptr) { - pjson::unique_ptr p = pjson::parse(R"({"k":42})"); +TEST(parse_returns_value) { + pjson_test::Parsed p = pjson_test::parse(R"({"k":42})"); CHECK(static_cast(p)); if (p) { const pjson* value = p->find("k"); @@ -187,13 +200,13 @@ TEST(parse_returns_unique_ptr) { CHECK_EQ(mustGetInt(*value), int64_t(42)); } - pjson::unique_ptr bad = pjson::parse("{not json"); - CHECK(!bad); // empty on failure + pjson_test::Parsed bad = pjson_test::parse("{not json"); + CHECK(!bad); // reports failure via pJsonParser::Error } TEST(parse_ptr_size_overload) { const char* src = "123456"; - auto p = pjson::parse(src, 3); // only "123" + auto p = pjson_test::parse(src, 3); // only "123" CHECK(static_cast(p)); if (p) CHECK_EQ(mustGetInt(*p), int64_t(123)); @@ -203,8 +216,8 @@ TEST(parse_ptr_size_overload) { // Parse errors expose a byte offset plus one-based line/byte-column coordinates. //===----------------------------------------------------------------------===// TEST(parse_error_reports_success) { - pjson::ParseError err; - auto p = pjson::parse(R"({"a":1})", err); + pJsonParser::Error err; + auto p = pjson_test::parse(R"({"a":1})", err); CHECK(static_cast(p)); CHECK(err.ok); CHECK_EQ(err.line, size_t(1)); @@ -212,8 +225,8 @@ TEST(parse_error_reports_success) { } TEST(parse_error_reports_offset_and_message) { - pjson::ParseError err; - auto p = pjson::parse("[1, 2, ]", err); // trailing comma at index 7 + pJsonParser::Error err; + auto p = pjson_test::parse("[1, 2, ]", err); // trailing comma at index 7 CHECK(!p); CHECK(!err.ok); CHECK(!err.message.empty()); @@ -223,26 +236,26 @@ TEST(parse_error_reports_offset_and_message) { } TEST(parse_error_reports_line_and_column) { - pjson::ParseError err; - CHECK(!pjson::parse("{\r\n \"a\": 1,\r\n \"b\": [2, ]\r\n}", err)); + pJsonParser::Error err; + CHECK(!pjson_test::parse("{\r\n \"a\": 1,\r\n \"b\": [2, ]\r\n}", err)); CHECK_EQ(err.line, size_t(3)); CHECK_EQ(err.column, size_t(12)); CHECK_EQ(err.offset, size_t(25)); - CHECK(!pjson::parse("1\r\n2", err)); + CHECK(!pjson_test::parse("1\r\n2", err)); CHECK_EQ(err.offset, size_t(3)); CHECK_EQ(err.line, size_t(2)); CHECK_EQ(err.column, size_t(1)); - CHECK(!pjson::parse("1\r2", err)); + CHECK(!pjson_test::parse("1\r2", err)); CHECK_EQ(err.offset, size_t(2)); CHECK_EQ(err.line, size_t(2)); CHECK_EQ(err.column, size_t(1)); } TEST(parse_error_trailing_garbage) { - pjson::ParseError err; - auto p = pjson::parse("42 abc", err); + pJsonParser::Error err; + auto p = pjson_test::parse("42 abc", err); CHECK(!p); CHECK(!err.ok); CHECK_EQ(err.offset, size_t(3)); // 'a' @@ -251,10 +264,10 @@ TEST(parse_error_trailing_garbage) { } TEST(parse_error_depth_message) { - pjson::ParseOptions opt; + pJsonParser::Options opt; opt.maxDepth = 2; - pjson::ParseError err; - auto p = pjson::parse("[[[1]]]", err, opt); + pJsonParser::Error err; + auto p = pjson_test::parse("[[[1]]]", err, opt); CHECK(!p); CHECK(!err.ok); CHECK(err.message.find("depth") != std::string::npos); @@ -423,16 +436,18 @@ TEST(erase_wrong_type_is_false) { //===----------------------------------------------------------------------===// // Listing object keys for iteration. //===----------------------------------------------------------------------===// -TEST(keys_returns_sorted_keys) { +TEST(keys_returns_each_object_key) { pjson j; j["gamma"] = static_cast(1); j["alpha"] = static_cast(2); j["beta"] = static_cast(3); std::vector k = j.keys(); CHECK_EQ(k.size(), size_t(3)); - CHECK_EQ(k[0], std::string("alpha")); - CHECK_EQ(k[1], std::string("beta")); - CHECK_EQ(k[2], std::string("gamma")); + for (size_t i = 0; i < k.size(); ++i) { + CHECK(j.hasKey(k[i])); + for (size_t other = i + 1; other < k.size(); ++other) + CHECK(k[i] != k[other]); + } pjson notMap; notMap = static_cast(5); @@ -492,7 +507,7 @@ TEST(write_to_stream) { TEST(parse_from_stream) { std::istringstream is(R"({ "name": "Ada", "scores": [90, 82] })"); - auto p = pjson::parseStream(is); + auto p = pjson_test::parseStream(is); CHECK(static_cast(p)); if (!p) return; @@ -508,8 +523,8 @@ TEST(parse_from_stream) { TEST(parse_from_stream_with_error) { std::istringstream is("{bad"); - pjson::ParseError err; - auto p = pjson::parseStream(is, err); + pJsonParser::Error err; + auto p = pjson_test::parseStream(is, err); CHECK(!p); CHECK(!err.ok); } @@ -521,7 +536,7 @@ TEST(stream_round_trip) { std::ostringstream os; j.write(os); std::istringstream is(os.str()); - auto rt = pjson::parseStream(is); + auto rt = pjson_test::parseStream(is); CHECK(static_cast(rt)); CHECK(*rt == j); } diff --git a/pjsontest/src/tests_fuzz.cpp b/pjsontest/src/tests_fuzz.cpp index 6808071..8a3f6a9 100644 --- a/pjsontest/src/tests_fuzz.cpp +++ b/pjsontest/src/tests_fuzz.cpp @@ -89,8 +89,8 @@ namespace { } // namespace //===----------------------------------------------------------------------===// -// Random valid documents survive serialize -> parse -> serialize unchanged, -// in both compact and pretty form, and equal themselves after a round-trip. +// Random valid documents preserve their value across compact and pretty +// serialization round-trips. Object member byte order is unspecified. //===----------------------------------------------------------------------===// TEST(fuzz_valid_document_round_trip) { std::mt19937 rng(0xABCDEF01u); @@ -101,16 +101,14 @@ TEST(fuzz_valid_document_round_trip) { std::string compact = doc.toString(); auto rc = parse(compact); CHECK(rc != nullptr); - if (rc) { - CHECK_EQ(rc->toString(), compact); + if (rc) CHECK(*rc == doc); // structural equality holds - } std::string pretty = doc.toString(pjson::SerializeOptions::prettyPrinted()); auto rp = parse(pretty); CHECK(rp != nullptr); if (rp) - CHECK_EQ(rp->toString(), compact); + CHECK(*rp == doc); } } @@ -133,7 +131,7 @@ TEST(fuzz_random_bytes_parser) { auto p2 = parse(out); CHECK(p2 != nullptr); if (p2) - CHECK_EQ(p2->toString(), out); + CHECK(*p2 == *p); } } CHECK(true); // reaching here means no crash across all iterations @@ -238,12 +236,12 @@ TEST(fuzz_schema_validation_never_crashes) { pjson doc; buildRandom(doc, rng, 3); - std::vector errors; - bool ok = doc.validate(*schema, errors); + std::vector errors; + bool ok = pjson_test::schemaValidate(doc, *schema, errors); // The contract: ok == errors.empty(). Also validate() (no errors arg) // must agree with the collecting form. CHECK_EQ(ok, errors.empty()); - CHECK_EQ(ok, doc.validate(*schema)); + CHECK_EQ(ok, pjson_test::schemaValidate(doc, *schema)); } } @@ -264,16 +262,16 @@ TEST(fuzz_malformed_schemas_tolerated) { const char* values[] = {"5", "\"str\"", "[1,2,3]", R"({"k0":1})", "true", "null"}; for (const char* v : values) { auto d = parse(v); - std::vector errors; - CHECK(d->validate(*schema, errors)); + std::vector errors; + CHECK(pjson_test::schemaValidate(*d, *schema, errors)); CHECK(errors.empty()); } } auto invalidRegex = parse(R"({"pattern":"([unclosed"})"); auto stringValue = parse("\"value\""); - std::vector errors; - CHECK(!stringValue->validate(*invalidRegex, errors)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(*stringValue, *invalidRegex, errors)); CHECK(!errors.empty()); } @@ -295,7 +293,7 @@ TEST(fuzz_copy_move_independence) { std::string before = a.toString(); pjson d(std::move(c)); // move ctor - CHECK_EQ(d.toString(), before); + CHECK(d == a); CHECK(c.isNull()); // moved-from is null // Mutating the copy must not disturb the original. diff --git a/pjsontest/src/tests_malformed.cpp b/pjsontest/src/tests_malformed.cpp index fbe93de..7a7e0d5 100644 --- a/pjsontest/src/tests_malformed.cpp +++ b/pjsontest/src/tests_malformed.cpp @@ -17,6 +17,7 @@ // without throwing and (where meaningful) report a sensible error offset. // #include "pjson.h" +#include "pjson_parser.h" #include "test_harness.h" #include "test_util.h" @@ -35,7 +36,7 @@ TEST(malformed_empty_inputs) { CHECK_PARSE_FAILS("\t\n\r "); CHECK_PARSE_FAILS("\r\n"); // A lone NUL byte is not a value. - CHECK(pjson::parse(std::string("\0", 1)) == nullptr); + CHECK(pjson_test::parse(std::string("\0", 1)) == nullptr); } //===----------------------------------------------------------------------===// @@ -130,7 +131,7 @@ TEST(malformed_number_out_of_range) { // A 400-digit integer overflows int64, falls back to double, overflows // that too, and is rejected. std::string huge(400, '9'); - CHECK(pjson::parse(huge) == nullptr); + CHECK(pjson_test::parse(huge) == nullptr); // Just inside range is fine. CHECK(parse("1e308") != nullptr); } @@ -185,7 +186,7 @@ TEST(malformed_keywords) { //===----------------------------------------------------------------------===// TEST(malformed_bom_prefix) { const char bom[] = {(char)0xEF, (char)0xBB, (char)0xBF, '1'}; - CHECK(pjson::parse(bom, sizeof(bom)) == nullptr); + CHECK(pjson_test::parse(bom, sizeof(bom)) == nullptr); } //===----------------------------------------------------------------------===// @@ -195,7 +196,7 @@ TEST(malformed_excessive_depth_arrays) { const int depth = 200000; std::string s(depth, '['); s += std::string(depth, ']'); - CHECK(pjson::parse(s) == nullptr); // default maxDepth guards this + CHECK(pjson_test::parse(s) == nullptr); // default maxDepth guards this } TEST(malformed_excessive_depth_objects) { @@ -207,7 +208,7 @@ TEST(malformed_excessive_depth_objects) { s += "1"; for (int i = 0; i < depth; ++i) s += "}"; - CHECK(pjson::parse(s) == nullptr); + CHECK(pjson_test::parse(s) == nullptr); } //===----------------------------------------------------------------------===// @@ -217,36 +218,36 @@ TEST(malformed_excessive_depth_objects) { TEST(malformed_control_chars) { const char rawNL[] = {'"', 'a', '\n', 'b', '"'}; const char rawTab[] = {'"', '\t', '"'}; - CHECK(pjson::parse(rawNL, sizeof(rawNL)) == nullptr); - CHECK(pjson::parse(rawTab, sizeof(rawTab)) == nullptr); + CHECK(pjson_test::parse(rawNL, sizeof(rawNL)) == nullptr); + CHECK(pjson_test::parse(rawTab, sizeof(rawTab)) == nullptr); } TEST(malformed_bad_escapes_and_surrogates) { - CHECK(pjson::parse("\"\\x41\"") == nullptr); - CHECK(pjson::parse("\"\\uD800\"") == nullptr); // lone high surrogate - CHECK(pjson::parse("\"\\uDC00\"") == nullptr); // lone low surrogate + CHECK(pjson_test::parse("\"\\x41\"") == nullptr); + CHECK(pjson_test::parse("\"\\uD800\"") == nullptr); // lone high surrogate + CHECK(pjson_test::parse("\"\\uDC00\"") == nullptr); // lone low surrogate } TEST(malformed_invalid_utf8) { const char bad[] = {'"', (char)0xC3, (char)0x28, '"'}; // 0xC3 not followed by continuation const char lone[] = {'"', (char)0xFF, '"'}; - CHECK(pjson::parse(bad, sizeof(bad)) == nullptr); - CHECK(pjson::parse(lone, sizeof(lone)) == nullptr); + CHECK(pjson_test::parse(bad, sizeof(bad)) == nullptr); + CHECK(pjson_test::parse(lone, sizeof(lone)) == nullptr); } //===----------------------------------------------------------------------===// // Error offsets point at the offending byte. //===----------------------------------------------------------------------===// TEST(malformed_error_offsets) { - pjson::ParseError err; + pJsonParser::Error err; - CHECK(!pjson::parse("[1, 2, ]", err)); + CHECK(!pjson_test::parse("[1, 2, ]", err)); CHECK_EQ(err.offset, size_t(7)); // the ']' after a trailing comma - pjson::parse(" @", err); + pjson_test::parse(" @", err); CHECK_EQ(err.offset, size_t(3)); // first non-ws garbage - pjson::parse("{\"a\":1 \"b\":2}", err); + pjson_test::parse("{\"a\":1 \"b\":2}", err); CHECK(!err.ok); CHECK(!err.message.empty()); } @@ -277,8 +278,8 @@ TEST(malformed_partial_tree_teardown_is_leak_free) { R"([{"a":1},{"b":2},{"c":3},])", }; for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { - pjson::ParseError err; - CHECK(pjson::parse(cases[i], err) == nullptr); + pJsonParser::Error err; + CHECK(pjson_test::parse(cases[i], err) == nullptr); CHECK(!err.ok); } } diff --git a/pjsontest/src/tests_mutation.cpp b/pjsontest/src/tests_mutation.cpp index 39e84c9..da20d1b 100644 --- a/pjsontest/src/tests_mutation.cpp +++ b/pjsontest/src/tests_mutation.cpp @@ -19,6 +19,8 @@ // #include "pjson.h" #include "test_harness.h" +#include "test_util.h" +#include #include #include @@ -62,7 +64,7 @@ TEST(mutate_build_deep_mixed_tree) { expectString(j["user"]["history"][1]["action"], "logout"); // The whole thing round-trips. - pjson::unique_ptr rt = pjson::parse(j.toString()); + pjson_test::Parsed rt = pjson_test::parse(j.toString()); CHECK(rt != nullptr); CHECK(*rt == j); } @@ -204,7 +206,8 @@ TEST(mutate_clear_and_rebuild) { // Editing a parsed document in place, then re-serializing. //===----------------------------------------------------------------------===// TEST(mutate_edit_parsed_document) { - pjson::unique_ptr p = pjson::parse(R"({ "list":[10,20,30], "meta":{"v":1}, "drop":true })"); + pjson_test::Parsed p = + pjson_test::parse(R"({ "list":[10,20,30], "meta":{"v":1}, "drop":true })"); CHECK(p != nullptr); pjson& j = *p; @@ -221,7 +224,7 @@ TEST(mutate_edit_parsed_document) { CHECK(!j.hasKey("drop")); // Still valid JSON after all the edits. - pjson::unique_ptr rt = pjson::parse(j.toString()); + pjson_test::Parsed rt = pjson_test::parse(j.toString()); CHECK(rt != nullptr); CHECK(*rt == j); } @@ -236,9 +239,16 @@ TEST(mutate_negative_index_edits) { j[-3] = static_cast(10); // first expectInt(j[0], int64_t(10)); expectInt(j[2], int64_t(30)); - // Out-of-range negative clamps to the front element. - j[-10] = static_cast(0); - expectInt(j[0], int64_t(0)); + // Out-of-range negative mutation is rejected and preserves the array. + const std::string before = j.toString(); + bool threw = false; + try { + j[-10] = static_cast(0); + } catch (const std::out_of_range&) { + threw = true; + } + CHECK(threw); + CHECK_EQ(j.toString(), before); } //===----------------------------------------------------------------------===// @@ -416,7 +426,7 @@ TEST(mutate_full_lifecycle) { CHECK_EQ(doc.size(), size_t(1)); // Everything still serializes and round-trips. - pjson::unique_ptr rt = pjson::parse(doc.toString()); + pjson_test::Parsed rt = pjson_test::parse(doc.toString()); CHECK(rt != nullptr); CHECK(*rt == doc); } diff --git a/pjsontest/src/tests_numbers.cpp b/pjsontest/src/tests_numbers.cpp new file mode 100644 index 0000000..6d3f614 --- /dev/null +++ b/pjsontest/src/tests_numbers.cpp @@ -0,0 +1,227 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// PJSON-NUM-001/002/003: exact unsigned-integer support, the unrepresentable +// number policy, and explicit non-finite floating-point handling. +// +#include "pjson.h" +#include "pjson_parser.h" +#include "test_harness.h" +#include "test_util.h" + +#include +#include +#include +#include +#include + +using namespace ByteDance; + +//===----------------------------------------------------------------------===// +// UINT64_MAX round-trips exactly instead of collapsing to a double. +//===----------------------------------------------------------------------===// +TEST(uint_max_round_trips_exactly) { + const std::string doc = "18446744073709551615"; // UINT64_MAX + pjson_test::Parsed p = pjson_test::parse(doc); + CHECK(p != nullptr); + if (!p) + return; + CHECK(p->isUInt()); + CHECK(p->isInteger()); + CHECK(!p->isInt()); + uint64_t v = 0; + CHECK(p->tryGet(v)); + CHECK_EQ(v, std::numeric_limits::max()); + // Exact decimal serialization, not 1.8446744073709552e+19. + CHECK_EQ(p->toString(), doc); +} + +//===----------------------------------------------------------------------===// +// The signed/unsigned boundary is classified exactly. +//===----------------------------------------------------------------------===// +TEST(int_uint_boundary_classification) { + // INT64_MAX stays signed. + pjson_test::Parsed maxSigned = pjson_test::parse("9223372036854775807"); + CHECK(maxSigned && maxSigned->isInt()); + + // INT64_MAX + 1 becomes unsigned. + pjson_test::Parsed firstUnsigned = pjson_test::parse("9223372036854775808"); + CHECK(firstUnsigned != nullptr); + if (firstUnsigned) { + CHECK(firstUnsigned->isUInt()); + uint64_t v = 0; + CHECK(firstUnsigned->tryGet(v)); + CHECK_EQ(v, uint64_t(9223372036854775808ULL)); + // It does not fit int64_t, so a signed read fails cleanly. + int64_t s = -1; + CHECK(!firstUnsigned->tryGet(s)); + CHECK_EQ(s, int64_t(-1)); + } +} + +//===----------------------------------------------------------------------===// +// Explicit uint64_t assignment keeps unsigned identity even for small values. +//===----------------------------------------------------------------------===// +TEST(uint_assignment_keeps_identity) { + pjson v; + v = uint64_t(7); + CHECK(v.isUInt()); + CHECK(!v.isInt()); + // Cross-representation comparison is still exact: 7u == 7 == 7.0. + pjson signedSeven; + signedSeven = int64_t(7); + CHECK(v == signedSeven); + pjson doubleSeven; + doubleSeven = double(7.0); + CHECK(v == doubleSeven); +} + +//===----------------------------------------------------------------------===// +// tryGet(uint64_t&) accepts a non-negative signed value; rejects a negative one. +//===----------------------------------------------------------------------===// +TEST(uint_tryget_from_signed) { + pjson pos; + pos = int64_t(42); + uint64_t u = 0; + CHECK(pos.tryGet(u)); + CHECK_EQ(u, uint64_t(42)); + + pjson neg; + neg = int64_t(-1); + u = 999; + CHECK(!neg.tryGet(u)); + CHECK_EQ(u, uint64_t(999)); // unchanged on failure +} + +//===----------------------------------------------------------------------===// +// Unsigned vector assignment and append build unsigned children. +//===----------------------------------------------------------------------===// +TEST(uint_vector_assignment_and_append) { + std::vector values; + values.push_back(1); + values.push_back(std::numeric_limits::max()); + + pjson arr; + arr = values; + CHECK(arr.isArray()); + CHECK_EQ(arr.size(), size_t(2)); + const pjson* second = arr.find(1); + CHECK(second != nullptr); + if (second) { + CHECK(second->isUInt()); + uint64_t v = 0; + CHECK(second->tryGet(v)); + CHECK_EQ(v, std::numeric_limits::max()); + } + + pjson appended; + appended += uint64_t(5); + CHECK(appended.isArray()); + CHECK_EQ(appended.size(), size_t(1)); + const pjson* elem = appended.find(0); + CHECK(elem && elem->isUInt()); +} + +//===----------------------------------------------------------------------===// +// Values above UINT64_MAX are rejected by default and opt-in under lossy policy. +//===----------------------------------------------------------------------===// +TEST(number_above_uint64_policy) { + const std::string doc = "18446744073709551616"; // UINT64_MAX + 1 + pJsonParser::Error err; + pjson_test::Parsed rejected = pjson_test::parse(doc, err); + CHECK(rejected == nullptr); + CHECK(!err.ok); + + pJsonParser::Options lossy; + lossy.numberPolicy = pJsonParser::Options::AllowLossyNumbers; + pjson_test::Parsed allowed = pjson_test::parse(doc, lossy); + CHECK(allowed != nullptr); + if (allowed) + CHECK(allowed->isDouble()); +} + +//===----------------------------------------------------------------------===// +// Non-finite doubles: default serialization fails; opt-in policies map them. +//===----------------------------------------------------------------------===// +TEST(non_finite_serialization_policy) { + pjson nan; + nan = std::numeric_limits::quiet_NaN(); + + // Default: RejectNonFinite -> toString throws. + bool threw = false; + try { + (void)nan.toString(); + } catch (const std::invalid_argument&) { + threw = true; + } + CHECK(threw); + + // NonFiniteToNull emits null (legacy behavior, now explicit). + pjson::SerializeOptions toNull; + toNull.nonFinite = pjson::SerializeOptions::NonFiniteToNull; + CHECK_EQ(nan.toString(toNull), std::string("null")); + + // NonFiniteToString emits sentinel strings. + pjson posInf; + posInf = std::numeric_limits::infinity(); + pjson negInf; + negInf = -std::numeric_limits::infinity(); + pjson::SerializeOptions toStr; + toStr.nonFinite = pjson::SerializeOptions::NonFiniteToString; + CHECK_EQ(posInf.toString(toStr), std::string("\"Infinity\"")); + CHECK_EQ(negInf.toString(toStr), std::string("\"-Infinity\"")); + CHECK_EQ(nan.toString(toStr), std::string("\"NaN\"")); +} + +//===----------------------------------------------------------------------===// +// Non-finite streaming output follows the same policy (failbit by default). +//===----------------------------------------------------------------------===// +TEST(non_finite_stream_policy) { + pjson inf; + inf = std::numeric_limits::infinity(); + std::ostringstream out; + inf.write(out); // default RejectNonFinite + CHECK(out.fail()); + + std::ostringstream out2; + pjson::SerializeOptions toNull; + toNull.nonFinite = pjson::SerializeOptions::NonFiniteToNull; + inf.write(out2, toNull); + CHECK(!out2.fail()); + CHECK_EQ(out2.str(), std::string("null")); +} + +//===----------------------------------------------------------------------===// +// Finite doubles still round-trip, including negative zero and subnormals. +//===----------------------------------------------------------------------===// +TEST(finite_double_round_trips) { + const double values[] = { + -0.0, std::numeric_limits::denorm_min(), std::numeric_limits::max(), 1.0, + 123.456, + }; + for (double d : values) { + pjson v; + v = d; + const std::string text = v.toString(); + pjson_test::Parsed p = pjson_test::parse(text); + CHECK(p != nullptr); + if (!p) + continue; + double parsed = 0.0; + CHECK(p->tryGet(parsed)); + // Exact bit-for-bit recovery for finite values. + CHECK(std::memcmp(&parsed, &d, sizeof(double)) == 0); + } +} diff --git a/pjsontest/src/tests_parse.cpp b/pjsontest/src/tests_parse.cpp index b5c59d8..3c69083 100644 --- a/pjsontest/src/tests_parse.cpp +++ b/pjsontest/src/tests_parse.cpp @@ -14,14 +14,17 @@ // //===----------------------------------------------------------------------===// // Parsing: valid documents of every shape, the (ptr,size) overload, and an -// exhaustive set of invalid inputs that must return nullptr without throwing. +// exhaustive set of invalid inputs that must return null values without throwing. // Number-grammar acceptance/rejection lives here too. // #include "pjson.h" +#include "pjson_parser.h" #include "test_harness.h" #include "test_util.h" #include +#include +#include #include using namespace ByteDance; @@ -31,6 +34,13 @@ using pjson_test::valueDouble; using pjson_test::valueInt; using pjson_test::valueString; +static_assert(sizeof(pJsonParser) == sizeof(void*), + "pJsonParser ABI must remain a one-pointer handle"); +static_assert(alignof(pJsonParser) == alignof(void*), + "pJsonParser ABI alignment must remain pointer-aligned"); +static_assert(std::is_nothrow_move_constructible::value, + "pJsonParser move construction must remain noexcept"); + //===----------------------------------------------------------------------===// // Valid top-level scalars //===----------------------------------------------------------------------===// @@ -157,40 +167,40 @@ TEST(parse_crlf_document) { //===----------------------------------------------------------------------===// TEST(parse_duplicate_key_policies) { const std::string document = "{\"a\":1,\n\"a\":2}"; - pjson::ParseError err; - CHECK(pjson::parse(document, err) == nullptr); + pJsonParser::Error err; + CHECK(pjson_test::parse(document, err) == nullptr); CHECK(!err.ok); CHECK_EQ(err.offset, size_t(8)); CHECK_EQ(err.line, size_t(2)); CHECK_EQ(err.column, size_t(1)); CHECK(err.message.find("duplicate") != std::string::npos); - pjson::ParseOptions keepLast; - keepLast.duplicateKeys = pjson::ParseOptions::KeepLastDuplicate; - auto last = pjson::parse(document, keepLast); + pJsonParser::Options keepLast; + keepLast.duplicateKeys = pJsonParser::Options::KeepLastDuplicate; + auto last = pjson_test::parse(document, keepLast); CHECK(last != nullptr); CHECK_EQ(last->size(), size_t(1)); CHECK_EQ(valueInt((*last)["a"]), int64_t(2)); - pjson::ParseOptions keepFirst; - keepFirst.duplicateKeys = pjson::ParseOptions::KeepFirstDuplicate; - auto first = pjson::parse(document, keepFirst); + pJsonParser::Options keepFirst; + keepFirst.duplicateKeys = pJsonParser::Options::KeepFirstDuplicate; + auto first = pjson_test::parse(document, keepFirst); CHECK(first != nullptr); CHECK_EQ(valueInt((*first)["a"]), int64_t(1)); - pjson::ParseOptions strictLast; - strictLast.duplicateKeys = pjson::ParseOptions::KeepLastDuplicate; - CHECK(pjson::parse(document, strictLast) != nullptr); + pJsonParser::Options strictLast; + strictLast.duplicateKeys = pJsonParser::Options::KeepLastDuplicate; + CHECK(pjson_test::parse(document, strictLast) != nullptr); } TEST(parse_error_reuse_across_calls) { - pjson::ParseError err; + pJsonParser::Error err; - CHECK(pjson::parse("{", err) == nullptr); + CHECK(pjson_test::parse("{", err) == nullptr); CHECK(!err.ok); CHECK(!err.message.empty()); - pjson::unique_ptr ok = pjson::parse("42", err); + pjson_test::Parsed ok = pjson_test::parse("42", err); CHECK(ok != nullptr); CHECK(err.ok); CHECK_EQ(err.offset, size_t(0)); @@ -199,12 +209,64 @@ TEST(parse_error_reuse_across_calls) { CHECK(err.message.empty()); CHECK_EQ(valueInt(*ok), int64_t(42)); - CHECK(pjson::parse("[1,]", err) == nullptr); + CHECK(pjson_test::parse("[1,]", err) == nullptr); CHECK(!err.ok); CHECK_EQ(err.offset, size_t(3)); CHECK(!err.message.empty()); } +TEST(parser_retains_configuration_and_is_reusable) { + pJsonParser::Options options; + options.maxDepth = 7; + options.maxNodes = 23; + options.maxInputBytes = 4096; + options.duplicateKeys = pJsonParser::Options::KeepLastDuplicate; + options.numberPolicy = pJsonParser::Options::AllowLossyNumbers; + pJsonParser parser(options); + + CHECK_EQ(parser.options().maxDepth, 7); + CHECK_EQ(parser.options().maxNodes, size_t(23)); + CHECK_EQ(parser.options().maxInputBytes, size_t(4096)); + CHECK_EQ(parser.options().duplicateKeys, pJsonParser::Options::KeepLastDuplicate); + CHECK_EQ(parser.options().numberPolicy, pJsonParser::Options::AllowLossyNumbers); + + pJsonParser::Error error; + pjson first = parser.parse(R"({"value":1,"value":2})", error); + CHECK(error.ok); + CHECK_EQ(valueInt(first["value"]), int64_t(2)); + + pjson rejected = parser.parse("[1,]", error); + CHECK(!error.ok); + CHECK(rejected.isNull()); + + pjson second = parser.parse("true", error); + CHECK(error.ok); + CHECK_EQ(valueBool(second), true); + CHECK(&second.getAllocator() == &parser.allocator()); +} + +TEST(parser_copy_and_move_preserve_configuration) { + pJsonParser::Options options; + options.maxDepth = 9; + options.duplicateKeys = pJsonParser::Options::KeepLastDuplicate; + pJsonParser parser(options); + + pJsonParser copy(parser); + CHECK_EQ(copy.options().maxDepth, 9); + CHECK_EQ(copy.options().duplicateKeys, pJsonParser::Options::KeepLastDuplicate); + + pJsonParser moved(std::move(copy)); + pJsonParser::Error error; + pjson value = moved.parse(R"({"a":1,"a":2})", error); + CHECK(error.ok); + CHECK_EQ(valueInt(value["a"]), int64_t(2)); + + // A moved-from parser remains usable with default configuration. + pjson rejected = copy.parse(R"({"a":1,"a":2})", error); + CHECK(!error.ok); + CHECK(rejected.isNull()); +} + //===----------------------------------------------------------------------===// // The (ptr, size) overload: embedded NUL, explicit length, nullptr, partial //===----------------------------------------------------------------------===// @@ -223,11 +285,11 @@ TEST(parse_ptr_size_with_embedded_nul_in_string) { } TEST(parse_nullptr_is_null_not_crash) { - CHECK(pjson::parse(nullptr, 10) == nullptr); + CHECK(pjson_test::parse(nullptr, 10) == nullptr); } TEST(parse_zero_length_is_null) { - CHECK(pjson::parse("anything", 0) == nullptr); + CHECK(pjson_test::parse("anything", 0) == nullptr); } //===----------------------------------------------------------------------===// @@ -318,10 +380,10 @@ TEST(parse_invalid_keywords) { // Number grammar: valid forms accepted with correct type //===----------------------------------------------------------------------===// TEST(parse_valid_numbers) { - pjson::unique_ptr zero = parse("0"); - pjson::unique_ptr negative = parse("-123"); - pjson::unique_ptr exponent = parse("1e3"); - pjson::unique_ptr fraction = parse("123.456"); + pjson_test::Parsed zero = parse("0"); + pjson_test::Parsed negative = parse("-123"); + pjson_test::Parsed exponent = parse("1e3"); + pjson_test::Parsed fraction = parse("123.456"); CHECK(zero != nullptr); CHECK(parse("-0") != nullptr); CHECK(parse("123") != nullptr); @@ -343,11 +405,21 @@ TEST(parse_valid_numbers) { CHECK_EQ(valueDouble(*fraction), 123.456); } -TEST(parse_bigint_falls_back_without_throw) { - // Beyond int64 range: must not throw; stored as double. +TEST(parse_bigint_rejected_by_default) { + // Beyond uint64 range: rejected by default (PJSON-NUM-001) rather than + // silently rounded to a double. auto p = parse("100000000000000000000000"); + CHECK(p == nullptr); +} + +TEST(parse_bigint_lossy_opt_in_stores_double) { + // With the explicit opt-in, the same token stores the nearest double. + pJsonParser::Options opt; + opt.numberPolicy = pJsonParser::Options::AllowLossyNumbers; + auto p = pjson_test::parse("100000000000000000000000", opt); CHECK(p != nullptr); - CHECK_EQ(p->getType(), pjson::jsonNumberDouble); + if (p) + CHECK_EQ(p->getType(), pjson::jsonNumberDouble); } TEST(parse_int64_boundary) { diff --git a/pjsontest/src/tests_pathological.cpp b/pjsontest/src/tests_pathological.cpp index af7d566..534e345 100644 --- a/pjsontest/src/tests_pathological.cpp +++ b/pjsontest/src/tests_pathological.cpp @@ -18,11 +18,13 @@ // they exercise unusually expensive paths without introducing timing flakes. //===----------------------------------------------------------------------===// #include "pjson.h" +#include "pjson_parser.h" #include "test_harness.h" #include "test_util.h" #include #include +#include #include #include #include @@ -126,14 +128,14 @@ TEST(pathological_mixed_numeric_equality_is_exact_above_binary64_integer_precisi // A long finite mantissa and a long, zero-padded exponent must be scanned in // full without changing their values. Very large positive values fail at a -// stable location, while a very negative exponent remains a valid finite JSON -// number (normally underflowing to zero). +// stable location, while a very negative exponent is rejected unless the +// caller opts in to its lossy conversion to zero. TEST(pathological_very_long_numeric_tokens) { const size_t digitCount = 65536; - pjson::ParseError err; + pJsonParser::Error err; const std::string longMantissa = "1." + std::string(digitCount, '0'); - auto mantissa = pjson::parse(longMantissa, err); + auto mantissa = pjson_test::parse(longMantissa, err); CHECK(mantissa != nullptr); CHECK(err.ok); if (mantissa) { @@ -142,7 +144,7 @@ TEST(pathological_very_long_numeric_tokens) { } const std::string paddedExponent = "1e+" + std::string(digitCount, '0') + std::string("1"); - auto finiteExponent = pjson::parse(paddedExponent, err); + auto finiteExponent = pjson_test::parse(paddedExponent, err); CHECK(finiteExponent != nullptr); CHECK(err.ok); if (finiteExponent) @@ -151,23 +153,32 @@ TEST(pathological_very_long_numeric_tokens) { // IEC 60559 implementations have infinities, so strtod must expose these // positive overflows and pjson must reject them rather than storing inf. if (std::numeric_limits::has_infinity) { + // A very long all-nines integer exceeds the exact 64-bit range, so the + // default policy rejects it with the integer-range diagnostic before any + // double fallback is attempted. const std::string hugeInteger(digitCount, '9'); - CHECK(pjson::parse(hugeInteger, err) == nullptr); + CHECK(pjson_test::parse(hugeInteger, err) == nullptr); CHECK(!err.ok); CHECK_EQ(err.offset, size_t(0)); CHECK_EQ(err.line, size_t(1)); CHECK_EQ(err.column, size_t(1)); - CHECK_EQ(err.message, std::string("number out of range")); + CHECK_EQ(err.message, + std::string("integer out of range; enable AllowLossyNumbers to store as double")); const std::string hugePositiveExponent = "1e+" + std::string(digitCount, '9'); - CHECK(pjson::parse(hugePositiveExponent, err) == nullptr); + CHECK(pjson_test::parse(hugePositiveExponent, err) == nullptr); CHECK(!err.ok); CHECK_EQ(err.offset, size_t(0)); CHECK_EQ(err.message, std::string("number out of range")); } const std::string hugeNegativeExponent = "1e-" + std::string(digitCount, '9'); - auto underflow = pjson::parse(hugeNegativeExponent, err); + CHECK(pjson_test::parse(hugeNegativeExponent, err) == nullptr); + CHECK(!err.ok); + CHECK_EQ(err.code, pJsonParser::Error::NumberRange); + pJsonParser::Options lossy; + lossy.numberPolicy = pJsonParser::Options::AllowLossyNumbers; + auto underflow = pjson_test::parse(hugeNegativeExponent, err, lossy); CHECK(underflow != nullptr); CHECK(err.ok); if (underflow) { @@ -178,6 +189,61 @@ TEST(pathological_very_long_numeric_tokens) { } } +TEST(pathological_binary64_halfway_rounding) { + if (!isIeeeBinary64()) { + CHECK(std::numeric_limits::is_specialized); + return; + } + + struct Case { + const char* text; + double expected; + }; + const Case cases[] = { + {"1.00000000000000011102230246251565404236316680908203125", 1.0}, + {"1.00000000000000011102230246251565404236316680908203126", std::nextafter(1.0, 2.0)}, + {"2.47032822920623272088284396434110686182529901307162382212792841250337753635104375e-324", + 0.0}, + {"2.47032822920623272088284396434110686182529901307162382212792841250337753635104376e-324", + std::numeric_limits::denorm_min()}, + }; + pJsonParser::Options lossy; + lossy.numberPolicy = pJsonParser::Options::AllowLossyNumbers; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) { + pJsonParser::Error error; + pjson value = pJsonParser(lossy).parse(cases[i].text, error); + CHECK(error.ok); + CHECK_EQ(doubleValue(value), cases[i].expected); + } +} + +TEST(pathological_random_binary64_round_trips_bit_exactly) { + if (!isIeeeBinary64()) { + CHECK(std::numeric_limits::is_specialized); + return; + } + + uint64_t state = UINT64_C(0x9e3779b97f4a7c15); + for (size_t i = 0; i < size_t(10000); ++i) { + state ^= state >> 12; + state ^= state << 25; + state ^= state >> 27; + const uint64_t bits = state * UINT64_C(2685821657736338717); + double value = 0.0; + std::memcpy(&value, &bits, sizeof(value)); + if (!std::isfinite(value)) + continue; + pjson node; + node = value; + pJsonParser::Error error; + pjson reparsed = pJsonParser().parse(node.toString(), error); + CHECK(error.ok); + double result = 0.0; + CHECK(reparsed.tryGet(result)); + CHECK(std::memcmp(&result, &value, sizeof(value)) == 0); + } +} + // Exercise normal/subnormal/max-finite conversion at exact binary64 values. // The literal expectations are intentionally conditional because C++ does not // require double to use the IEC 60559 binary64 representation. @@ -249,11 +315,11 @@ TEST(pathological_wide_array_node_budget_boundary) { const std::string json = makeFlatArray(width); CHECK_EQ(json.size(), width * 2U + 1U); - pjson::ParseOptions opts; + pJsonParser::Options opts; opts.maxNodes = width + 1U; opts.maxInputBytes = json.size(); - pjson::ParseError err; - auto atLimit = pjson::parse(json, err, opts); + pJsonParser::Error err; + auto atLimit = pjson_test::parse(json, err, opts); CHECK(atLimit != nullptr); CHECK(err.ok); if (atLimit) { @@ -263,7 +329,7 @@ TEST(pathological_wide_array_node_budget_boundary) { } opts.maxNodes = width; - CHECK(pjson::parse(json, err, opts) == nullptr); + CHECK(pjson_test::parse(json, err, opts) == nullptr); CHECK(!err.ok); CHECK_EQ(err.offset, json.size() - 2U); CHECK_EQ(err.line, size_t(1)); @@ -279,11 +345,11 @@ TEST(pathological_wide_object_node_budget_boundary) { size_t lastValueOffset = 0; const std::string json = makeFlatObject(width, lastValueOffset); - pjson::ParseOptions opts; + pJsonParser::Options opts; opts.maxNodes = width + 1U; opts.maxInputBytes = json.size(); - pjson::ParseError err; - auto atLimit = pjson::parse(json, err, opts); + pJsonParser::Error err; + auto atLimit = pjson_test::parse(json, err, opts); CHECK(atLimit != nullptr); CHECK(err.ok); if (atLimit) { @@ -296,7 +362,7 @@ TEST(pathological_wide_object_node_budget_boundary) { } opts.maxNodes = width; - CHECK(pjson::parse(json, err, opts) == nullptr); + CHECK(pjson_test::parse(json, err, opts) == nullptr); CHECK(!err.ok); CHECK_EQ(err.offset, lastValueOffset); CHECK_EQ(err.line, size_t(1)); @@ -325,24 +391,24 @@ TEST(pathological_large_escaped_payload_and_byte_budget) { CHECK_EQ(raw.size(), repeats * 5U); CHECK_EQ(json.size(), repeats * 13U + 2U); - pjson::ParseOptions opts; + pJsonParser::Options opts; opts.maxInputBytes = json.size(); - pjson::ParseError err; - auto fromBuffer = pjson::parse(json, err, opts); + pJsonParser::Error err; + auto fromBuffer = pjson_test::parse(json, err, opts); CHECK(fromBuffer != nullptr); CHECK(err.ok); if (fromBuffer) CHECK_EQ(stringValue(*fromBuffer), raw); std::istringstream acceptedStream(json); - auto fromStream = pjson::parseStream(acceptedStream, err, opts); + auto fromStream = pjson_test::parseStream(acceptedStream, err, opts); CHECK(fromStream != nullptr); CHECK(err.ok); if (fromStream) CHECK_EQ(stringValue(*fromStream), raw); opts.maxInputBytes = json.size() - 1U; - CHECK(pjson::parse(json, err, opts) == nullptr); + CHECK(pjson_test::parse(json, err, opts) == nullptr); CHECK(!err.ok); CHECK_EQ(err.offset, json.size() - 1U); CHECK_EQ(err.line, size_t(1)); @@ -350,7 +416,7 @@ TEST(pathological_large_escaped_payload_and_byte_budget) { CHECK_EQ(err.message, std::string(kInputBudgetError)); std::istringstream rejectedStream(json); - CHECK(pjson::parseStream(rejectedStream, err, opts) == nullptr); + CHECK(pjson_test::parseStream(rejectedStream, err, opts) == nullptr); CHECK(!err.ok); CHECK_EQ(err.offset, json.size() - 1U); CHECK_EQ(err.line, size_t(1)); diff --git a/pjsontest/src/tests_pointer_patch.cpp b/pjsontest/src/tests_pointer_patch.cpp index 64e378c..f0da6b1 100644 --- a/pjsontest/src/tests_pointer_patch.cpp +++ b/pjsontest/src/tests_pointer_patch.cpp @@ -48,12 +48,18 @@ namespace { } // Parses fixed test fixtures while still recording a normal harness failure on bad setup. - pjson::unique_ptr parseChecked(const char* text) { - pjson::unique_ptr doc = parse(text); + pjson_test::Parsed parseChecked(const char* text) { + pjson_test::Parsed doc = parse(text); CHECK(doc != nullptr); return doc; } + void expectJson(const pjson& actual, const char* expectedText) { + pjson_test::Parsed expected = parseChecked(expectedText); + if (expected != nullptr) + CHECK(actual == *expected); + } + // Returns an explicitly typed empty patch document for programmatic operation assembly. pjson makePatchArray() { pjson patch; @@ -62,7 +68,7 @@ namespace { } // Canonical RFC 6901 object containing every token-escaping example. - pjson::unique_ptr makeRfc6901ExampleDoc() { + pjson_test::Parsed makeRfc6901ExampleDoc() { return parseChecked( R"({"foo":["bar","baz"],"":0,"a/b":1,"c%d":2,"e^f":3,"g|h":4,"i\\j":5,"k\"l":6," ":7,"m~n":8})"); } @@ -128,9 +134,9 @@ TEST(patch_options_defaults_are_finite) { // RFC 6901 pointer examples and escaping //===----------------------------------------------------------------------===// TEST(pointer_rfc6901_examples) { - pjson::unique_ptr doc = makeRfc6901ExampleDoc(); + pjson_test::Parsed doc = makeRfc6901ExampleDoc(); - CHECK(doc->findPointer("") == doc.get()); + CHECK(doc->findPointer("") == &*doc); CHECK_EQ(doc->findPointer("/foo")->size(), size_t(2)); CHECK_EQ(mustGetString(*doc->findPointer("/foo/0")), std::string("bar")); CHECK_EQ(mustGetInt(*doc->findPointer("/")), int64_t(0)); @@ -145,7 +151,7 @@ TEST(pointer_rfc6901_examples) { } TEST(pointer_char_ptr_and_const_overloads_work) { - pjson::unique_ptr doc = makeRfc6901ExampleDoc(); + pjson_test::Parsed doc = makeRfc6901ExampleDoc(); const pjson& cdoc = *doc; const pjson* cnode = cdoc.findPointer("/foo/1"); @@ -181,7 +187,7 @@ TEST(pointer_empty_token_after_slash_is_empty_key) { // Pointer errors and non-vivifying behavior //===----------------------------------------------------------------------===// TEST(pointer_invalid_syntax_requires_leading_slash_or_empty) { - pjson::unique_ptr doc = parseChecked(R"({"foo":1})"); + pjson_test::Parsed doc = parseChecked(R"({"foo":1})"); pjson::PointerError err; CHECK(doc->findPointer("foo", err) == nullptr); CHECK(!err.ok); @@ -190,7 +196,7 @@ TEST(pointer_invalid_syntax_requires_leading_slash_or_empty) { } TEST(pointer_invalid_escape_sequences_fail) { - pjson::unique_ptr doc = parseChecked(R"({"foo":1})"); + pjson_test::Parsed doc = parseChecked(R"({"foo":1})"); pjson::PointerError badDigit; CHECK(doc->findPointer("/~2", badDigit) == nullptr); @@ -204,7 +210,7 @@ TEST(pointer_invalid_escape_sequences_fail) { } TEST(pointer_missing_target_reports_error) { - pjson::unique_ptr doc = parseChecked(R"({"foo":1})"); + pjson_test::Parsed doc = parseChecked(R"({"foo":1})"); pjson::PointerError err; CHECK(doc->findPointer("/bar", err) == nullptr); CHECK(!err.ok); @@ -215,7 +221,7 @@ TEST(pointer_missing_target_reports_error) { } TEST(pointer_expected_container_reports_error) { - pjson::unique_ptr doc = parseChecked(R"({"foo":1})"); + pjson_test::Parsed doc = parseChecked(R"({"foo":1})"); pjson::PointerError err; CHECK(doc->findPointer("/foo/bar", err) == nullptr); CHECK(!err.ok); @@ -225,7 +231,7 @@ TEST(pointer_expected_container_reports_error) { } TEST(pointer_invalid_array_index_reports_error) { - pjson::unique_ptr doc = parseChecked(R"(["x","y"])"); + pjson_test::Parsed doc = parseChecked(R"(["x","y"])"); pjson::PointerError leadingZero; CHECK(doc->findPointer("/01", leadingZero) == nullptr); @@ -241,7 +247,7 @@ TEST(pointer_invalid_array_index_reports_error) { } TEST(pointer_array_index_out_of_range_reports_error) { - pjson::unique_ptr doc = parseChecked(R"(["x","y"])"); + pjson_test::Parsed doc = parseChecked(R"(["x","y"])"); pjson::PointerError err; CHECK(doc->findPointer("/2", err) == nullptr); CHECK(!err.ok); @@ -250,7 +256,7 @@ TEST(pointer_array_index_out_of_range_reports_error) { } TEST(pointer_append_token_is_not_lookup) { - pjson::unique_ptr doc = parseChecked(R"(["x","y"])"); + pjson_test::Parsed doc = parseChecked(R"(["x","y"])"); pjson::PointerError err; CHECK(doc->findPointer("/-", err) == nullptr); CHECK(!err.ok); @@ -294,7 +300,7 @@ TEST(pointer_mutable_find_can_edit_existing_node_without_creating_new_ones) { } TEST(pointer_error_object_is_reused_across_failure_and_success) { - pjson::unique_ptr doc = parseChecked(R"({"foo":1})"); + pjson_test::Parsed doc = parseChecked(R"({"foo":1})"); pjson::PointerError err; CHECK(doc->findPointer("/missing", err) == nullptr); @@ -395,7 +401,7 @@ TEST(patch_invalid_op_is_rejected) { // JSON Patch: add //===----------------------------------------------------------------------===// TEST(patch_add_object_member_and_replace_existing_member) { - pjson::unique_ptr doc = parseChecked(R"({"a":1})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1})"); pjson patch = makePatchArray(); patch[0]["op"] = "add"; patch[0]["path"] = "/b"; @@ -407,11 +413,11 @@ TEST(patch_add_object_member_and_replace_existing_member) { pjson::PatchError err; CHECK(doc->applyPatch(patch, err)); CHECK(err.ok); - CHECK_EQ(doc->toString(), std::string("{\"a\":9,\"b\":2}")); + expectJson(*doc, R"({"a":9,"b":2})"); } TEST(patch_add_root_replaces_whole_document) { - pjson::unique_ptr doc = parseChecked(R"({"a":1})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1})"); pjson value; value["replaced"] = true; value["n"] = static_cast(7); @@ -422,11 +428,11 @@ TEST(patch_add_root_replaces_whole_document) { patch[0]["value"] = value; CHECK(doc->applyPatch(patch)); - CHECK_EQ(doc->toString(), std::string("{\"n\":7,\"replaced\":true}")); + expectJson(*doc, R"({"n":7,"replaced":true})"); } TEST(patch_add_array_inserts_and_appends) { - pjson::unique_ptr doc = parseChecked(R"(["a","c"])"); + pjson_test::Parsed doc = parseChecked(R"(["a","c"])"); pjson patch = makePatchArray(); patch[0]["op"] = "add"; patch[0]["path"] = "/1"; @@ -440,7 +446,7 @@ TEST(patch_add_array_inserts_and_appends) { } TEST(patch_add_requires_existing_parent_and_valid_array_index) { - pjson::unique_ptr doc = parseChecked(R"({"a":[1,2]})"); + pjson_test::Parsed doc = parseChecked(R"({"a":[1,2]})"); const pjson before(*doc); pjson missingParent = makePatchArray(); @@ -475,7 +481,7 @@ TEST(patch_add_requires_existing_parent_and_valid_array_index) { // JSON Patch: remove / replace //===----------------------------------------------------------------------===// TEST(patch_remove_object_member_and_array_element) { - pjson::unique_ptr doc = parseChecked(R"({"a":1,"arr":["x","y","z"]})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1,"arr":["x","y","z"]})"); pjson patch = makePatchArray(); patch[0]["op"] = "remove"; patch[0]["path"] = "/a"; @@ -487,7 +493,7 @@ TEST(patch_remove_object_member_and_array_element) { } TEST(patch_remove_root_succeeds_and_leaves_null) { - pjson::unique_ptr doc = parseChecked(R"({"a":1})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1})"); pjson patch = makePatchArray(); patch[0]["op"] = "remove"; patch[0]["path"] = ""; @@ -499,7 +505,7 @@ TEST(patch_remove_root_succeeds_and_leaves_null) { } TEST(patch_remove_and_replace_require_existing_target) { - pjson::unique_ptr doc = parseChecked(R"({"a":[1,2],"b":1})"); + pjson_test::Parsed doc = parseChecked(R"({"a":[1,2],"b":1})"); const pjson before(*doc); pjson removeMissing = makePatchArray(); @@ -521,7 +527,7 @@ TEST(patch_remove_and_replace_require_existing_target) { } TEST(patch_replace_root_and_existing_member) { - pjson::unique_ptr doc = parseChecked(R"({"a":1,"b":2})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1,"b":2})"); pjson replaceWhole; replaceWhole["done"] = true; @@ -541,7 +547,7 @@ TEST(patch_replace_root_and_existing_member) { // JSON Patch: move / copy / test //===----------------------------------------------------------------------===// TEST(patch_move_object_member_and_same_array_reorder) { - pjson::unique_ptr doc = parseChecked(R"({"obj":{"a":1},"arr":["a","b","c"]})"); + pjson_test::Parsed doc = parseChecked(R"({"obj":{"a":1},"arr":["a","b","c"]})"); pjson patch = makePatchArray(); patch[0]["op"] = "move"; patch[0]["from"] = "/obj/a"; @@ -551,11 +557,11 @@ TEST(patch_move_object_member_and_same_array_reorder) { patch[1]["path"] = "/arr/2"; CHECK(doc->applyPatch(patch)); - CHECK_EQ(doc->toString(), std::string("{\"arr\":[\"b\",\"c\",\"a\"],\"obj\":{\"b\":1}}")); + expectJson(*doc, R"({"arr":["b","c","a"],"obj":{"b":1}})"); } TEST(patch_move_from_must_exist_and_cannot_move_into_descendant) { - pjson::unique_ptr doc = parseChecked(R"({"a":{"b":1},"x":0})"); + pjson_test::Parsed doc = parseChecked(R"({"a":{"b":1},"x":0})"); const pjson before(*doc); pjson missingFrom = makePatchArray(); @@ -587,15 +593,14 @@ TEST(patch_move_from_must_exist_and_cannot_move_into_descendant) { } TEST(patch_copy_duplicates_value_without_mutating_source) { - pjson::unique_ptr doc = parseChecked(R"({"src":{"nested":[1,2]},"dst":0})"); + pjson_test::Parsed doc = parseChecked(R"({"src":{"nested":[1,2]},"dst":0})"); pjson patch = makePatchArray(); patch[0]["op"] = "copy"; patch[0]["from"] = "/src"; patch[0]["path"] = "/dst"; CHECK(doc->applyPatch(patch)); - CHECK_EQ(doc->toString(), - std::string("{\"dst\":{\"nested\":[1,2]},\"src\":{\"nested\":[1,2]}}")); + expectJson(*doc, R"({"dst":{"nested":[1,2]},"src":{"nested":[1,2]}})"); pjson* copiedArray = doc->findPointer("/dst/nested"); CHECK(copiedArray != nullptr); @@ -604,7 +609,7 @@ TEST(patch_copy_duplicates_value_without_mutating_source) { } TEST(patch_test_uses_rfc_numeric_equality_and_fails_atomically) { - pjson::unique_ptr doc = parseChecked(R"({"n":1,"arr":[{"x":1.0}]})"); + pjson_test::Parsed doc = parseChecked(R"({"n":1,"arr":[{"x":1.0}]})"); const pjson before(*doc); pjson pass = makePatchArray(); @@ -633,14 +638,14 @@ TEST(patch_test_uses_rfc_numeric_equality_and_fails_atomically) { } TEST(patch_test_numeric_equality_above_2pow53_and_rounded_inequality) { - pjson::unique_ptr exact = parseChecked(R"({"n":9007199254740994})"); + pjson_test::Parsed exact = parseChecked(R"({"n":9007199254740994})"); pjson patch = makePatchArray(); patch[0]["op"] = "test"; patch[0]["path"] = "/n"; patch[0]["value"] = double(9007199254740994.0); CHECK(exact->applyPatch(patch)); - pjson::unique_ptr rounded = parseChecked(R"({"n":9007199254740993})"); + pjson_test::Parsed rounded = parseChecked(R"({"n":9007199254740993})"); pjson bad = makePatchArray(); bad[0]["op"] = "test"; bad[0]["path"] = "/n"; @@ -652,7 +657,7 @@ TEST(patch_test_numeric_equality_above_2pow53_and_rounded_inequality) { } TEST(patch_copy_and_move_can_replace_root) { - pjson::unique_ptr copied = parseChecked(R"({"a":{"b":1},"x":2})"); + pjson_test::Parsed copied = parseChecked(R"({"a":{"b":1},"x":2})"); pjson copyPatch = makePatchArray(); copyPatch[0]["op"] = "copy"; copyPatch[0]["from"] = "/a"; @@ -660,7 +665,7 @@ TEST(patch_copy_and_move_can_replace_root) { CHECK(copied->applyPatch(copyPatch)); CHECK_EQ(copied->toString(), std::string("{\"b\":1}")); - pjson::unique_ptr moved = parseChecked(R"({"a":{"b":1},"x":2})"); + pjson_test::Parsed moved = parseChecked(R"({"a":{"b":1},"x":2})"); pjson movePatch = makePatchArray(); movePatch[0]["op"] = "move"; movePatch[0]["from"] = "/a"; @@ -673,7 +678,7 @@ TEST(patch_copy_and_move_can_replace_root) { // JSON Patch: invalid path / from syntax and full rollback //===----------------------------------------------------------------------===// TEST(patch_invalid_path_and_from_bubble_structured_errors) { - pjson::unique_ptr doc = parseChecked(R"({"a":1,"b":2})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1,"b":2})"); const pjson before(*doc); pjson badPath = makePatchArray(); @@ -698,7 +703,7 @@ TEST(patch_invalid_path_and_from_bubble_structured_errors) { } TEST(patch_error_object_is_reused_across_failure_and_success) { - pjson::unique_ptr doc = parseChecked(R"({"a":1})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1})"); pjson::PatchError err; pjson failing = makePatchArray(); @@ -726,7 +731,7 @@ TEST(patch_error_object_is_reused_across_failure_and_success) { } TEST(patch_atomic_rollback_on_late_failure) { - pjson::unique_ptr doc = parseChecked(R"({"a":1,"arr":[10,20]})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1,"arr":[10,20]})"); const pjson before(*doc); pjson patch = makePatchArray(); patch[0]["op"] = "replace"; @@ -747,7 +752,7 @@ TEST(patch_atomic_rollback_on_late_failure) { } TEST(patch_resource_limits_are_atomic_and_error_is_reusable) { - pjson::unique_ptr doc = parseChecked(R"({"a":1,"nested":{"x":2}})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1,"nested":{"x":2}})"); const pjson before(*doc); pjson patch = makePatchArray(); patch[0]["op"] = "replace"; @@ -787,7 +792,7 @@ TEST(patch_resource_limits_are_atomic_and_error_is_reusable) { } TEST(patch_large_string_value_and_copy_respect_clone_byte_limit) { - pjson::unique_ptr doc = parseChecked(R"({"src":"small","keep":1})"); + pjson_test::Parsed doc = parseChecked(R"({"src":"small","keep":1})"); const pjson before(*doc); const std::string large(4096, 'x'); @@ -831,7 +836,7 @@ TEST(patch_work_limit_bounds_deep_pointer_and_test_equality) { } TEST(patch_zero_limits_use_safe_ceilings_for_small_documents) { - pjson::unique_ptr doc = parseChecked(R"({"a":1})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1})"); pjson patch = makePatchArray(); patch[0]["op"] = "replace"; patch[0]["path"] = "/a"; @@ -869,40 +874,39 @@ TEST(patch_deep_pointer_and_patch_are_iterative_safe) { // JSON Merge Patch (RFC 7396) //===----------------------------------------------------------------------===// TEST(merge_patch_rfc7396_primary_example) { - pjson::unique_ptr doc = parseChecked( + pjson_test::Parsed doc = parseChecked( R"({"title":"Goodbye!","author":{"givenName":"John","familyName":"Doe"},"tags":["example","sample"],"content":"This will be unchanged"})"); - pjson::unique_ptr patch = parseChecked( + pjson_test::Parsed patch = parseChecked( R"({"title":"Hello!","phoneNumber":"+01-123-456-7890","author":{"familyName":null},"tags":["example"]})"); pjson::PatchError err; CHECK(doc->applyMergePatch(*patch, err)); CHECK(err.ok); - CHECK_EQ(doc->toString(), - std::string("{\"author\":{\"givenName\":\"John\"},\"content\":\"This will be " - "unchanged\",\"phoneNumber\":\"+01-123-456-7890\",\"tags\":[\"example\"]," - "\"title\":\"Hello!\"}")); + expectJson( + *doc, + R"({"author":{"givenName":"John"},"content":"This will be unchanged","phoneNumber":"+01-123-456-7890","tags":["example"],"title":"Hello!"})"); } TEST(merge_patch_null_members_remove_object_keys) { - pjson::unique_ptr doc = parseChecked(R"({"a":1,"b":2,"c":{"x":1,"y":2}})"); - pjson::unique_ptr patch = parseChecked(R"({"a":null,"c":{"y":null}})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1,"b":2,"c":{"x":1,"y":2}})"); + pjson_test::Parsed patch = parseChecked(R"({"a":null,"c":{"y":null}})"); CHECK(doc->applyMergePatch(*patch)); - CHECK_EQ(doc->toString(), std::string("{\"b\":2,\"c\":{\"x\":1}}")); + expectJson(*doc, R"({"b":2,"c":{"x":1}})"); } TEST(merge_patch_non_object_patch_replaces_entire_target) { - pjson::unique_ptr arrayDoc = parseChecked(R"({"a":1})"); - pjson::unique_ptr arrayPatch = parseChecked(R"([1,2,3])"); + pjson_test::Parsed arrayDoc = parseChecked(R"({"a":1})"); + pjson_test::Parsed arrayPatch = parseChecked(R"([1,2,3])"); CHECK(arrayDoc->applyMergePatch(*arrayPatch)); CHECK_EQ(arrayDoc->toString(), std::string("[1,2,3]")); - pjson::unique_ptr nullDoc = parseChecked(R"({"a":1})"); + pjson_test::Parsed nullDoc = parseChecked(R"({"a":1})"); pjson nullPatch; CHECK(nullDoc->applyMergePatch(nullPatch)); CHECK(nullDoc->isNull()); - pjson::unique_ptr scalarDoc = parseChecked(R"({"a":1})"); + pjson_test::Parsed scalarDoc = parseChecked(R"({"a":1})"); pjson scalarPatch; scalarPatch = static_cast(7); CHECK(scalarDoc->applyMergePatch(scalarPatch)); @@ -912,33 +916,33 @@ TEST(merge_patch_non_object_patch_replaces_entire_target) { TEST(merge_patch_when_target_is_non_object_object_patch_starts_from_empty_object) { pjson doc; doc = static_cast(5); - pjson::unique_ptr patch = parseChecked(R"({"a":1,"b":{"c":2}})"); + pjson_test::Parsed patch = parseChecked(R"({"a":1,"b":{"c":2}})"); CHECK(doc.applyMergePatch(*patch)); - CHECK_EQ(doc.toString(), std::string("{\"a\":1,\"b\":{\"c\":2}}")); + expectJson(doc, R"({"a":1,"b":{"c":2}})"); } TEST(merge_patch_arrays_are_replaced_wholesale_not_merged_elementwise) { - pjson::unique_ptr doc = parseChecked(R"({"arr":[1,2,3],"obj":{"arr":[4,5]}})"); - pjson::unique_ptr patch = parseChecked(R"({"arr":[9],"obj":{"arr":[7,8,9]}})"); + pjson_test::Parsed doc = parseChecked(R"({"arr":[1,2,3],"obj":{"arr":[4,5]}})"); + pjson_test::Parsed patch = parseChecked(R"({"arr":[9],"obj":{"arr":[7,8,9]}})"); CHECK(doc->applyMergePatch(*patch)); - CHECK_EQ(doc->toString(), std::string("{\"arr\":[9],\"obj\":{\"arr\":[7,8,9]}}")); + expectJson(*doc, R"({"arr":[9],"obj":{"arr":[7,8,9]}})"); } TEST(merge_patch_empty_object_is_no_op) { - pjson::unique_ptr doc = parseChecked(R"({"a":1,"b":{"c":2}})"); + pjson_test::Parsed doc = parseChecked(R"({"a":1,"b":{"c":2}})"); const pjson before(*doc); - pjson::unique_ptr patch = parseChecked(R"({})"); + pjson_test::Parsed patch = parseChecked(R"({})"); CHECK(doc->applyMergePatch(*patch)); CHECK(*doc == before); } TEST(merge_patch_resource_limits_are_atomic) { - pjson::unique_ptr doc = parseChecked(R"({"keep":1,"nested":{"old":true}})"); + pjson_test::Parsed doc = parseChecked(R"({"keep":1,"nested":{"old":true}})"); const pjson before(*doc); - pjson::unique_ptr patch = parseChecked(R"({"nested":{"new":2},"added":3})"); + pjson_test::Parsed patch = parseChecked(R"({"nested":{"new":2},"added":3})"); pjson::PatchOptions options; options.maxWork = 1; @@ -956,7 +960,7 @@ TEST(merge_patch_resource_limits_are_atomic) { TEST(merge_patch_preserves_resource_limit_from_member_processing) { pjson target; - pjson::unique_ptr patch = parseChecked(R"({"a":1})"); + pjson_test::Parsed patch = parseChecked(R"({"a":1})"); pjson::PatchOptions options; options.maxClonedNodes = 1; pjson::PatchError error; diff --git a/pjsontest/src/tests_roundtrip.cpp b/pjsontest/src/tests_roundtrip.cpp index 3cafb1a..11f2676 100644 --- a/pjsontest/src/tests_roundtrip.cpp +++ b/pjsontest/src/tests_roundtrip.cpp @@ -19,6 +19,7 @@ // #include "pjson.h" #include "test_harness.h" +#include "test_util.h" #include #include @@ -58,7 +59,7 @@ TEST(format_double_is_short_and_stable) { TEST(format_double_large_integral_value_round_trips_with_double_kind) { pjson value; value = double(2738421882738290.0); - pjson::unique_ptr reparsed = pjson::parse(value.toString()); + pjson_test::Parsed reparsed = pjson_test::parse(value.toString()); CHECK(reparsed != nullptr); CHECK(reparsed->isDouble()); CHECK(*reparsed == value); @@ -67,7 +68,7 @@ TEST(format_double_large_integral_value_round_trips_with_double_kind) { TEST(format_double_high_precision_round_trips) { pjson j; j = double(3.141592653589793); - pjson::unique_ptr rt = pjson::parse(j.toString()); + pjson_test::Parsed rt = pjson_test::parse(j.toString()); CHECK(rt != nullptr); expectDouble(*rt, 3.141592653589793); } @@ -91,9 +92,9 @@ TEST(format_negative_and_zero) { } //===----------------------------------------------------------------------===// -// Compact round-trip: parse(serialize(x)) reproduces serialize(x) +// Compact round-trip: parse(serialize(x)) preserves the JSON value. //===----------------------------------------------------------------------===// -TEST(compact_round_trip_reproduces) { +TEST(compact_round_trip_preserves_value) { pjson o; o["s"] = std::string("text with \"quotes\" and \\slash"); o["i"] = static_cast(-42); @@ -104,19 +105,16 @@ TEST(compact_round_trip_reproduces) { o["nested"]["deep"]["deeper"] = std::string("value"); std::string compact = o.toString(); - pjson::unique_ptr p1 = pjson::parse(compact); + pjson_test::Parsed p1 = pjson_test::parse(compact); CHECK(p1 != nullptr); - CHECK_EQ(p1->toString(), compact); - // A second generation is identical (idempotent). - pjson::unique_ptr p2 = pjson::parse(p1->toString()); - CHECK(p2 != nullptr); - CHECK_EQ(p2->toString(), compact); + if (p1 != nullptr) + CHECK(*p1 == o); } //===----------------------------------------------------------------------===// -// Pretty output: re-parses to the same compact form and is idempotent +// Pretty output re-parses to the same value. //===----------------------------------------------------------------------===// -TEST(pretty_reparses_to_same_compact) { +TEST(pretty_reparses_to_same_value) { pjson o; o["a"] = static_cast(1); o["b"]["c"] = std::vector({"x", "y"}); @@ -130,10 +128,10 @@ TEST(pretty_reparses_to_same_compact) { std::string pretty = o.toString(prettyOpts); CHECK_NE(compact, pretty); // formatting differs - pjson::unique_ptr pp = pjson::parse(pretty); + pjson_test::Parsed pp = pjson_test::parse(pretty); CHECK(pp != nullptr); - CHECK_EQ(pp->toString(), compact); // same data - CHECK_EQ(pp->toString(prettyOpts), pretty); // pretty is idempotent + if (pp != nullptr) + CHECK(*pp == o); } //===----------------------------------------------------------------------===// @@ -142,31 +140,32 @@ TEST(pretty_reparses_to_same_compact) { TEST(empty_structures_round_trip) { pjson a; a.resetTo(pjson::jsonArray); - pjson::unique_ptr ra = pjson::parse(a.toString()); + pjson_test::Parsed ra = pjson_test::parse(a.toString()); CHECK(ra != nullptr); CHECK_EQ(ra->getType(), pjson::jsonArray); CHECK_EQ(ra->toString(), a.toString()); pjson m; m.resetTo(pjson::jsonObject); - pjson::unique_ptr rm = pjson::parse(m.toString()); + pjson_test::Parsed rm = pjson_test::parse(m.toString()); CHECK(rm != nullptr); CHECK_EQ(rm->getType(), pjson::jsonObject); pjson n; CHECK_EQ(n.toString(), std::string("null")); - pjson::unique_ptr rn = pjson::parse(n.toString()); + pjson_test::Parsed rn = pjson_test::parse(n.toString()); CHECK(rn != nullptr); CHECK_EQ(rn->getType(), pjson::jsonNull); } TEST(nested_empty_containers_round_trip) { - pjson::unique_ptr p = pjson::parse("{\"a\":[],\"b\":{},\"c\":[[],{}]}"); + pjson_test::Parsed p = pjson_test::parse("{\"a\":[],\"b\":{},\"c\":[[],{}]}"); CHECK(p != nullptr); std::string compact = p->toString(); - pjson::unique_ptr p2 = pjson::parse(compact); + pjson_test::Parsed p2 = pjson_test::parse(compact); CHECK(p2 != nullptr); - CHECK_EQ(p2->toString(), compact); + if (p2 != nullptr) + CHECK(*p2 == *p); } //===----------------------------------------------------------------------===// @@ -286,28 +285,26 @@ namespace { } // namespace -TEST(fuzz_round_trip_is_stable) { +TEST(fuzz_round_trip_preserves_value) { std::mt19937 rng(0xC0FFEE); // fixed seed -> deterministic, reproducible const pjson::SerializeOptions prettyOpts = prettyOptions(); for (int iter = 0; iter < 500; ++iter) { pjson doc; build_random(doc, rng, 4); - // Compact: parse(serialize(x)) must reproduce serialize(x) exactly. + // Compact: parse(serialize(x)) must preserve the represented value. std::string compact = doc.toString(); - pjson::unique_ptr rc = pjson::parse(compact); + pjson_test::Parsed rc = pjson_test::parse(compact); CHECK(rc != nullptr); - if (rc) { - CHECK_EQ(rc->toString(), compact); - } + if (rc) + CHECK(*rc == doc); - // Pretty: must re-parse to the same compact form. + // Pretty: must re-parse to the same represented value. std::string pretty = doc.toString(prettyOpts); - pjson::unique_ptr rp = pjson::parse(pretty); + pjson_test::Parsed rp = pjson_test::parse(pretty); CHECK(rp != nullptr); - if (rp) { - CHECK_EQ(rp->toString(), compact); - } + if (rp) + CHECK(*rp == doc); } } @@ -323,15 +320,14 @@ TEST(fuzz_never_throws_on_arbitrary_bytes) { int n = len(rng); for (int i = 0; i < n; ++i) s += static_cast(byte(rng)); - pjson::unique_ptr p = pjson::parse(s); // must not throw + pjson_test::Parsed p = pjson_test::parse(s); // must not throw if (p) { // If it parsed, it must re-serialize and re-parse consistently. std::string out = p->toString(); - pjson::unique_ptr p2 = pjson::parse(out); + pjson_test::Parsed p2 = pjson_test::parse(out); CHECK(p2 != nullptr); - if (p2) { - CHECK_EQ(p2->toString(), out); - } + if (p2) + CHECK(*p2 == *p); } ++handled; } diff --git a/pjsontest/src/tests_schema.cpp b/pjsontest/src/tests_schema.cpp index 440adff..37af2bc 100644 --- a/pjsontest/src/tests_schema.cpp +++ b/pjsontest/src/tests_schema.cpp @@ -19,29 +19,35 @@ // #include "pjson.h" #include "test_harness.h" +#include "test_util.h" #include #include using namespace ByteDance; +static_assert(sizeof(pJsonSchemaValidator) == sizeof(void*), + "pJsonSchemaValidator ABI must remain a one-pointer handle"); +static_assert(alignof(pJsonSchemaValidator) == alignof(void*), + "pJsonSchemaValidator ABI alignment must remain pointer-aligned"); + namespace { - pjson::unique_ptr parseJson(const char* text) { - return pjson::parse(std::string(text)); + pjson_test::Parsed parseJson(const char* text) { + return pjson_test::parse(std::string(text)); } // Convenience: parse a schema and a document (both from JSON text) and return // whether the document validates, capturing errors. bool validates(const char* schemaText, const char* dataText, - std::vector& errors) { - pjson::unique_ptr schema = parseJson(schemaText); - pjson::unique_ptr data = parseJson(dataText); + std::vector& errors) { + pjson_test::Parsed schema = parseJson(schemaText); + pjson_test::Parsed data = parseJson(dataText); if (!schema || !data) return false; - return data->validate(*schema, errors); + return pjson_test::schemaValidate(*data, *schema, errors); } - bool hasMessageContaining(const std::vector& errors, + bool hasMessageContaining(const std::vector& errors, const std::string& needle) { for (size_t i = 0; i < errors.size(); ++i) { if (errors[i].message.find(needle) != std::string::npos) @@ -51,7 +57,7 @@ namespace { } bool validates(const char* schemaText, const char* dataText) { - std::vector errors; + std::vector errors; return validates(schemaText, dataText, errors); } @@ -72,14 +78,77 @@ TEST(schema_type_matches) { } TEST(schema_type_mismatch_reports_path_and_message) { - std::vector errors; + std::vector errors; CHECK(!validates(R"({"type":"integer"})", R"("nope")", errors)); CHECK_EQ(errors.size(), size_t(1)); - CHECK_EQ(errors[0].path, std::string("")); // root + CHECK_EQ(errors[0].code, pJsonSchemaValidator::Error::TypeMismatch); + CHECK_EQ(errors[0].instanceLocation, std::string("")); // root + CHECK_EQ(errors[0].schemaLocation, std::string("/type")); + CHECK_EQ(errors[0].keyword, std::string("type")); CHECK(errors[0].message.find("integer") != std::string::npos); CHECK(errors[0].message.find("string") != std::string::npos); } +TEST(schema_diagnostics_support_first_error_and_nested_combinator_causes) { + pjson schema = + pJsonParser().parse(R"({"anyOf":[{"type":"string"},{"type":"integer"},{"type":"array"}]})"); + pjson instance = pJsonParser().parse(R"({})"); + + pJsonSchemaValidator::Options first; + first.stopAfterFirstError = true; + pJsonSchemaValidator firstValidator(schema, first); + std::vector firstErrors; + CHECK(!firstValidator.validate(instance, firstErrors)); + CHECK_EQ(firstErrors.size(), size_t(1)); + CHECK_EQ(firstErrors[0].code, pJsonSchemaValidator::Error::CombinatorMismatch); + CHECK_EQ(firstErrors[0].keyword, std::string("anyOf")); + + pJsonSchemaValidator::Options nested; + nested.collectNestedCauses = true; + pJsonSchemaValidator nestedValidator(schema, nested); + std::vector errors; + CHECK(!nestedValidator.validate(instance, errors)); + const pJsonSchemaValidator::Error* combinator = nullptr; + for (size_t i = 0; i < errors.size(); ++i) { + if (errors[i].keyword == "anyOf") + combinator = &errors[i]; + } + CHECK(combinator != nullptr); + if (combinator != nullptr) { + CHECK_EQ(combinator->code, pJsonSchemaValidator::Error::CombinatorMismatch); + CHECK_EQ(combinator->schemaLocation, std::string("/anyOf")); + CHECK_EQ(combinator->causes.size(), size_t(3)); + if (combinator->causes.size() == size_t(3)) { + CHECK_EQ(combinator->causes[0].schemaLocation, std::string("/anyOf/0/type")); + CHECK_EQ(combinator->causes[1].schemaLocation, std::string("/anyOf/1/type")); + CHECK_EQ(combinator->causes[2].schemaLocation, std::string("/anyOf/2/type")); + } + } +} + +TEST(schema_diagnostic_options_share_one_bounded_contract) { + pjson malformed = pJsonParser().parse(R"({"$schema":7,"$vocabulary":false})"); + pJsonSchemaValidator::Options first; + first.stopAfterFirstError = true; + pJsonSchemaValidator compileValidator(malformed, first); + CHECK(!compileValidator.isSchemaValid()); + CHECK_EQ(compileValidator.schemaErrors().size(), size_t(1)); + + pjson schema = + pJsonParser().parse(R"({"anyOf":[{"type":"string"},{"type":"integer"},{"type":"array"}]})"); + pjson instance; + instance = true; + pJsonSchemaValidator::Options bounded; + bounded.maxErrors = 3; + bounded.collectNestedCauses = true; + pJsonSchemaValidator validator(schema, bounded); + std::vector errors; + CHECK(!validator.validate(instance, errors)); + CHECK_EQ(errors.size(), size_t(1)); + CHECK_EQ(errors[0].causes.size(), size_t(2)); + CHECK(errors.size() + errors[0].causes.size() <= bounded.maxErrors); +} + TEST(schema_type_integer_vs_number) { CHECK(!validates(R"({"type":"integer"})", "4.5")); // fractional not integer CHECK(validates(R"({"type":"integer"})", "4.0")); // whole double is integer @@ -100,11 +169,11 @@ TEST(schema_required_present) { } TEST(schema_required_missing) { - std::vector errors; + std::vector errors; CHECK( !validates(R"({"type":"object","required":["name","age"]})", R"({"name":"Ada"})", errors)); CHECK_EQ(errors.size(), size_t(1)); - CHECK_EQ(errors[0].path, std::string("")); + CHECK_EQ(errors[0].instanceLocation, std::string("")); CHECK(errors[0].message.find("age") != std::string::npos); } @@ -113,10 +182,10 @@ TEST(schema_properties_recurse_with_path) { R"({"type":"object","properties":{ "age":{"type":"integer"}, "name":{"type":"string"}}})"; - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"age":"old","name":"Ada"})", errors)); CHECK_EQ(errors.size(), size_t(1)); - CHECK_EQ(errors[0].path, std::string("/age")); // JSON-Pointer to the child + CHECK_EQ(errors[0].instanceLocation, std::string("/age")); // JSON-Pointer to the child } TEST(schema_additional_properties_false) { @@ -124,10 +193,10 @@ TEST(schema_additional_properties_false) { R"({"type":"object","properties":{"a":{"type":"integer"}}, "additionalProperties":false})"; CHECK(validates(schema, R"({"a":1})")); - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"a":1,"b":2})", errors)); CHECK_EQ(errors.size(), size_t(1)); - CHECK_EQ(errors[0].path, std::string("/b")); + CHECK_EQ(errors[0].instanceLocation, std::string("/b")); } TEST(schema_min_max_properties) { @@ -141,10 +210,10 @@ TEST(schema_min_max_properties) { //===----------------------------------------------------------------------===// TEST(schema_items_applies_to_each_element) { CHECK(validates(R"({"type":"array","items":{"type":"integer"}})", "[1,2,3]")); - std::vector errors; + std::vector errors; CHECK(!validates(R"({"type":"array","items":{"type":"integer"}})", R"([1,"two",3])", errors)); CHECK_EQ(errors.size(), size_t(1)); - CHECK_EQ(errors[0].path, std::string("/1")); // index of the bad element + CHECK_EQ(errors[0].instanceLocation, std::string("/1")); // index of the bad element } TEST(schema_min_max_items) { @@ -195,27 +264,60 @@ TEST(schema_pattern) { CHECK(!validates(R"({"pattern":"^[a-z]+$"})", R"("Hello1")")); } +TEST(schema_pattern_unicode_ecmascript_semantics) { + CHECK(validates(R"({"pattern":"^\\p{Letter}+$"})", R"("Helloπ")")); + CHECK(!validates(R"({"pattern":"^\\p{Letter}+$"})", R"("123")")); + CHECK(validates(R"({"pattern":"^🐲*$"})", R"("🐲🐲")")); + CHECK(!validates(R"({"pattern":"^🐲*$"})", R"("🐉")")); + + pjson_test::SchemaOptions trusted = pjson_test::SchemaOptions::trustedRegex(); + pjson schema = pJsonParser().parse(R"({"pattern":"(?<=a+)b"})"); + pjson value = pJsonParser().parse(R"("aaab")"); + std::vector errors; + CHECK(pjson_test::schemaValidate(value, schema, errors, trusted)); +} + +TEST(schema_regex_format_uses_ecmascript_syntax) { + pjson_test::SchemaOptions options; + options.validateFormats = true; + pjson schema = pJsonParser().parse(R"({"format":"regex"})"); + const char* valid[] = {"([abc])+\\s+$", "(?x)", "(?<=a+)b", "[]", "[^]", "\\cA"}; + const char* invalid[] = {"^(abc]", "\\a", "(?Px)", "(?#comment)a", "(?i)abc"}; + for (size_t i = 0; i < sizeof(valid) / sizeof(valid[0]); ++i) { + pjson value; + value = valid[i]; + std::vector errors; + CHECK(pjson_test::schemaValidate(value, schema, errors, options)); + } + for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); ++i) { + pjson value; + value = invalid[i]; + std::vector errors; + CHECK(!pjson_test::schemaValidate(value, schema, errors, options)); + } +} + TEST(schema_pattern_redos_safety_policy) { - pjson::unique_ptr schema = parseJson(R"({"pattern":"^(a+)+$","minLength":10})"); - pjson::unique_ptr value = parseJson(R"("aaaa")"); + pjson_test::Parsed schema = parseJson(R"({"pattern":"^(a+)+$","minLength":10})"); + pjson_test::Parsed value = parseJson(R"("aaaa")"); CHECK(schema != nullptr); CHECK(value != nullptr); - std::vector errors; - CHECK(!value->validate(*schema, errors)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(*value, *schema, errors)); CHECK_EQ(errors.size(), size_t(2)); // policy failure + minLength (collect all) CHECK(errors[0].message.find("minLength") != std::string::npos || errors[1].message.find("minLength") != std::string::npos); CHECK(errors[0].message.find("safety policy") != std::string::npos || errors[1].message.find("safety policy") != std::string::npos); - pjson::unique_ptr alternation = parseJson(R"({"pattern":"^(a|aa)+$"})"); + pjson_test::Parsed alternation = parseJson(R"({"pattern":"^(a|aa)+$"})"); errors.clear(); - CHECK(!value->validate(*alternation, errors)); + CHECK(!pjson_test::schemaValidate(*value, *alternation, errors)); CHECK(errors[0].message.find("safety policy") != std::string::npos); - pjson::unique_ptr hugeRepeat = parseJson(R"({"pattern":"^a{1000000}$"})"); + pjson_test::Parsed hugeRepeat = parseJson(R"({"pattern":"^a{1000000}$"})"); errors.clear(); - CHECK(!value->validate(*hugeRepeat, errors)); + CHECK(!pjson_test::schemaValidate(*value, *hugeRepeat, errors)); CHECK(errors[0].message.find("safety policy") != std::string::npos); } @@ -224,23 +326,35 @@ TEST(schema_pattern_size_limits_and_trusted_opt_in) { schema["pattern"] = std::string(257, 'a'); pjson value; value = "a"; - std::vector errors; - CHECK(!value.validate(schema, errors)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(value, schema, errors)); CHECK(errors[0].message.find("pattern exceeds") != std::string::npos); schema["pattern"] = "a"; value = std::string(4097, 'a'); errors.clear(); - CHECK(!value.validate(schema, errors)); + CHECK(!pjson_test::schemaValidate(value, schema, errors)); CHECK(errors[0].message.find("string exceeds") != std::string::npos); // Trusted applications may explicitly restore unrestricted behavior. - pjson::SchemaOptions trusted = pjson::SchemaOptions::trustedRegex(); + pjson_test::SchemaOptions trusted = pjson_test::SchemaOptions::trustedRegex(); errors.clear(); - CHECK(value.validate(schema, errors, trusted)); + CHECK(pjson_test::schemaValidate(value, schema, errors, trusted)); CHECK(errors.empty()); } +TEST(schema_trusted_regex_still_has_backend_work_limit) { + pjson schema = pJsonParser().parse(R"({"pattern":"^(a|aa)+$"})"); + pjson value; + value = std::string(64, 'a') + "!"; + pjson_test::SchemaOptions trusted = pjson_test::SchemaOptions::trustedRegex(); + std::vector errors; + CHECK(!pjson_test::schemaValidate(value, schema, errors, trusted)); + CHECK(!errors.empty()); + CHECK_EQ(errors[0].code, pjson_test::SchemaError::ResourceLimit); + CHECK(errors[0].message.find("work limit") != std::string::npos); +} + //===----------------------------------------------------------------------===// // const / enum //===----------------------------------------------------------------------===// @@ -303,7 +417,7 @@ TEST(schema_collects_all_failures) { "age":{"type":"integer","minimum":0}, "name":{"type":"string"}}})"; // age is a negative string (2 problems), name is a number (1), email missing (1). - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"age":"x","name":5})", errors)); // Expect: missing email, /age type, /name type. (age minimum can't run on a // non-number.) At least three distinct failures collected. @@ -319,7 +433,7 @@ TEST(schema_valid_document_has_no_errors) { "age":{"type":"integer","minimum":0}, "tags":{"type":"array","items":{"type":"string"}}}, "additionalProperties":false})"; - std::vector errors; + std::vector errors; CHECK(validates(schema, R"({"name":"Ada","age":36,"tags":["x","y"]})", errors)); CHECK_EQ(errors.size(), size_t(0)); } @@ -336,14 +450,14 @@ TEST(schema_built_programmatically) { schema["properties"]["age"]["type"] = "integer"; schema["properties"]["age"]["minimum"] = int64_t(0); - pjson::unique_ptr data = parseJson(R"({"name":"Ada","age":36})"); - CHECK(data->validate(schema)); + pjson_test::Parsed data = parseJson(R"({"name":"Ada","age":36})"); + CHECK(pjson_test::schemaValidate(*data, schema)); - pjson::unique_ptr bad = parseJson(R"({"name":"Ada","age":-1})"); - std::vector errors; - CHECK(!bad->validate(schema, errors)); + pjson_test::Parsed bad = parseJson(R"({"name":"Ada","age":-1})"); + std::vector errors; + CHECK(!pjson_test::schemaValidate(*bad, schema, errors)); CHECK_EQ(errors.size(), size_t(1)); - CHECK_EQ(errors[0].path, std::string("/age")); + CHECK_EQ(errors[0].instanceLocation, std::string("/age")); } //===----------------------------------------------------------------------===// @@ -351,10 +465,10 @@ TEST(schema_built_programmatically) { //===----------------------------------------------------------------------===// TEST(schema_pointer_escaping) { const char* schema = R"({"type":"object","properties":{"a/b":{"type":"integer"}}})"; - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"a/b":"x"})", errors)); CHECK_EQ(errors.size(), size_t(1)); - CHECK_EQ(errors[0].path, std::string("/a~1b")); // '/' escaped as ~1 + CHECK_EQ(errors[0].instanceLocation, std::string("/a~1b")); // '/' escaped as ~1 } TEST(schema_const_exact_mixed_numeric_equality_beyond_2pow53) { @@ -363,11 +477,11 @@ TEST(schema_const_exact_mixed_numeric_equality_beyond_2pow53) { pjson exact; exact = int64_t(9007199254740993LL); - CHECK(exact.validate(schema)); + CHECK(pjson_test::schemaValidate(exact, schema)); pjson rounded; rounded = double(9007199254740992.0); - CHECK(!rounded.validate(schema)); + CHECK(!pjson_test::schemaValidate(rounded, schema)); } TEST(schema_enum_exact_mixed_numeric_equality_beyond_2pow53) { @@ -377,11 +491,11 @@ TEST(schema_enum_exact_mixed_numeric_equality_beyond_2pow53) { pjson exact; exact = int64_t(9007199254740993LL); - CHECK(exact.validate(schema)); + CHECK(pjson_test::schemaValidate(exact, schema)); pjson rounded; rounded = double(9007199254740992.0); - CHECK(!rounded.validate(schema)); + CHECK(!pjson_test::schemaValidate(rounded, schema)); } TEST(schema_unique_items_exact_mixed_numeric_equality_beyond_2pow53) { @@ -391,12 +505,12 @@ TEST(schema_unique_items_exact_mixed_numeric_equality_beyond_2pow53) { pjson distinct; distinct[0] = int64_t(9007199254740993LL); distinct[1] = double(9007199254740992.0); - CHECK(distinct.validate(schema)); + CHECK(pjson_test::schemaValidate(distinct, schema)); pjson duplicate; duplicate[0] = int64_t(9007199254740992LL); duplicate[1] = double(9007199254740992.0); - CHECK(!duplicate.validate(schema)); + CHECK(!pjson_test::schemaValidate(duplicate, schema)); } TEST(schema_exact_numeric_bounds_beyond_2pow53) { @@ -405,23 +519,23 @@ TEST(schema_exact_numeric_bounds_beyond_2pow53) { pjson below; below = int64_t(9007199254740992LL); - CHECK(!below.validate(minimumSchema)); + CHECK(!pjson_test::schemaValidate(below, minimumSchema)); pjson belowDouble; belowDouble = double(9007199254740992.0); - CHECK(!belowDouble.validate(minimumSchema)); + CHECK(!pjson_test::schemaValidate(belowDouble, minimumSchema)); pjson at; at = int64_t(9007199254740993LL); - CHECK(at.validate(minimumSchema)); + CHECK(pjson_test::schemaValidate(at, minimumSchema)); pjson exclusiveMaximumSchema; exclusiveMaximumSchema["exclusiveMaximum"] = int64_t(9007199254740993LL); - CHECK(at.validate(minimumSchema)); - CHECK(!at.validate(exclusiveMaximumSchema)); + CHECK(pjson_test::schemaValidate(at, minimumSchema)); + CHECK(!pjson_test::schemaValidate(at, exclusiveMaximumSchema)); pjson maximumDoubleSchema; maximumDoubleSchema["maximum"] = double(9007199254740992.0); - CHECK(!at.validate(maximumDoubleSchema)); + CHECK(!pjson_test::schemaValidate(at, maximumDoubleSchema)); } TEST(schema_length_counts_unicode_code_points) { @@ -453,18 +567,31 @@ TEST(schema_malformed_not_shape_is_ignored) { } TEST(schema_error_constructors_and_collector_append) { - pjson::SchemaError empty; - CHECK_EQ(empty.path, std::string()); + pjson_test::SchemaError empty; + CHECK_EQ(empty.code, pJsonSchemaValidator::Error::None); + CHECK_EQ(empty.instanceLocation, std::string()); + CHECK_EQ(empty.schemaLocation, std::string()); + CHECK_EQ(empty.keyword, std::string()); CHECK_EQ(empty.message, std::string()); - - pjson::SchemaError concrete("/age", "expected integer"); - CHECK_EQ(concrete.path, std::string("/age")); + CHECK_EQ(empty.category, pJsonSchemaValidator::Error::InstanceValidation); + CHECK(empty.causes.empty()); + + pjson_test::SchemaError concrete(pJsonSchemaValidator::Error::TypeMismatch, + pJsonSchemaValidator::Error::InstanceValidation, "/age", + "/properties/age/type", "type", "expected integer"); + CHECK_EQ(concrete.code, pJsonSchemaValidator::Error::TypeMismatch); + CHECK_EQ(concrete.instanceLocation, std::string("/age")); + CHECK_EQ(concrete.schemaLocation, std::string("/properties/age/type")); + CHECK_EQ(concrete.keyword, std::string("type")); CHECK_EQ(concrete.message, std::string("expected integer")); + CHECK_EQ(concrete.category, pJsonSchemaValidator::Error::InstanceValidation); - std::vector errors; - errors.push_back(pjson::SchemaError("/seed", "existing")); + std::vector errors; + errors.push_back(pjson_test::SchemaError(pJsonSchemaValidator::Error::InternalError, + pJsonSchemaValidator::Error::InstanceValidation, + "/seed", "", "", "existing")); CHECK(!validates(R"({"type":"object","required":["name"]})", R"({})", errors)); - CHECK_EQ(errors[0].path, std::string("/seed")); + CHECK_EQ(errors[0].instanceLocation, std::string("/seed")); CHECK_EQ(errors[0].message, std::string("existing")); CHECK(errors.size() >= size_t(2)); CHECK(hasMessageContaining(errors, "missing required property")); diff --git a/pjsontest/src/tests_schema_2020.cpp b/pjsontest/src/tests_schema_2020.cpp new file mode 100644 index 0000000..1dc2e79 --- /dev/null +++ b/pjsontest/src/tests_schema_2020.cpp @@ -0,0 +1,730 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// PJSON-SCHEMA-000..003: strict fail-closed subset mode plus the Draft 2020-12 +// applicator keywords added to the validator (if/then/else, prefixItems, +// contains/minContains/maxContains, dependentSchemas). +// +#include "pjson.h" +#include "pjson_parser.h" +#include "test_harness.h" +#include "test_util.h" + +#include +#include +#include +#include +#include + +using namespace ByteDance; + +namespace { + + bool validates(const char* schemaText, const char* dataText, + const pjson_test::SchemaOptions& opts = pjson_test::SchemaOptions()) { + pjson_test::Parsed schema = pjson_test::parse(schemaText); + pjson_test::Parsed data = pjson_test::parse(dataText); + if (!schema || !data) + return false; + return pjson_test::schemaValidate(*data, *schema, opts); + } + + struct ResolverFixture { + std::map documents; + size_t calls; + ResolverFixture() + : calls(0) {} + }; + + struct CountingAllocator : pjson::Allocator { + size_t allocations; + size_t deallocations; + CountingAllocator() + : allocations(0) + , deallocations(0) {} + void* allocate(size_t size, size_t, AllocationKind) override { + ++allocations; + return ::operator new(size); + } + void deallocate(void* pointer, size_t, size_t, AllocationKind) noexcept override { + ++deallocations; + ::operator delete(pointer); + } + }; + + bool resolveFixture(const std::string& uri, pjson& output, void* context) { + ResolverFixture& fixture = *static_cast(context); + ++fixture.calls; + std::map::const_iterator found = fixture.documents.find(uri); + if (found == fixture.documents.end()) + return false; + output.copyFrom(found->second); + return true; + } + + bool throwingResolver(const std::string&, pjson&, void*) { + throw std::runtime_error("resolver failure"); + } + +} // namespace + +//===----------------------------------------------------------------------===// +// if/then/else selects the correct branch (A.5 regression: previously ignored). +//===----------------------------------------------------------------------===// +TEST(schema_if_then_else) { + const char* schema = + "{\"if\":{\"properties\":{\"kind\":{\"const\":\"a\"}},\"required\":[\"kind\"]}," + "\"then\":{\"required\":[\"a_field\"]}," + "\"else\":{\"required\":[\"other_field\"]}}"; + + // kind == "a" but missing a_field -> then-branch fails. + CHECK(!validates(schema, "{\"kind\":\"a\"}")); + // kind == "a" with a_field -> passes. + CHECK(validates(schema, "{\"kind\":\"a\",\"a_field\":1}")); + // kind != "a" -> else-branch requires other_field. + CHECK(!validates(schema, "{\"kind\":\"b\"}")); + CHECK(validates(schema, "{\"kind\":\"b\",\"other_field\":1}")); +} + +//===----------------------------------------------------------------------===// +// prefixItems constrains leading positions; items constrains the rest. +//===----------------------------------------------------------------------===// +TEST(schema_prefix_items_and_items) { + const char* schema = "{\"prefixItems\":[{\"type\":\"string\"},{\"type\":\"integer\"}]," + "\"items\":{\"type\":\"boolean\"}}"; + + CHECK(validates(schema, "[\"x\", 1]")); + CHECK(validates(schema, "[\"x\", 1, true, false]")); + // Third element must be boolean. + CHECK(!validates(schema, "[\"x\", 1, 2]")); + // First element must be a string. + CHECK(!validates(schema, "[3, 1]")); +} + +//===----------------------------------------------------------------------===// +// contains / minContains / maxContains. +//===----------------------------------------------------------------------===// +TEST(schema_contains_bounds) { + const char* schema = + "{\"contains\":{\"type\":\"integer\"},\"minContains\":2,\"maxContains\":3}"; + + CHECK(!validates(schema, "[\"a\", \"b\"]")); // 0 integers, below min + CHECK(!validates(schema, "[1, \"a\"]")); // 1 integer, below min + CHECK(validates(schema, "[1, 2, \"a\"]")); // 2 integers, ok + CHECK(validates(schema, "[1, 2, 3]")); // 3 integers, ok + CHECK(!validates(schema, "[1, 2, 3, 4]")); // 4 integers, above max +} + +//===----------------------------------------------------------------------===// +// dependentSchemas applies a subschema when a triggering property is present. +//===----------------------------------------------------------------------===// +TEST(schema_dependent_schemas) { + const char* schema = "{\"dependentSchemas\":{\"card\":{\"required\":[\"billing_address\"]}}}"; + + CHECK(validates(schema, "{}")); // no trigger + CHECK(validates(schema, "{\"card\":1,\"billing_address\":\"x\"}")); + CHECK(!validates(schema, "{\"card\":1}")); // trigger without dependency +} + +//===----------------------------------------------------------------------===// +// PJSON-SCHEMA-000: strict subset mode fails closed on an unsupported standard +// keyword, while permissive (default) mode ignores it. +//===----------------------------------------------------------------------===// +TEST(schema_strict_mode_fails_on_unsupported_standard_keyword) { + // contentSchema is a standard 2020-12 keyword pjson does not enforce. + const char* schema = "{\"type\":\"object\",\"contentSchema\":false}"; + const char* data = "{\"extra\":1}"; + + // Permissive default: the unsupported keyword is ignored, so this passes. + CHECK(validates(schema, data)); + + // Strict: the unsupported standard keyword makes validation fail closed. + pjson_test::SchemaOptions strict = pjson_test::SchemaOptions::strict(); + CHECK(!validates(schema, data, strict)); +} + +//===----------------------------------------------------------------------===// +// Strict mode still permits unknown non-standard extension keywords. +//===----------------------------------------------------------------------===// +TEST(schema_strict_mode_allows_extension_keywords) { + const char* schema = "{\"type\":\"string\",\"x-vendor-hint\":\"anything\"}"; + pjson_test::SchemaOptions strict = pjson_test::SchemaOptions::strict(); + CHECK(validates(schema, "\"hello\"", strict)); + CHECK(!validates(schema, "42", strict)); // the supported keyword still applies +} + +//===----------------------------------------------------------------------===// +// PJSON-SCHEMA-001: explicit dialect and vocabulary contract. +//===----------------------------------------------------------------------===// +TEST(schema_documented_subset_dialect_is_explicit_and_reusable) { + pjson schema; + schema["$schema"] = pJsonSchemaValidator::documentedSubsetDialectUri(); + schema["type"] = "integer"; + + pJsonSchemaValidator validator(schema); + CHECK(validator.isSchemaValid()); + CHECK(validator.schemaErrors().empty()); + CHECK_EQ(validator.dialect(), std::string(pJsonSchemaValidator::documentedSubsetDialectUri())); + + pjson integerValue; + integerValue = int64_t(7); + pjson stringValue; + stringValue = "seven"; + CHECK(validator.validate(integerValue)); + CHECK(!validator.validate(stringValue)); +} + +TEST(schema_unsupported_declared_dialect_fails_compilation) { + pjson schema; + schema["$schema"] = "https://json-schema.org/draft/2020-12/schema"; + schema["type"] = "integer"; + pJsonSchemaValidator validator(schema); + + CHECK(!validator.isSchemaValid()); + CHECK_EQ(validator.schemaErrors().size(), size_t(1)); + CHECK_EQ(validator.schemaErrors()[0].category, pJsonSchemaValidator::Error::SchemaCompilation); + CHECK_EQ(validator.schemaErrors()[0].code, pJsonSchemaValidator::Error::UnsupportedDialect); + CHECK_EQ(validator.schemaErrors()[0].schemaLocation, std::string("/$schema")); + CHECK_EQ(validator.schemaErrors()[0].keyword, std::string("$schema")); + + pjson value; + value = int64_t(7); + std::vector errors; + CHECK(!validator.validate(value, errors)); + CHECK_EQ(errors.size(), size_t(1)); + CHECK_EQ(errors[0].category, pJsonSchemaValidator::Error::SchemaCompilation); +} + +TEST(schema_draft2020_preset_accepts_official_dialect) { + pjson schema = pJsonParser().parse( + R"({"$schema":"https://json-schema.org/draft/2020-12/schema","type":"integer"})"); + pJsonSchemaValidator validator(schema, pJsonSchemaValidator::Options::draft2020()); + CHECK(validator.isSchemaValid()); + CHECK_EQ(validator.dialect(), std::string(pJsonSchemaValidator::draft2020DialectUri())); + pjson integerValue; + integerValue = int64_t(7); + pjson stringValue; + stringValue = "seven"; + CHECK(validator.validate(integerValue)); + CHECK(!validator.validate(stringValue)); +} + +TEST(schema_draft2020_preset_rejects_meta_schema_violation) { + pjson schema = pJsonParser().parse( + R"({"$schema":"https://json-schema.org/draft/2020-12/schema","$defs":{"bad":{"type":1}}})"); + pJsonSchemaValidator validator(schema, pJsonSchemaValidator::Options::draft2020()); + CHECK(!validator.isSchemaValid()); + CHECK(!validator.schemaErrors().empty()); + CHECK_EQ(validator.schemaErrors()[0].category, pJsonSchemaValidator::Error::SchemaCompilation); + CHECK(validator.schemaErrors()[0].schemaLocation.find("/$defs/bad/type") != std::string::npos); +} + +TEST(schema_custom_dialect_controls_validation_vocabulary) { + const std::string dialect = "https://example.test/meta/no-validation"; + ResolverFixture fixture; + fixture.documents[dialect] = pJsonParser().parse( + R"({"$id":"https://example.test/meta/no-validation","$vocabulary":{"https://json-schema.org/draft/2020-12/vocab/core":true,"https://json-schema.org/draft/2020-12/vocab/applicator":true}})"); + pjson schema = pJsonParser().parse( + R"({"$schema":"https://example.test/meta/no-validation","properties":{"blocked":false,"number":{"minimum":10}}})"); + pJsonSchemaValidator::Options options = pJsonSchemaValidator::Options::draft2020(); + options.resolver = resolveFixture; + options.resolverContext = &fixture; + pJsonSchemaValidator validator(schema, options); + CHECK(validator.isSchemaValid()); + CHECK_EQ(fixture.calls, size_t(1)); + CHECK(!validator.validate(pJsonParser().parse(R"({"blocked":1})"))); + CHECK(validator.validate(pJsonParser().parse(R"({"number":1})"))); +} + +TEST(schema_custom_dialect_is_charged_once_as_a_resolved_document) { + const std::string dialect = "https://example.test/meta/small"; + ResolverFixture fixture; + fixture.documents[dialect] = pJsonParser().parse( + R"({"$id":"https://example.test/meta/small","$vocabulary":{"https://json-schema.org/draft/2020-12/vocab/core":true,"https://json-schema.org/draft/2020-12/vocab/validation":true},"type":["object","boolean"]})"); + pjson schema = + pJsonParser().parse(R"({"$schema":"https://example.test/meta/small","type":"integer"})"); + pJsonSchemaValidator::Options options = pJsonSchemaValidator::Options::draft2020(); + options.resolver = resolveFixture; + options.resolverContext = &fixture; + options.maxResolvedDocuments = 1; + pJsonSchemaValidator validator(schema, options); + CHECK(validator.isSchemaValid()); + CHECK_EQ(fixture.calls, size_t(1)); + pjson value; + value = int64_t(3); + CHECK(validator.validate(value)); +} + +TEST(schema_invalid_root_dialect_does_not_invoke_resolver) { + ResolverFixture fixture; + pjson schema = pJsonParser().parse( + R"({"$schema":"urn:unsupported","$ref":"https://example.test/remote.json"})"); + pJsonSchemaValidator::Options options; + options.resolver = resolveFixture; + options.resolverContext = &fixture; + pJsonSchemaValidator validator(schema, options); + CHECK(!validator.isSchemaValid()); + CHECK_EQ(fixture.calls, size_t(0)); +} + +TEST(schema_unsupported_default_dialect_fails_when_schema_omits_schema_keyword) { + pjson schema; + schema["type"] = "integer"; + pJsonSchemaValidator::Options options; + options.defaultDialectUri = "urn:example:unsupported-dialect"; + pJsonSchemaValidator validator(schema, options); + CHECK(!validator.isSchemaValid()); + CHECK_EQ(validator.dialect(), std::string("urn:example:unsupported-dialect")); +} + +TEST(schema_vocabulary_contract_accepts_supported_and_optional_unknown) { + pjson schema; + schema["$schema"] = pJsonSchemaValidator::documentedSubsetDialectUri(); + schema["$vocabulary"][pJsonSchemaValidator::documentedSubsetVocabularyUri()] = true; + schema["$vocabulary"]["urn:example:optional-annotations"] = false; + schema["type"] = "string"; + + pJsonSchemaValidator validator(schema); + CHECK(validator.isSchemaValid()); + pjson value; + value = "ok"; + CHECK(validator.validate(value)); +} + +TEST(schema_vocabulary_contract_rejects_unknown_required_vocabulary) { + pjson schema; + schema["$schema"] = pJsonSchemaValidator::documentedSubsetDialectUri(); + schema["$vocabulary"]["urn:example:required-but-unsupported"] = true; + pJsonSchemaValidator validator(schema); + + CHECK(!validator.isSchemaValid()); + CHECK_EQ(validator.schemaErrors().size(), size_t(1)); + CHECK(validator.schemaErrors()[0].message.find("unsupported required") != std::string::npos); +} + +TEST(schema_vocabulary_contract_accepts_supported_required_vocabulary) { + pjson schema; + schema["$vocabulary"][pJsonSchemaValidator::documentedSubsetVocabularyUri()] = true; + schema["minimum"] = int64_t(10); + pJsonSchemaValidator validator(schema); + CHECK(validator.isSchemaValid()); + + pjson below; + below = int64_t(9); + CHECK(!validator.validate(below)); +} + +TEST(schema_empty_default_dialect_selects_documented_subset) { + pjson schema; + pJsonSchemaValidator::Options options; + options.defaultDialectUri.clear(); + pJsonSchemaValidator validator(schema, options); + CHECK(validator.isSchemaValid()); + CHECK_EQ(validator.dialect(), std::string(pJsonSchemaValidator::documentedSubsetDialectUri())); +} + +TEST(schema_dialect_and_vocabulary_shapes_are_compilation_errors) { + pjson badDialect; + badDialect["$schema"] = int64_t(202012); + pJsonSchemaValidator dialectValidator(badDialect); + CHECK(!dialectValidator.isSchemaValid()); + + pjson badVocabulary; + badVocabulary["$vocabulary"] = "not-an-object"; + pJsonSchemaValidator vocabularyValidator(badVocabulary); + CHECK(!vocabularyValidator.isSchemaValid()); + + pjson badEntry; + badEntry["$vocabulary"]["urn:example:vocabulary"] = "required"; + pJsonSchemaValidator entryValidator(badEntry); + CHECK(!entryValidator.isSchemaValid()); + + pjson nonSchema; + nonSchema = int64_t(7); + pJsonSchemaValidator::Options strict = pJsonSchemaValidator::Options::strict(); + pJsonSchemaValidator nonSchemaValidator(nonSchema, strict); + CHECK(!nonSchemaValidator.isSchemaValid()); +} + +TEST(schema_reference_and_anchor_shapes_fail_validation_safely) { + pjson value; + for (const char* schemaText : {R"({"$ref":1})", R"({"$dynamicRef":false})", R"({"$id":[]})", + R"({"$anchor":"bad/name"})", R"({"$dynamicAnchor":""})"}) { + pjson schema = pJsonParser().parse(schemaText); + pJsonSchemaValidator validator(schema); + std::vector errors; + CHECK(!validator.validate(value, errors)); + CHECK(!errors.empty()); + } + + pjson nonSchemaTarget = pJsonParser().parse(R"({"$ref":"#/$defs/value","$defs":{"value":7}})"); + pJsonSchemaValidator::Options strict = pJsonSchemaValidator::Options::strict(); + pJsonSchemaValidator targetValidator(nonSchemaTarget, strict); + CHECK(!targetValidator.isSchemaValid()); +} + +TEST(schema_strict_mode_rejects_every_supported_keyword_shape) { + struct ShapeCase { + const char* schema; + const char* location; + const char* keyword; + pJsonSchemaValidator::Error::Code code; + }; + const ShapeCase cases[] = { + {R"({"type":"unknown"})", "/type", "type", pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"type":["string","string"]})", "/type", "type", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"enum":[]})", "/enum", "enum", pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"enum":[1,1.0]})", "/enum", "enum", pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"pattern":3})", "/pattern", "pattern", pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"pattern":"["})", "/pattern", "pattern", pJsonSchemaValidator::Error::RegexFailure}, + {R"({"format":false})", "/format", "format", pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"uniqueItems":1})", "/uniqueItems", "uniqueItems", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"minimum":"0"})", "/minimum", "minimum", pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"multipleOf":0})", "/multipleOf", "multipleOf", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"minLength":-1})", "/minLength", "minLength", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"maxItems":1.5})", "/maxItems", "maxItems", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"additionalProperties":7})", "/additionalProperties", "additionalProperties", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"items":"schema"})", "/items", "items", pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"allOf":[]})", "/allOf", "allOf", pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"anyOf":[true,7]})", "/anyOf/1", "anyOf", pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"prefixItems":false})", "/prefixItems", "prefixItems", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"prefixItems":[]})", "/prefixItems", "prefixItems", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"properties":[]})", "/properties", "properties", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"properties":{"x":7}})", "/properties/x", "properties", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"required":["x","x"]})", "/required", "required", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"dependentRequired":{"x":["y","y"]}})", "/dependentRequired/x", "dependentRequired", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"dependencies":{"x":7}})", "/dependencies/x", "dependencies", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"examples":false})", "/examples", "examples", + pJsonSchemaValidator::Error::InvalidSchema}, + {R"({"contentSchema":{}})", "/contentSchema", "contentSchema", + pJsonSchemaValidator::Error::UnsupportedKeyword}, + }; + + const pJsonSchemaValidator::Options strict = pJsonSchemaValidator::Options::strict(); + for (const ShapeCase& test : cases) { + const pjson schema = pJsonParser().parse(test.schema); + const pJsonSchemaValidator validator(schema, strict); + CHECK(!validator.isSchemaValid()); + CHECK(!validator.schemaErrors().empty()); + if (!validator.schemaErrors().empty()) { + CHECK_EQ(validator.schemaErrors()[0].code, test.code); + CHECK_EQ(validator.schemaErrors()[0].schemaLocation, std::string(test.location)); + CHECK_EQ(validator.schemaErrors()[0].keyword, std::string(test.keyword)); + } + } +} + +TEST(schema_permissive_mode_still_ignores_malformed_keyword_shapes) { + pjson schema = pJsonParser().parse( + R"({"type":7,"required":false,"properties":[],"allOf":false,"minimum":"zero"})"); + pJsonSchemaValidator validator(schema); + CHECK(validator.isSchemaValid()); + pjson value; + value = "accepted"; + CHECK(validator.validate(value)); +} + +TEST(schema_ids_inside_instance_valued_keywords_are_not_indexed) { + pjson schema = pJsonParser().parse( + R"({"const":{"$id":"https://example.test/not-a-schema","value":1},"$defs":{"actual":{"$id":"https://example.test/not-a-schema","type":"integer"}}})"); + pJsonSchemaValidator validator(schema); + pjson equalValue = + pJsonParser().parse(R"({"$id":"https://example.test/not-a-schema","value":1})"); + CHECK(validator.validate(equalValue)); +} + +TEST(schema_compilation_depth_is_bounded_for_programmatic_schemas) { + pjson schema; + pjson* cursor = &schema; + for (size_t i = 0; i < 1000; ++i) + cursor = &((*cursor)["allOf"][0]); + pJsonSchemaValidator validator(schema); + CHECK(!validator.isSchemaValid()); + CHECK(!validator.schemaErrors().empty()); + CHECK(validator.schemaErrors()[0].message.find("compilation depth") != std::string::npos); +} + +TEST(schema_duplicate_resource_ids_and_anchors_are_rejected) { + pjson duplicateId = pJsonParser().parse( + R"({"$id":"https://example.test/root","$defs":{"a":{"$id":"child"},"b":{"$id":"child"}}})"); + pJsonSchemaValidator idValidator(duplicateId); + CHECK(!idValidator.isSchemaValid()); + + pjson duplicateAnchor = + pJsonParser().parse(R"({"$defs":{"a":{"$anchor":"same"},"b":{"$anchor":"same"}}})"); + pJsonSchemaValidator anchorValidator(duplicateAnchor); + CHECK(!anchorValidator.isSchemaValid()); + + pjson malformedAnchor = pJsonParser().parse(R"({"$defs":{"a":{"$anchor":7}}})"); + pJsonSchemaValidator malformedAnchorValidator(malformedAnchor); + CHECK(!malformedAnchorValidator.isSchemaValid()); +} + +//===----------------------------------------------------------------------===// +// PJSON-SCHEMA-004: URI resources, anchors, dynamic references, and explicit +// resolver callbacks. pjson never performs implicit I/O. +//===----------------------------------------------------------------------===// +TEST(schema_anchor_and_nested_id_resolution) { + CHECK(validates( + R"({"$ref":"#integer","$defs":{"value":{"$anchor":"integer","type":"integer"}}})", "7")); + CHECK(!validates( + R"({"$ref":"#integer","$defs":{"value":{"$anchor":"integer","type":"integer"}}})", + R"("seven")")); + + const char* nested = + R"({"$id":"https://example.test/root.json","$ref":"nested.json#value","$defs":{"nested":{"$id":"nested.json","$defs":{"v":{"$anchor":"value","type":"string"}}}}})"; + CHECK(validates(nested, R"("ok")")); + CHECK(!validates(nested, "9")); + + const char* encodedPointer = + R"({"$ref":"#%2F$defs%2Fvalue","$defs":{"value":{"type":"boolean"}}})"; + CHECK(validates(encodedPointer, "true")); + CHECK(!validates(encodedPointer, "0")); +} + +TEST(schema_external_resolver_and_fragment) { + ResolverFixture fixture; + fixture.documents["https://example.test/remote.json"] = pJsonParser().parse( + R"({"$id":"https://example.test/remote.json","$defs":{"value":{"type":"integer"}}})"); + + pjson schema = + pJsonParser().parse(R"({"$ref":"https://example.test/remote.json#/$defs/value"})"); + pJsonSchemaValidator::Options options; + options.resolver = resolveFixture; + options.resolverContext = &fixture; + pJsonSchemaValidator validator(schema, options); + CHECK_EQ(fixture.calls, size_t(1)); + CHECK(validator.options().resolver == nullptr); + CHECK(validator.options().resolverContext == nullptr); + + pjson valid; + valid = int64_t(5); + pjson invalid; + invalid = "five"; + CHECK(validator.validate(valid)); + std::vector errors; + CHECK(!validator.validate(invalid, errors)); + CHECK_EQ(errors.size(), size_t(1)); + CHECK_EQ(errors[0].code, pJsonSchemaValidator::Error::TypeMismatch); + CHECK_EQ(errors[0].instanceLocation, std::string()); + CHECK_EQ(errors[0].schemaLocation, + std::string("https://example.test/remote.json#/$defs/value/type")); + CHECK_EQ(errors[0].keyword, std::string("type")); + CHECK_EQ(fixture.calls, size_t(1)); // resolved once during construction +} + +TEST(schema_retrieval_uri_resolves_relative_root_reference) { + ResolverFixture fixture; + fixture.documents["https://example.test/schemas/remote.json"] = + pJsonParser().parse(R"({"type":"integer"})"); + pjson schema = pJsonParser().parse(R"({"$ref":"remote.json"})"); + pJsonSchemaValidator::Options options; + options.retrievalUri = "https://example.test/schemas/root.json"; + options.resolver = resolveFixture; + options.resolverContext = &fixture; + pJsonSchemaValidator validator(schema, options); + CHECK(validator.isSchemaValid()); + CHECK_EQ(fixture.calls, size_t(1)); + pjson integerValue; + integerValue = int64_t(1); + CHECK(validator.validate(integerValue)); + + pjson noBaseSchema = pJsonParser().parse(R"({"$ref":"remote.json"})"); + pJsonSchemaValidator noBase(noBaseSchema, options); + // `options` supplies retrievalUri here, so this remains valid. + CHECK(noBase.isSchemaValid()); + pJsonSchemaValidator::Options missingBaseOptions; + missingBaseOptions.resolver = resolveFixture; + missingBaseOptions.resolverContext = &fixture; + pJsonSchemaValidator missingBase(noBaseSchema, missingBaseOptions); + CHECK(!missingBase.isSchemaValid()); +} + +TEST(schema_reference_resolution_normalizes_dot_segments) { + ResolverFixture fixture; + fixture.documents["https://example.test/schemas/remote.json"] = + pJsonParser().parse(R"({"type":"integer"})"); + pjson schema = pJsonParser().parse(R"({"$ref":"./defs/../remote.json"})"); + pJsonSchemaValidator::Options options; + options.retrievalUri = "https://example.test/schemas/root.json"; + options.resolver = resolveFixture; + options.resolverContext = &fixture; + pJsonSchemaValidator validator(schema, options); + CHECK(validator.isSchemaValid()); + CHECK_EQ(fixture.calls, size_t(1)); + pjson value; + value = int64_t(1); + CHECK(validator.validate(value)); +} + +TEST(schema_retrieval_uri_applies_relative_root_id_once) { + pjson schema = pJsonParser().parse( + R"({"$id":"sub/root.json","$ref":"#value","$defs":{"v":{"$anchor":"value","type":"string"}}})"); + pJsonSchemaValidator::Options options; + options.retrievalUri = "https://example.test/schemas/source.json"; + pJsonSchemaValidator validator(schema, options); + CHECK(validator.isSchemaValid()); + pjson value; + value = "ok"; + CHECK(validator.validate(value)); +} + +TEST(schema_external_resolution_is_explicit_and_budgeted) { + pjson schema = pJsonParser().parse(R"({"$ref":"https://example.test/remote.json"})"); + pJsonSchemaValidator noResolver(schema); + pjson value; + std::vector errors; + CHECK(!noResolver.isSchemaValid()); + CHECK(!noResolver.validate(value, errors)); + CHECK(!errors.empty()); + CHECK(errors[0].message.find("no resolver") != std::string::npos); + + ResolverFixture fixture; + fixture.documents["https://example.test/remote.json"] = + pJsonParser().parse(R"({"type":"null"})"); + pJsonSchemaValidator::Options options; + options.resolver = resolveFixture; + options.resolverContext = &fixture; + options.maxResolvedBytes = 1; + pJsonSchemaValidator limited(schema, options); + errors.clear(); + CHECK(!limited.validate(value, errors)); + CHECK(!errors.empty()); + CHECK(errors[0].message.find("resolved-byte budget") != std::string::npos); + + options.maxResolvedBytes = size_t(16) * 1024 * 1024; + options.maxResolvedDocuments = 1; + pjson twoDocuments = pJsonParser().parse( + R"({"allOf":[{"$ref":"https://example.test/one.json"},{"$ref":"https://example.test/two.json"}]})"); + fixture.documents["https://example.test/one.json"] = pJsonParser().parse("true"); + fixture.documents["https://example.test/two.json"] = pJsonParser().parse("true"); + pJsonSchemaValidator documentLimited(twoDocuments, options); + CHECK(!documentLimited.isSchemaValid()); + CHECK(documentLimited.schemaErrors()[0].message.find("resolved-document budget") != + std::string::npos); +} + +TEST(schema_external_resolver_exception_becomes_compilation_error) { + pjson schema = pJsonParser().parse(R"({"$ref":"https://example.test/remote.json"})"); + pJsonSchemaValidator::Options options; + options.resolver = throwingResolver; + bool caught = false; + try { + pJsonSchemaValidator validator(schema, options); + CHECK(!validator.isSchemaValid()); + } catch (...) { + caught = true; + } + CHECK(!caught); +} + +TEST(schema_external_resource_with_unsupported_dialect_fails_compilation) { + ResolverFixture fixture; + fixture.documents["https://example.test/remote.json"] = pJsonParser().parse( + R"({"$schema":"https://json-schema.org/draft/2020-12/schema","type":"integer"})"); + pjson schema = pJsonParser().parse(R"({"$ref":"https://example.test/remote.json"})"); + pJsonSchemaValidator::Options options; + options.resolver = resolveFixture; + options.resolverContext = &fixture; + pJsonSchemaValidator validator(schema, options); + CHECK(!validator.isSchemaValid()); + CHECK(!validator.schemaErrors().empty()); + CHECK_EQ(validator.schemaErrors()[0].schemaLocation, + std::string("https://example.test/remote.json#/$schema")); + + fixture.documents["https://example.test/remote.json"] = + pJsonParser().parse(R"({"$vocabulary":{"urn:example:required":true}})"); + pJsonSchemaValidator vocabularyValidator(schema, options); + CHECK(!vocabularyValidator.isSchemaValid()); +} + +TEST(schema_validator_owns_schema_beyond_caller_allocator_lifetime) { + pJsonSchemaValidator* validator = nullptr; + { + CountingAllocator allocator; + pJsonParser::Error error; + pjson schema = pJsonParser(allocator).parse(R"({"type":"integer"})", error); + CHECK(error.ok); + validator = new pJsonSchemaValidator(schema); + CHECK(&validator->schema().getAllocator() != &allocator); + } + pjson value; + value = int64_t(4); + CHECK(validator->validate(value)); + delete validator; +} + +TEST(schema_compiled_validator_supports_concurrent_read_only_validation) { + pjson schema = pJsonParser().parse( + R"({"type":"object","properties":{"value":{"type":"integer"}},"required":["value"],"unevaluatedProperties":false})"); + const pJsonSchemaValidator validator(schema, pJsonSchemaValidator::Options::modernSubset()); + bool results[8] = {false, false, false, false, false, false, false, false}; + std::vector threads; + for (size_t i = 0; i < 8; ++i) { + threads.push_back(std::thread([&validator, &results, i]() { + pjson instance; + instance["value"] = int64_t(i); + std::vector errors; + results[i] = validator.validate(instance, errors) && errors.empty(); + })); + } + for (size_t i = 0; i < threads.size(); ++i) + threads[i].join(); + for (size_t i = 0; i < 8; ++i) + CHECK(results[i]); +} + +TEST(schema_dynamic_ref_uses_outer_dynamic_anchor) { + const char* schema = + R"({"$id":"https://example.test/strict-tree","$dynamicAnchor":"node","type":"object","properties":{"value":{"type":"integer"},"child":{"$dynamicRef":"#node"}},"required":["value"],"additionalProperties":false})"; + CHECK(validates(schema, R"({"value":1,"child":{"value":2}})")); + CHECK(!validates(schema, R"({"value":1,"child":{"value":"bad"}})")); +} + +TEST(schema_unevaluated_properties_collects_successful_applicator_annotations) { + const char* schema = + R"({"allOf":[{"properties":{"a":{"type":"integer"}}}],"anyOf":[{"properties":{"b":{"type":"string"}}},{"properties":{"c":true}}],"unevaluatedProperties":false})"; + CHECK(validates(schema, R"({"a":1,"b":"ok","c":true})")); + CHECK(!validates(schema, R"({"a":1,"b":"ok","extra":true})")); +} + +TEST(schema_unevaluated_items_collects_prefix_contains_and_conditionals) { + const char* schema = + R"({"prefixItems":[{"type":"string"}],"contains":{"type":"integer"},"unevaluatedItems":false})"; + CHECK(validates(schema, R"(["head",1,2])")); + CHECK(!validates(schema, R"(["head",1,true])")); + + const char* conditional = R"({"if":{"prefixItems":[{"const":"a"}]},"unevaluatedItems":false})"; + CHECK(validates(conditional, R"(["a"])")); + CHECK(!validates(conditional, R"(["b"])")); +} + +TEST(schema_unevaluated_keywords_ignore_non_container_instances) { + CHECK(validates(R"({"unevaluatedProperties":false})", "7")); + CHECK(validates(R"({"unevaluatedItems":false})", R"("value")")); +} diff --git a/pjsontest/src/tests_schema_complex.cpp b/pjsontest/src/tests_schema_complex.cpp index 76c18a0..e2ba4ee 100644 --- a/pjsontest/src/tests_schema_complex.cpp +++ b/pjsontest/src/tests_schema_complex.cpp @@ -19,6 +19,7 @@ // #include "pjson.h" #include "test_harness.h" +#include "test_util.h" #include #include #include @@ -27,14 +28,14 @@ using namespace ByteDance; namespace { - pjson::unique_ptr parseJson(const char* text) { - return pjson::parse(std::string(text)); + pjson_test::Parsed parseJson(const char* text) { + return pjson_test::parse(std::string(text)); } // Returns true if some collected error has exactly this path. - bool hasErrorAt(const std::vector& errs, const std::string& path) { + bool hasErrorAt(const std::vector& errs, const std::string& path) { for (const auto& e : errs) { - if (e.path == path) + if (e.instanceLocation == path) return true; } return false; @@ -73,9 +74,9 @@ namespace { // A fully valid, deeply nested document passes with zero errors. //===----------------------------------------------------------------------===// TEST(complex_schema_valid_document) { - pjson::unique_ptr schema = parseJson(kPersonSchema); + pjson_test::Parsed schema = parseJson(kPersonSchema); CHECK(schema != nullptr); - pjson::unique_ptr data = parseJson(R"({ + pjson_test::Parsed data = parseJson(R"({ "id": 42, "name": "Ada Lovelace", "email": "ada@example.com", @@ -84,8 +85,8 @@ TEST(complex_schema_valid_document) { "address": { "city": "London", "zip": "12345" } })"); CHECK(data != nullptr); - std::vector errors; - CHECK(data->validate(*schema, errors)); + std::vector errors; + CHECK(pjson_test::schemaValidate(*data, *schema, errors)); CHECK_EQ(errors.size(), size_t(0)); } @@ -93,8 +94,8 @@ TEST(complex_schema_valid_document) { // Every violation across the tree is collected in a single pass. //===----------------------------------------------------------------------===// TEST(complex_schema_collects_all_violations) { - pjson::unique_ptr schema = parseJson(kPersonSchema); - pjson::unique_ptr data = parseJson(R"({ + pjson_test::Parsed schema = parseJson(kPersonSchema); + pjson_test::Parsed data = parseJson(R"({ "id": 0, "name": "", "email": "no-at-sign", @@ -104,8 +105,8 @@ TEST(complex_schema_collects_all_violations) { "extra": true })"); CHECK(data != nullptr); - std::vector errors; - CHECK(!data->validate(*schema, errors)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(*data, *schema, errors)); // Each independent problem should be reported with its own pointer path. CHECK(hasErrorAt(errors, "/id")); // below minimum 1 @@ -133,12 +134,12 @@ TEST(complex_schema_deep_pointer_path) { } } })"; - pjson::unique_ptr s = parseJson(schema); - pjson::unique_ptr d = parseJson(R"({ "matrix": [[1,2],[3,"bad"],[5]] })"); - std::vector errors; - CHECK(!d->validate(*s, errors)); + pjson_test::Parsed s = parseJson(schema); + pjson_test::Parsed d = parseJson(R"({ "matrix": [[1,2],[3,"bad"],[5]] })"); + std::vector errors; + CHECK(!pjson_test::schemaValidate(*d, *s, errors)); CHECK_EQ(errors.size(), size_t(1)); - CHECK_EQ(errors[0].path, std::string("/matrix/1/1")); + CHECK_EQ(errors[0].instanceLocation, std::string("/matrix/1/1")); } //===----------------------------------------------------------------------===// @@ -152,12 +153,12 @@ TEST(complex_schema_allof_object_constraints) { { "properties": { "a": { "type": "integer" } } } ] })"; - pjson::unique_ptr good = parseJson("{\"a\":5}"); - pjson::unique_ptr schemaValue = parseJson(schema); - CHECK(good->validate(*schemaValue)); - std::vector errors; - pjson::unique_ptr bad = parseJson("{\"a\":\"x\"}"); - CHECK(!bad->validate(*schemaValue, errors)); + pjson_test::Parsed good = parseJson("{\"a\":5}"); + pjson_test::Parsed schemaValue = parseJson(schema); + CHECK(pjson_test::schemaValidate(*good, *schemaValue)); + std::vector errors; + pjson_test::Parsed bad = parseJson("{\"a\":\"x\"}"); + CHECK(!pjson_test::schemaValidate(*bad, *schemaValue, errors)); CHECK(hasErrorAt(errors, "/a")); } @@ -168,25 +169,25 @@ TEST(complex_schema_anyof_branches) { { "required": ["b"] } ] })"; - pjson::unique_ptr schemaValue = parseJson(schema); - CHECK(parseJson("{\"a\":1}")->validate(*schemaValue)); - CHECK(parseJson("{\"b\":1}")->validate(*schemaValue)); - CHECK(!parseJson("{\"c\":1}")->validate(*schemaValue)); + pjson_test::Parsed schemaValue = parseJson(schema); + CHECK(pjson_test::schemaValidate(*parseJson("{\"a\":1}"), *schemaValue)); + CHECK(pjson_test::schemaValidate(*parseJson("{\"b\":1}"), *schemaValue)); + CHECK(!pjson_test::schemaValidate(*parseJson("{\"c\":1}"), *schemaValue)); } TEST(complex_schema_oneof_exactly_one) { // A value that satisfies two branches must FAIL oneOf. const char* schema = R"({ "oneOf": [ { "type": "number" }, { "type": "integer" } ] })"; - pjson::unique_ptr schemaValue = parseJson(schema); - CHECK(parseJson("2.5")->validate(*schemaValue)); // number only - CHECK(!parseJson("5")->validate(*schemaValue)); // both number and integer + pjson_test::Parsed schemaValue = parseJson(schema); + CHECK(pjson_test::schemaValidate(*parseJson("2.5"), *schemaValue)); // number only + CHECK(!pjson_test::schemaValidate(*parseJson("5"), *schemaValue)); // both number and integer } TEST(complex_schema_not_nested) { const char* schema = R"({ "not": { "required": ["forbidden"] } })"; - pjson::unique_ptr schemaValue = parseJson(schema); - CHECK(parseJson("{\"ok\":1}")->validate(*schemaValue)); - CHECK(!parseJson("{\"forbidden\":1}")->validate(*schemaValue)); + pjson_test::Parsed schemaValue = parseJson(schema); + CHECK(pjson_test::schemaValidate(*parseJson("{\"ok\":1}"), *schemaValue)); + CHECK(!pjson_test::schemaValidate(*parseJson("{\"forbidden\":1}"), *schemaValue)); } TEST(complex_schema_combinator_inside_properties) { @@ -195,11 +196,11 @@ TEST(complex_schema_combinator_inside_properties) { "val": { "anyOf": [ { "type": "string" }, { "type": "integer" } ] } } })"; - pjson::unique_ptr schemaValue = parseJson(schema); - CHECK(parseJson("{\"val\":\"x\"}")->validate(*schemaValue)); - CHECK(parseJson("{\"val\":7}")->validate(*schemaValue)); - std::vector errors; - CHECK(!parseJson("{\"val\":true}")->validate(*schemaValue, errors)); + pjson_test::Parsed schemaValue = parseJson(schema); + CHECK(pjson_test::schemaValidate(*parseJson("{\"val\":\"x\"}"), *schemaValue)); + CHECK(pjson_test::schemaValidate(*parseJson("{\"val\":7}"), *schemaValue)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(*parseJson("{\"val\":true}"), *schemaValue, errors)); CHECK(hasErrorAt(errors, "/val")); } @@ -207,18 +208,19 @@ TEST(complex_schema_combinator_inside_properties) { // Boolean sub-schemas. //===----------------------------------------------------------------------===// TEST(complex_schema_items_false_rejects_nonempty) { - pjson::unique_ptr schemaValue = parseJson(R"({"items":false})"); - CHECK(parseJson("[]")->validate(*schemaValue)); - std::vector errors; - CHECK(!parseJson("[1]")->validate(*schemaValue, errors)); - CHECK_EQ(errors[0].path, std::string("/0")); + pjson_test::Parsed schemaValue = parseJson(R"({"items":false})"); + CHECK(pjson_test::schemaValidate(*parseJson("[]"), *schemaValue)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(*parseJson("[1]"), *schemaValue, errors)); + CHECK_EQ(errors[0].instanceLocation, std::string("/0")); } TEST(complex_schema_property_true_false) { const char* schema = R"({ "properties": { "yes": true, "no": false } })"; - pjson::unique_ptr schemaValue = parseJson(schema); - CHECK(parseJson("{\"yes\":123}")->validate(*schemaValue)); // true accepts - CHECK(!parseJson("{\"no\":1}")->validate(*schemaValue)); // false rejects presence + pjson_test::Parsed schemaValue = parseJson(schema); + CHECK(pjson_test::schemaValidate(*parseJson("{\"yes\":123}"), *schemaValue)); // true accepts + CHECK(!pjson_test::schemaValidate(*parseJson("{\"no\":1}"), + *schemaValue)); // false rejects presence } //===----------------------------------------------------------------------===// @@ -226,10 +228,11 @@ TEST(complex_schema_property_true_false) { //===----------------------------------------------------------------------===// TEST(complex_schema_irrelevant_constraints_ignored) { // minItems on a number, minLength on an array, etc. do not fire. - CHECK(parseJson("5")->validate(*parseJson(R"({"minItems":3})"))); - CHECK(parseJson("[1]")->validate(*parseJson(R"({"minLength":3})"))); - CHECK(parseJson("\"hi\"")->validate(*parseJson(R"({"minimum":100})"))); - CHECK(parseJson("5")->validate( + CHECK(pjson_test::schemaValidate(*parseJson("5"), *parseJson(R"({"minItems":3})"))); + CHECK(pjson_test::schemaValidate(*parseJson("[1]"), *parseJson(R"({"minLength":3})"))); + CHECK(pjson_test::schemaValidate(*parseJson("\"hi\""), *parseJson(R"({"minimum":100})"))); + CHECK(pjson_test::schemaValidate( + *parseJson("5"), *parseJson(R"({"required":["a"]})"))); // required only checks objects } @@ -237,11 +240,11 @@ TEST(complex_schema_irrelevant_constraints_ignored) { // uniqueItems with deep (structural) comparison. //===----------------------------------------------------------------------===// TEST(complex_schema_unique_items_deep) { - pjson::unique_ptr schemaValue = parseJson(R"({"uniqueItems":true})"); - CHECK(parseJson("[[1,2],[1,3]]")->validate(*schemaValue)); - CHECK(!parseJson("[[1,2],[1,2]]")->validate(*schemaValue)); - CHECK(!parseJson(R"([{"a":1},{"a":1}])")->validate(*schemaValue)); - CHECK(parseJson(R"([{"a":1},{"a":2}])")->validate(*schemaValue)); + pjson_test::Parsed schemaValue = parseJson(R"({"uniqueItems":true})"); + CHECK(pjson_test::schemaValidate(*parseJson("[[1,2],[1,3]]"), *schemaValue)); + CHECK(!pjson_test::schemaValidate(*parseJson("[[1,2],[1,2]]"), *schemaValue)); + CHECK(!pjson_test::schemaValidate(*parseJson(R"([{"a":1},{"a":1}])"), *schemaValue)); + CHECK(pjson_test::schemaValidate(*parseJson(R"([{"a":1},{"a":2}])"), *schemaValue)); } //===----------------------------------------------------------------------===// @@ -249,58 +252,60 @@ TEST(complex_schema_unique_items_deep) { //===----------------------------------------------------------------------===// TEST(complex_schema_enum_structured) { const char* schema = R"({ "enum": [ {"a":1}, [1,2,3], "text" ] })"; - pjson::unique_ptr schemaValue = parseJson(schema); - CHECK(parseJson(R"({"a":1})")->validate(*schemaValue)); - CHECK(parseJson("[1,2,3]")->validate(*schemaValue)); - CHECK(parseJson("\"text\"")->validate(*schemaValue)); - CHECK(!parseJson("[1,2]")->validate(*schemaValue)); - CHECK(!parseJson(R"({"a":2})")->validate(*schemaValue)); + pjson_test::Parsed schemaValue = parseJson(schema); + CHECK(pjson_test::schemaValidate(*parseJson(R"({"a":1})"), *schemaValue)); + CHECK(pjson_test::schemaValidate(*parseJson("[1,2,3]"), *schemaValue)); + CHECK(pjson_test::schemaValidate(*parseJson("\"text\""), *schemaValue)); + CHECK(!pjson_test::schemaValidate(*parseJson("[1,2]"), *schemaValue)); + CHECK(!pjson_test::schemaValidate(*parseJson(R"({"a":2})"), *schemaValue)); } TEST(complex_schema_const_structured) { const char* schema = R"({ "const": { "nested": [1, {"x": true}] } })"; - pjson::unique_ptr schemaValue = parseJson(schema); - CHECK(parseJson(R"({"nested":[1,{"x":true}]})")->validate(*schemaValue)); - CHECK(!parseJson(R"({"nested":[1,{"x":false}]})")->validate(*schemaValue)); + pjson_test::Parsed schemaValue = parseJson(schema); + CHECK(pjson_test::schemaValidate(*parseJson(R"({"nested":[1,{"x":true}]})"), *schemaValue)); + CHECK(!pjson_test::schemaValidate(*parseJson(R"({"nested":[1,{"x":false}]})"), *schemaValue)); } //===----------------------------------------------------------------------===// // multipleOf with fractional divisors. //===----------------------------------------------------------------------===// TEST(complex_schema_multiple_of_fractions) { - CHECK(parseJson("0.3")->validate(*parseJson(R"({"multipleOf":0.1})"))); - CHECK(parseJson("15")->validate(*parseJson(R"({"multipleOf":5})"))); - CHECK(!parseJson("14")->validate(*parseJson(R"({"multipleOf":5})"))); + CHECK(pjson_test::schemaValidate(*parseJson("0.3"), *parseJson(R"({"multipleOf":0.1})"))); + CHECK(pjson_test::schemaValidate(*parseJson("15"), *parseJson(R"({"multipleOf":5})"))); + CHECK(!pjson_test::schemaValidate(*parseJson("14"), *parseJson(R"({"multipleOf":5})"))); // Divisor of zero is guarded (treated as no constraint). - CHECK(parseJson("5")->validate(*parseJson(R"({"multipleOf":0})"))); + CHECK(pjson_test::schemaValidate(*parseJson("5"), *parseJson(R"({"multipleOf":0})"))); } //===----------------------------------------------------------------------===// // The empty schema and unknown keywords accept everything. //===----------------------------------------------------------------------===// TEST(complex_schema_empty_and_unknown) { - CHECK(parseJson("5")->validate(*parseJson("{}"))); - CHECK(parseJson("[1,2,3]")->validate(*parseJson("{}"))); - CHECK(parseJson(R"({"a":1})") - ->validate(*parseJson(R"({"title":"ignored","description":"also ignored"})"))); + CHECK(pjson_test::schemaValidate(*parseJson("5"), *parseJson("{}"))); + CHECK(pjson_test::schemaValidate(*parseJson("[1,2,3]"), *parseJson("{}"))); + CHECK(pjson_test::schemaValidate( + *parseJson(R"({"a":1})"), + *parseJson(R"({"title":"ignored","description":"also ignored"})"))); // A schema-valued additionalProperties constraint applies to every key // not matched by properties or patternProperties. - CHECK(!parseJson(R"({"x":"str"})") - ->validate(*parseJson(R"({"additionalProperties":{"type":"integer"}})"))); - CHECK(parseJson(R"({"x":7})") - ->validate(*parseJson(R"({"additionalProperties":{"type":"integer"}})"))); + CHECK( + !pjson_test::schemaValidate(*parseJson(R"({"x":"str"})"), + *parseJson(R"({"additionalProperties":{"type":"integer"}})"))); + CHECK(pjson_test::schemaValidate(*parseJson(R"({"x":7})"), + *parseJson(R"({"additionalProperties":{"type":"integer"}})"))); } //===----------------------------------------------------------------------===// // A schema built programmatically behaves identically to a parsed one. //===----------------------------------------------------------------------===// TEST(complex_schema_built_vs_parsed_equivalent) { - pjson::unique_ptr parsed = parseJson(kPersonSchema); + pjson_test::Parsed parsed = parseJson(kPersonSchema); // Validate the same doc against both and compare pass/fail + error count. - pjson::unique_ptr data = parseJson(R"({ "id": 1, "name": "X", "email": "x@y" })"); - std::vector e1; - bool ok1 = data->validate(*parsed, e1); + pjson_test::Parsed data = parseJson(R"({ "id": 1, "name": "X", "email": "x@y" })"); + std::vector e1; + bool ok1 = pjson_test::schemaValidate(*data, *parsed, e1); CHECK(ok1); CHECK_EQ(e1.size(), size_t(0)); } @@ -312,11 +317,11 @@ TEST(complex_schema_type_union) { const char* schema = R"({ "properties": { "id": { "type": ["integer", "string"] } } })"; - pjson::unique_ptr schemaValue = parseJson(schema); - CHECK(parseJson(R"({"id":5})")->validate(*schemaValue)); - CHECK(parseJson(R"({"id":"abc"})")->validate(*schemaValue)); - std::vector errors; - CHECK(!parseJson(R"({"id":true})")->validate(*schemaValue, errors)); + pjson_test::Parsed schemaValue = parseJson(schema); + CHECK(pjson_test::schemaValidate(*parseJson(R"({"id":5})"), *schemaValue)); + CHECK(pjson_test::schemaValidate(*parseJson(R"({"id":"abc"})"), *schemaValue)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(*parseJson(R"({"id":true})"), *schemaValue, errors)); CHECK(hasErrorAt(errors, "/id")); } @@ -336,9 +341,9 @@ TEST(complex_schema_large_array_collects_per_element) { data[i] = int64_t(i); } } - pjson::unique_ptr schema = parseJson(R"({ "type": "array", "items": { "type": "integer" } })"); - std::vector errors; - CHECK(!data.validate(*schema, errors)); + pjson_test::Parsed schema = parseJson(R"({ "type": "array", "items": { "type": "integer" } })"); + std::vector errors; + CHECK(!pjson_test::schemaValidate(data, *schema, errors)); CHECK_EQ(errors.size(), static_cast(expectedBad)); // The first bad element is at index 0. CHECK(hasErrorAt(errors, "/0")); @@ -352,10 +357,10 @@ TEST(complex_schema_unique_items_exact_mixed_numeric_equality_beyond_2pow53) { pjson dataDistinct; dataDistinct[0] = int64_t(9007199254740993LL); dataDistinct[1] = double(9007199254740992.0); - CHECK(dataDistinct.validate(schema)); + CHECK(pjson_test::schemaValidate(dataDistinct, schema)); pjson dataEqual; dataEqual[0] = int64_t(9007199254740992LL); dataEqual[1] = double(9007199254740992.0); - CHECK(!dataEqual.validate(schema)); + CHECK(!pjson_test::schemaValidate(dataEqual, schema)); } diff --git a/pjsontest/src/tests_schema_official.cpp b/pjsontest/src/tests_schema_official.cpp index 4705b15..688f1eb 100644 --- a/pjsontest/src/tests_schema_official.cpp +++ b/pjsontest/src/tests_schema_official.cpp @@ -13,13 +13,14 @@ // limitations under the License. // //===----------------------------------------------------------------------===// -// Optional official draft-07 JSON-Schema-Test-Suite conformance integration. -// This harness intentionally uses an explicit -// manifest so unsupported files or groups are skipped with a concrete reason -// instead of disappearing through ad-hoc filtering. +// Optional official Draft 7 and Draft 2020-12 JSON-Schema-Test-Suite +// integration. Explicit manifests record every selected run/skip decision so +// unsupported files or groups cannot disappear through ad-hoc filtering. // #include "pjson.h" +#include "pjson_parser.h" #include "test_harness.h" +#include "test_util.h" #include #include @@ -46,11 +47,11 @@ using namespace ByteDance; namespace { - pjson::unique_ptr parseJson(const std::string& text, pjson::ParseError* error = NULL) { + pjson_test::Parsed parseJson(const std::string& text, pJsonParser::Error* error = NULL) { if (error != NULL) { - return pjson::parse(text, *error, pjson::ParseOptions()); + return pjson_test::parse(text, *error, pJsonParser::Options()); } - return pjson::parse(text, pjson::ParseOptions()); + return pjson_test::parse(text, pJsonParser::Options()); } // Every upstream file is either fully run, fully skipped, or filtered by named groups. @@ -136,6 +137,46 @@ namespace { #endif } + void listJsonFiles(const std::string& root, const std::string& relative, + std::vector& output) { + const std::string directory = relative.empty() ? root : joinPath(root, relative); +#if defined(_WIN32) + WIN32_FIND_DATAA entry; + const std::string pattern = joinPath(directory, "*"); + HANDLE handle = FindFirstFileA(pattern.c_str(), &entry); + if (handle == INVALID_HANDLE_VALUE) + return; + do { + const std::string name = entry.cFileName; + if (name == "." || name == "..") + continue; + const std::string child = relative.empty() ? name : relative + "/" + name; + if ((entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + listJsonFiles(root, child, output); + else if (name.size() >= 5 && name.substr(name.size() - 5) == ".json") + output.push_back(child); + } while (FindNextFileA(handle, &entry)); + FindClose(handle); +#else + DIR* handle = ::opendir(directory.c_str()); + if (handle == NULL) + return; + while (dirent* entry = ::readdir(handle)) { + const std::string name = entry->d_name; + if (name == "." || name == "..") + continue; + const std::string child = relative.empty() ? name : relative + "/" + name; + const std::string path = joinPath(root, child); + if (isDirectory(path)) + listJsonFiles(root, child, output); + else if (isRegularFile(path) && name.size() >= 5 && + name.substr(name.size() - 5) == ".json") + output.push_back(child); + } + ::closedir(handle); +#endif + } + std::string readFile(const std::string& path) { std::ifstream in(path.c_str(), std::ios::binary); if (!in) { @@ -153,6 +194,24 @@ namespace { return std::string(PJSON_TEST_DEFAULT_JSON_SCHEMA_TEST_SUITE_DIR); } + struct OfficialResolverContext { + std::string remoteRoot; + }; + + bool resolveOfficialSchema(const std::string& uri, pjson& output, void* opaque) { + OfficialResolverContext& context = *static_cast(opaque); + const std::string prefix = "http://localhost:1234/"; + if (uri.compare(0, prefix.size(), prefix) != 0) + return false; + const std::string relative = uri.substr(prefix.size()); + const std::string path = joinPath(context.remoteRoot, relative); + if (!isRegularFile(path)) + return false; + pJsonParser::Error error; + output = pJsonParser().parse(readFile(path), error); + return error.ok; + } + std::string resolveDraft7Dir() { const std::string configured = configuredSchemaSuiteDir(); if (configured.empty()) { @@ -171,6 +230,22 @@ namespace { return std::string(); } + // Draft 2020-12 lives under tests/draft2020-12 in the same pinned corpus. It + // shares the manifest-driven runner; only the directory and ledger differ. + std::string resolveDraft2020Dir() { + const std::string configured = configuredSchemaSuiteDir(); + if (configured.empty()) { + return std::string(); + } + if (isDirectory(joinPath(configured, "tests/draft2020-12"))) { + return joinPath(configured, "tests/draft2020-12"); + } + if (isDirectory(joinPath(configured, "draft2020-12"))) { + return joinPath(configured, "draft2020-12"); + } + return std::string(); + } + // Central compatibility ledger: unsupported upstream coverage is skipped with a reason, and // selected-group files fail if the upstream descriptions drift away from this manifest. std::vector manifest() { @@ -333,6 +408,478 @@ namespace { return rules; } + // Draft 2020-12 conformance ledger. Supported keyword files run whole; the + // remaining custom-meta-schema, Unicode \\p{} regex, and annotation-only + // format cases are skipped with a concrete reason. + std::vector manifest2020() { + std::vector rules; + FileRule r; + r = FileRule(); + r.relativePath = "additionalProperties.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "allOf.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "anchor.json"; + r.mode = RunWholeFile; + r.reason = "requires $anchor plus $id base resolution"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "anyOf.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "boolean_schema.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "const.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "contains.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "content.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "default.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "defs.json"; + r.mode = RunWholeFile; + r.reason = "bundled official Draft 2020-12 meta-schema"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "dependentRequired.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "dependentSchemas.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "dynamicRef.json"; + r.mode = RunSelectedGroups; + r.reason = ""; + r.groups.push_back(GroupRule{"A $dynamicRef to a $dynamicAnchor in the same schema " + "resource behaves like a normal $ref to an $anchor", + true, "supported"}); + r.groups.push_back(GroupRule{"A $dynamicRef to an $anchor in the same schema resource " + "behaves like a normal $ref to an $anchor", + true, "supported"}); + r.groups.push_back(GroupRule{"A $ref to a $dynamicAnchor in the same schema resource " + "behaves like a normal $ref to an $anchor", + true, "supported"}); + r.groups.push_back(GroupRule{"A $dynamicRef resolves to the first $dynamicAnchor still in " + "scope that is encountered when the schema is evaluated", + true, "supported"}); + r.groups.push_back( + GroupRule{"A $dynamicRef without anchor in fragment behaves identical to $ref", true, + "supported"}); + r.groups.push_back( + GroupRule{"A $dynamicRef with intermediate scopes that don't include a matching " + "$dynamicAnchor does not affect dynamic scope resolution", + true, "supported"}); + r.groups.push_back(GroupRule{"An $anchor with the same name as a $dynamicAnchor is not " + "used for dynamic scope resolution", + true, "supported"}); + r.groups.push_back(GroupRule{"A $dynamicRef without a matching $dynamicAnchor in the same " + "schema resource behaves like a normal $ref to $anchor", + true, "supported"}); + r.groups.push_back(GroupRule{"A $dynamicRef with a non-matching $dynamicAnchor in the same " + "schema resource behaves like a normal $ref to $anchor", + true, "supported"}); + r.groups.push_back( + GroupRule{"A $dynamicRef that initially resolves to a schema with a matching " + "$dynamicAnchor resolves to the first $dynamicAnchor in the dynamic scope", + true, "supported"}); + r.groups.push_back( + GroupRule{"A $dynamicRef that initially resolves to a schema without a matching " + "$dynamicAnchor behaves like a normal $ref to $anchor", + true, "supported"}); + r.groups.push_back( + GroupRule{"multiple dynamic paths to the $dynamicRef keyword", true, "supported"}); + r.groups.push_back(GroupRule{ + "after leaving a dynamic scope, it is not used by a $dynamicRef", true, "supported"}); + r.groups.push_back(GroupRule{"strict-tree schema, guards against misspelled properties", + true, "supported"}); + r.groups.push_back(GroupRule{"tests for implementation dynamic anchor and reference link", + true, "supported"}); + r.groups.push_back(GroupRule{ + "$ref and $dynamicAnchor are independent of order - $defs first", true, "supported"}); + r.groups.push_back(GroupRule{ + "$ref and $dynamicAnchor are independent of order - $ref first", true, "supported"}); + r.groups.push_back( + GroupRule{"$ref to $dynamicRef finds detached $dynamicAnchor", true, "supported"}); + r.groups.push_back(GroupRule{"$dynamicRef points to a boolean schema", true, "supported"}); + r.groups.push_back(GroupRule{ + "$dynamicRef skips over intermediate resources - direct reference", true, "supported"}); + r.groups.push_back( + GroupRule{"$dynamicRef avoids the root of each schema, but scopes are still registered", + true, "supported"}); + rules.push_back(r); + r = FileRule(); + r.relativePath = "enum.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "exclusiveMaximum.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "exclusiveMinimum.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "format.json"; + r.mode = RunSelectedGroups; + r.reason = ""; + r.groups.push_back(GroupRule{"email format", true, "supported"}); + r.groups.push_back(GroupRule{"idn-email format", true, "supported"}); + r.groups.push_back(GroupRule{"regex format", true, "supported"}); + r.groups.push_back( + GroupRule{"ipv4 format", true, "modern subset treats format as annotation-only"}); + r.groups.push_back( + GroupRule{"ipv6 format", true, "modern subset treats format as annotation-only"}); + r.groups.push_back(GroupRule{"idn-hostname format", true, "supported"}); + r.groups.push_back(GroupRule{"hostname format", true, "supported"}); + r.groups.push_back( + GroupRule{"date format", true, "modern subset treats format as annotation-only"}); + r.groups.push_back( + GroupRule{"date-time format", true, "modern subset treats format as annotation-only"}); + r.groups.push_back( + GroupRule{"time format", true, "modern subset treats format as annotation-only"}); + r.groups.push_back(GroupRule{"json-pointer format", true, "supported"}); + r.groups.push_back(GroupRule{"relative-json-pointer format", true, "supported"}); + r.groups.push_back(GroupRule{"iri format", true, "supported"}); + r.groups.push_back(GroupRule{"iri-reference format", true, "supported"}); + r.groups.push_back(GroupRule{"uri format", true, "supported"}); + r.groups.push_back(GroupRule{"uri-reference format", true, "supported"}); + r.groups.push_back(GroupRule{"uri-template format", true, "supported"}); + r.groups.push_back( + GroupRule{"uuid format", true, "modern subset treats format as annotation-only"}); + r.groups.push_back(GroupRule{"duration format", true, "supported"}); + rules.push_back(r); + r = FileRule(); + r.relativePath = "if-then-else.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "infinite-loop-detection.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "items.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "maxContains.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "maxItems.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "maxLength.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "maxProperties.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "maximum.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "minContains.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "minItems.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "minLength.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "minProperties.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "minimum.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "multipleOf.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "not.json"; + r.mode = RunSelectedGroups; + r.reason = ""; + r.groups.push_back(GroupRule{"not", true, "supported"}); + r.groups.push_back(GroupRule{"not multiple types", true, "supported"}); + r.groups.push_back(GroupRule{"not more complex schema", true, "supported"}); + r.groups.push_back(GroupRule{"forbidden property", true, "supported"}); + r.groups.push_back(GroupRule{"forbid everything with empty schema", true, "supported"}); + r.groups.push_back( + GroupRule{"forbid everything with boolean schema true", true, "supported"}); + r.groups.push_back( + GroupRule{"allow everything with boolean schema false", true, "supported"}); + r.groups.push_back(GroupRule{"double negation", true, "supported"}); + r.groups.push_back( + GroupRule{"collect annotations inside a 'not', even if collection is disabled", true, + "supported internal annotation evaluation"}); + rules.push_back(r); + r = FileRule(); + r.relativePath = "oneOf.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "pattern.json"; + r.mode = RunSelectedGroups; + r.reason = ""; + r.groups.push_back(GroupRule{"pattern validation", true, "supported"}); + r.groups.push_back(GroupRule{"pattern is not anchored", true, "supported"}); + r.groups.push_back(GroupRule{"pattern with Unicode property escape requires unicode mode", + true, "SRELL provides Unicode ECMAScript property escapes"}); + rules.push_back(r); + r = FileRule(); + r.relativePath = "patternProperties.json"; + r.mode = RunSelectedGroups; + r.reason = ""; + r.groups.push_back(GroupRule{"patternProperties validates properties matching a regex", + true, "supported"}); + r.groups.push_back( + GroupRule{"multiple simultaneous patternProperties are validated", true, "supported"}); + r.groups.push_back(GroupRule{"regexes are not anchored by default and are case sensitive", + true, "supported"}); + r.groups.push_back(GroupRule{"patternProperties with boolean schemas", true, "supported"}); + r.groups.push_back( + GroupRule{"patternProperties with null valued instance properties", true, "supported"}); + r.groups.push_back(GroupRule{"patternProperties with Unicode property escape", true, + "SRELL provides Unicode ECMAScript property escapes"}); + rules.push_back(r); + r = FileRule(); + r.relativePath = "prefixItems.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "properties.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "propertyNames.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "ref.json"; + r.mode = RunSelectedGroups; + r.reason = ""; + r.groups.push_back(GroupRule{"root pointer ref", true, "supported"}); + r.groups.push_back(GroupRule{"relative pointer ref to object", true, "supported"}); + r.groups.push_back(GroupRule{"relative pointer ref to array", true, "supported"}); + r.groups.push_back(GroupRule{"escaped pointer ref", true, "supported"}); + r.groups.push_back(GroupRule{"nested refs", true, "supported"}); + r.groups.push_back(GroupRule{"ref applies alongside sibling keywords", true, + "supported modern subset semantics"}); + r.groups.push_back(GroupRule{"remote ref, containing refs itself", true, + "bundled official Draft 2020-12 meta-schema"}); + r.groups.push_back( + GroupRule{"property named $ref that is not a reference", true, "supported"}); + r.groups.push_back( + GroupRule{"property named $ref, containing an actual $ref", true, "supported"}); + r.groups.push_back(GroupRule{"$ref to boolean schema true", true, "supported"}); + r.groups.push_back(GroupRule{"$ref to boolean schema false", true, "supported"}); + r.groups.push_back( + GroupRule{"Recursive references between schemas", true, "supported explicit resolver"}); + r.groups.push_back(GroupRule{"refs with quote", true, "supported"}); + r.groups.push_back( + GroupRule{"ref creates new scope when adjacent to keywords", true, "supported"}); + r.groups.push_back(GroupRule{ + "naive replacement of $ref with its destination is not correct", true, "supported"}); + r.groups.push_back(GroupRule{"refs with relative uris and defs", true, "supported"}); + r.groups.push_back( + GroupRule{"relative refs with absolute uris and defs", true, "supported"}); + r.groups.push_back( + GroupRule{"$id must be resolved against nearest parent, not just immediate parent", + true, "supported"}); + r.groups.push_back(GroupRule{"order of evaluation: $id and $ref", true, "supported"}); + r.groups.push_back( + GroupRule{"order of evaluation: $id and $anchor and $ref", true, "supported"}); + r.groups.push_back( + GroupRule{"order of evaluation: $id and $ref on nested schema", true, "supported"}); + r.groups.push_back( + GroupRule{"simple URN base URI with $ref via the URN", true, "supported"}); + r.groups.push_back(GroupRule{"simple URN base URI with JSON pointer", true, "supported"}); + r.groups.push_back(GroupRule{"URN base URI with NSS", true, "supported"}); + r.groups.push_back(GroupRule{"URN base URI with r-component", true, "supported"}); + r.groups.push_back(GroupRule{"URN base URI with q-component", true, "supported"}); + r.groups.push_back( + GroupRule{"URN base URI with URN and JSON pointer ref", true, "supported"}); + r.groups.push_back(GroupRule{"URN base URI with URN and anchor ref", true, "supported"}); + r.groups.push_back(GroupRule{"URN ref with nested pointer ref", true, "supported"}); + r.groups.push_back(GroupRule{"ref to if", true, "supported"}); + r.groups.push_back(GroupRule{"ref to then", true, "supported"}); + r.groups.push_back(GroupRule{"ref to else", true, "supported"}); + r.groups.push_back(GroupRule{"ref with absolute-path-reference", true, "supported"}); + r.groups.push_back( + GroupRule{"$id with file URI still resolves pointers - *nix", true, "supported"}); + r.groups.push_back( + GroupRule{"$id with file URI still resolves pointers - windows", true, "supported"}); + r.groups.push_back(GroupRule{"empty tokens in $ref json-pointer", true, "supported"}); + rules.push_back(r); + r = FileRule(); + r.relativePath = "refRemote.json"; + r.mode = RunWholeFile; + r.reason = "requires remote schema resolution"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "required.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "type.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "unevaluatedItems.json"; + r.mode = RunWholeFile; + r.reason = "supported unevaluated-item annotation propagation"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "unevaluatedProperties.json"; + r.mode = RunWholeFile; + r.reason = "supported unevaluated-property annotation propagation"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "uniqueItems.json"; + r.mode = RunWholeFile; + r.reason = "supported documented-subset keywords"; + rules.push_back(r); + r = FileRule(); + r.relativePath = "vocabulary.json"; + r.mode = RunSelectedGroups; + r.reason = ""; + r.groups.push_back( + GroupRule{"schema that uses custom metaschema with with no validation vocabulary", true, + "per-resource custom meta-schema vocabulary activation"}); + r.groups.push_back(GroupRule{"ignore unrecognized optional vocabulary", true, "supported"}); + rules.push_back(r); + + // Optional suites remain explicit as well. Running subsets that exercise + // already documented behavior prevents the small mandatory skip count + // from being mistaken for a full conformance denominator. + const auto addWhole = [&rules](const char* path, const char* reason) { + FileRule rule; + rule.relativePath = path; + rule.mode = RunWholeFile; + rule.reason = reason; + rules.push_back(rule); + }; + const auto addSkip = [&rules](const char* path, const char* reason) { + FileRule rule; + rule.relativePath = path; + rule.mode = SkipWholeFile; + rule.reason = reason; + rules.push_back(rule); + }; + addWhole("optional/anchor.json", "identifier isolation inside instance-valued keywords"); + addWhole("optional/dependencies-compatibility.json", + "supported legacy compatibility keyword"); + addWhole("optional/dynamicRef.json", "supported dynamic-scope behavior"); + addWhole("optional/float-overflow.json", "bounded binary64 arithmetic behavior"); + addWhole("optional/id.json", "identifier isolation inside instance-valued keywords"); + addWhole("optional/no-schema.json", "documented default dialect behavior"); + addWhole("optional/refOfUnknownKeyword.json", + "JSON Pointer references may target arbitrary schema-shaped locations"); + addWhole("optional/unknownKeyword.json", + "unknown-keyword contents are not traversed as schemas"); + + addSkip("optional/bignum.json", + "pjson intentionally rejects integers outside its signed/unsigned 64-bit model"); + addSkip("optional/cross-draft.json", + "historic JSON Schema dialect interpretation is not implemented"); + addWhole("optional/ecmascript-regex.json", + "SRELL Unicode ECMAScript regular-expression implementation"); + addWhole("optional/non-bmp-regex.json", + "SRELL Unicode code-point regular-expression semantics"); + addSkip("optional/format-assertion.json", + "complete optional format-assertion vocabulary is not implemented"); + + static const char* const kFormatSuites[] = { + "optional/format/duration.json", + "optional/format/email.json", + "optional/format/hostname.json", + "optional/format/idn-email.json", + "optional/format/idn-hostname.json", + "optional/format/iri-reference.json", + "optional/format/iri.json", + "optional/format/json-pointer.json", + "optional/format/relative-json-pointer.json", + "optional/format/uri-reference.json", + "optional/format/uri-template.json", + "optional/format/uri.json", + }; + for (size_t i = 0; i < sizeof(kFormatSuites) / sizeof(kFormatSuites[0]); ++i) + addSkip(kFormatSuites[i], + "Draft 2020-12 format assertions require vocabulary-controlled activation"); + addWhole("optional/format/regex.json", "supported asserted regex format"); + addWhole("optional/format/ecmascript-regex.json", + "SRELL parser with ECMA-262 extension restrictions"); + addWhole("optional/format/date.json", "supported date assertion"); + addWhole("optional/format/time.json", "supported time assertion"); + addWhole("optional/format/date-time.json", "supported date-time assertion"); + addWhole("optional/format/ipv4.json", "supported IPv4 assertion"); + addWhole("optional/format/ipv6.json", "supported IPv6 assertion"); + addWhole("optional/format/uuid.json", "supported UUID assertion"); + addWhole("optional/format/unknown.json", "unknown formats remain annotations"); + return rules; + } + // Manifest and diagnostic helpers used by the execution pipeline below. const GroupRule* findGroupRule(const FileRule& fileRule, const std::string& description) { for (size_t i = 0; i < fileRule.groups.size(); ++i) { @@ -361,15 +908,15 @@ namespace { return desc->tryGet(value) ? value : std::string(""); } - std::string firstErrorSummary(const std::vector& errors) { + std::string firstErrorSummary(const std::vector& errors) { if (errors.empty()) { return std::string("no schema errors reported"); } std::ostringstream os; os << "first error"; - if (!errors[0].path.empty()) { - os << " at " << errors[0].path; + if (!errors[0].instanceLocation.empty()) { + os << " at " << errors[0].instanceLocation; } if (!errors[0].message.empty()) { os << ": " << errors[0].message; @@ -381,9 +928,36 @@ namespace { ::pjson_test::report_failure(__FILE__, __LINE__, scope.c_str(), detail); } + void requireCompleteManifest(const std::string& suiteDir, const std::vector& rules) { + std::vector files; + listJsonFiles(suiteDir, std::string(), files); + std::sort(files.begin(), files.end()); + + std::vector declared; + for (size_t i = 0; i < rules.size(); ++i) + declared.push_back(rules[i].relativePath); + std::sort(declared.begin(), declared.end()); + + for (size_t i = 1; i < declared.size(); ++i) { + if (declared[i] == declared[i - 1]) + recordFailure("official schema suite manifest duplicate", declared[i]); + } + for (size_t i = 0; i < files.size(); ++i) { + if (!std::binary_search(declared.begin(), declared.end(), files[i])) + recordFailure("official schema suite manifest gap", + files[i] + " has no explicit run/skip decision"); + } + for (size_t i = 0; i < declared.size(); ++i) { + if (!std::binary_search(files.begin(), files.end(), declared[i])) + recordFailure("official schema suite manifest stale", + declared[i] + " is not present in the suite"); + } + } + // Runs one upstream case while preserving its file/group/case hierarchy in diagnostics. void runOneOfficialCase(const std::string& relativePath, const std::string& groupDesc, - const pjson& schema, const pjson& testCase, RunSummary& summary) { + const pJsonSchemaValidator& validator, const pjson& testCase, + RunSummary& summary) { const pjson* data = testCase.find("data"); const pjson* valid = testCase.find("valid"); const std::string caseDesc = testDescription(testCase); @@ -404,8 +978,8 @@ namespace { return; } - std::vector errors; - const bool actual = data->validate(schema, errors); + std::vector errors; + const bool actual = validator.validate(*data, errors); if (actual == expected) { return; } @@ -421,7 +995,8 @@ namespace { } // Validates a group shape once, then runs all of its cases against the shared schema. - void runWholeGroup(const std::string& relativePath, const pjson& group, RunSummary& summary) { + void runWholeGroup(const std::string& relativePath, const pjson& group, RunSummary& summary, + const pJsonSchemaValidator::Options& options, bool adaptDialect) { const pjson* schema = group.find("schema"); const pjson* tests = group.find("tests"); const std::string groupDesc = groupDescription(group); @@ -431,24 +1006,36 @@ namespace { return; } + // The upstream files declare their official draft URI. pjson does not + // claim those complete dialects: this manifest intentionally exercises + // selected cases under pjson's named documented-subset dialect instead. + // Removing only the root declaration is the explicit adaptation; every + // validation/applicator keyword and instance remains unchanged. Compile + // once per upstream group, matching the public validator lifecycle. + pjson subsetSchema(*schema); + if (adaptDialect) + subsetSchema.erase("$schema"); + pJsonSchemaValidator validator(subsetSchema, options); + const size_t count = tests->size(); summary.groupsRun += 1; for (size_t i = 0; i < count; ++i) { - const pjson* testCase = tests->find(static_cast(i)); + const pjson* testCase = tests->findIndex(i); if (testCase == NULL) { recordFailure("official schema suite case shape", relativePath + " :: " + groupDesc + " :: index " + pjson_test::to_str(static_cast(i))); continue; } - runOneOfficialCase(relativePath, groupDesc, *schema, *testCase, summary); + runOneOfficialCase(relativePath, groupDesc, validator, *testCase, summary); } } // Enforces a bidirectional manifest invariant: every upstream group has a rule and every rule // still names an upstream group. This makes suite upgrades fail visibly instead of shrinking // coverage silently. - void runSelectedGroups(const FileRule& fileRule, const pjson& suiteFile, RunSummary& summary) { + void runSelectedGroups(const FileRule& fileRule, const pjson& suiteFile, RunSummary& summary, + const pJsonSchemaValidator::Options& options, bool adaptDialect) { if (!suiteFile.isArray()) { recordFailure("official schema suite file shape", std::string(fileRule.relativePath) + " did not parse to an array"); @@ -457,7 +1044,7 @@ namespace { std::vector seenDescriptions; for (size_t i = 0; i < suiteFile.size(); ++i) { - const pjson* groupPtr = suiteFile.find(static_cast(i)); + const pjson* groupPtr = suiteFile.findIndex(i); if (groupPtr == NULL) { recordFailure("official schema suite group shape", std::string(fileRule.relativePath) + " :: index " + @@ -487,7 +1074,7 @@ namespace { continue; } - runWholeGroup(fileRule.relativePath, group, summary); + runWholeGroup(fileRule.relativePath, group, summary, options, adaptDialect); } for (size_t i = 0; i < fileRule.groups.size(); ++i) { @@ -502,7 +1089,8 @@ namespace { } // Runs every group in a file whose supported vocabulary needs no per-group filtering. - void runWholeFile(const FileRule& fileRule, const pjson& suiteFile, RunSummary& summary) { + void runWholeFile(const FileRule& fileRule, const pjson& suiteFile, RunSummary& summary, + const pJsonSchemaValidator::Options& options, bool adaptDialect) { if (!suiteFile.isArray()) { recordFailure("official schema suite file shape", std::string(fileRule.relativePath) + " did not parse to an array"); @@ -510,38 +1098,34 @@ namespace { } for (size_t i = 0; i < suiteFile.size(); ++i) { - const pjson* group = suiteFile.find(static_cast(i)); + const pjson* group = suiteFile.findIndex(i); if (group == NULL) { recordFailure("official schema suite group shape", std::string(fileRule.relativePath) + " :: index " + pjson_test::to_str(static_cast(i))); continue; } - runWholeGroup(fileRule.relativePath, *group, summary); + runWholeGroup(fileRule.relativePath, *group, summary, options, adaptDialect); } } } // namespace -TEST(schema_official_draft7_optional) { - const std::string draft7Dir = resolveDraft7Dir(); - if (draft7Dir.empty()) { - std::printf(" INFO JSON-Schema-Test-Suite skipped; set " - "PJSON_JSON_SCHEMA_TEST_SUITE_DIR or run " - "scripts/fetch-json-schema-test-suite.sh\n"); - CHECK(true); - return; - } - +// Shared manifest-driven runner used by both the draft7 and draft2020-12 gates. +static void runOfficialSuite(const std::string& suiteDir, const std::vector& rules, + const char* dialectLabel, const pJsonSchemaValidator::Options& options, + bool adaptDialect) { RunSummary summary; - const std::vector rules = manifest(); for (size_t i = 0; i < rules.size(); ++i) { - const std::string path = joinPath(draft7Dir, rules[i].relativePath); + const std::string path = joinPath(suiteDir, rules[i].relativePath); summary.filesVisited += 1; + pJsonSchemaValidator::Options fileOptions = options; + if (std::string(rules[i].relativePath).compare(0, 16, "optional/format/") == 0) + fileOptions.validateFormats = true; if (!isRegularFile(path)) { recordFailure("official schema suite file missing", - std::string(rules[i].relativePath) + " under " + draft7Dir); + std::string(rules[i].relativePath) + " under " + suiteDir); continue; } @@ -551,8 +1135,8 @@ TEST(schema_official_draft7_optional) { continue; } - pjson::ParseError parseError; - pjson::unique_ptr suite = parseJson(readFile(path), &parseError); + pJsonParser::Error parseError; + pjson_test::Parsed suite = parseJson(readFile(path), &parseError); if (!suite) { std::ostringstream os; os << rules[i].relativePath << " failed to parse"; @@ -564,18 +1148,49 @@ TEST(schema_official_draft7_optional) { } if (rules[i].mode == RunWholeFile) { - runWholeFile(rules[i], *suite, summary); + runWholeFile(rules[i], *suite, summary, fileOptions, adaptDialect); } else { - runSelectedGroups(rules[i], *suite, summary); + runSelectedGroups(rules[i], *suite, summary, fileOptions, adaptDialect); } } - std::printf(" INFO official schema suite visited %llu files (%llu whole-file skips), " + std::printf(" INFO official %s suite visited %llu files (%llu whole-file skips), " "ran %llu groups / %llu cases, skipped %llu groups / %llu cases\n", - static_cast(summary.filesVisited), + dialectLabel, static_cast(summary.filesVisited), static_cast(summary.filesSkipped), static_cast(summary.groupsRun), static_cast(summary.casesRun), static_cast(summary.groupsSkipped), static_cast(summary.casesSkipped)); } + +TEST(schema_official_draft7_optional) { + const std::string draft7Dir = resolveDraft7Dir(); + if (draft7Dir.empty()) { + std::printf(" INFO JSON-Schema-Test-Suite skipped; set " + "PJSON_JSON_SCHEMA_TEST_SUITE_DIR or run " + "scripts/fetch-json-schema-test-suite.sh\n"); + CHECK(true); + return; + } + runOfficialSuite(draft7Dir, manifest(), "draft7", pJsonSchemaValidator::Options(), true); +} + +TEST(schema_official_draft2020_optional) { + const std::string dir = resolveDraft2020Dir(); + if (dir.empty()) { + std::printf(" INFO draft2020-12 JSON-Schema-Test-Suite skipped; set " + "PJSON_JSON_SCHEMA_TEST_SUITE_DIR or run " + "scripts/fetch-json-schema-test-suite.sh\n"); + CHECK(true); + return; + } + OfficialResolverContext resolverContext; + resolverContext.remoteRoot = joinPath(configuredSchemaSuiteDir(), "remotes"); + pJsonSchemaValidator::Options options = pJsonSchemaValidator::Options::draft2020(); + options.resolver = resolveOfficialSchema; + options.resolverContext = &resolverContext; + const std::vector rules = manifest2020(); + requireCompleteManifest(dir, rules); + runOfficialSuite(dir, rules, "draft2020-12", options, false); +} diff --git a/pjsontest/src/tests_schema_vocabulary.cpp b/pjsontest/src/tests_schema_vocabulary.cpp index 7c7c324..941e7f2 100644 --- a/pjsontest/src/tests_schema_vocabulary.cpp +++ b/pjsontest/src/tests_schema_vocabulary.cpp @@ -18,6 +18,7 @@ // #include "pjson.h" #include "test_harness.h" +#include "test_util.h" #include #include @@ -25,37 +26,37 @@ using namespace ByteDance; namespace { - pjson::unique_ptr parseJson(const char* text) { - return pjson::parse(std::string(text)); + pjson_test::Parsed parseJson(const char* text) { + return pjson_test::parse(std::string(text)); } // Parse-and-validate adapters keep the vocabulary tables focused on schema behavior. bool validates(const char* schemaText, const char* dataText, - std::vector& errors, - const pjson::SchemaOptions& opts = pjson::SchemaOptions()) { - pjson::unique_ptr schema = parseJson(schemaText); - pjson::unique_ptr data = parseJson(dataText); + std::vector& errors, + const pjson_test::SchemaOptions& opts = pjson_test::SchemaOptions()) { + pjson_test::Parsed schema = parseJson(schemaText); + pjson_test::Parsed data = parseJson(dataText); if (!schema || !data) return false; - return data->validate(*schema, errors, opts); + return pjson_test::schemaValidate(*data, *schema, errors, opts); } bool validates(const char* schemaText, const char* dataText, - const pjson::SchemaOptions& opts = pjson::SchemaOptions()) { - std::vector errors; + const pjson_test::SchemaOptions& opts = pjson_test::SchemaOptions()) { + std::vector errors; return validates(schemaText, dataText, errors, opts); } // Error predicates assert semantic diagnostics without coupling tests to error ordering. - bool hasErrorAt(const std::vector& errors, const std::string& path) { + bool hasErrorAt(const std::vector& errors, const std::string& path) { for (const auto& err : errors) { - if (err.path == path) + if (err.instanceLocation == path) return true; } return false; } - bool hasMessageContaining(const std::vector& errors, + bool hasMessageContaining(const std::vector& errors, const std::string& needle) { for (const auto& err : errors) { if (err.message.find(needle) != std::string::npos) @@ -64,42 +65,42 @@ namespace { return false; } - bool hasErrorAtWithMessage(const std::vector& errors, + bool hasErrorAtWithMessage(const std::vector& errors, const std::string& path, const std::string& needle) { for (const auto& err : errors) { - if (err.path == path && err.message.find(needle) != std::string::npos) + if (err.instanceLocation == path && err.message.find(needle) != std::string::npos) return true; } return false; } // Small option factories make each validation-budget test state only its changed knob. - pjson::SchemaOptions optionsWithFormatValidation(bool enabled) { - pjson::SchemaOptions opts; + pjson_test::SchemaOptions optionsWithFormatValidation(bool enabled) { + pjson_test::SchemaOptions opts; opts.validateFormats = enabled; return opts; } - pjson::SchemaOptions optionsWithDepthBudget(size_t maxDepth) { - pjson::SchemaOptions opts; + pjson_test::SchemaOptions optionsWithDepthBudget(size_t maxDepth) { + pjson_test::SchemaOptions opts; opts.maxValidationDepth = maxDepth; return opts; } - pjson::SchemaOptions optionsWithRefBudget(size_t maxRefs) { - pjson::SchemaOptions opts; + pjson_test::SchemaOptions optionsWithRefBudget(size_t maxRefs) { + pjson_test::SchemaOptions opts; opts.maxRefResolutions = maxRefs; return opts; } - pjson::SchemaOptions optionsWithWorkBudget(size_t maxWork) { - pjson::SchemaOptions opts; + pjson_test::SchemaOptions optionsWithWorkBudget(size_t maxWork) { + pjson_test::SchemaOptions opts; opts.maxValidationWork = maxWork; return opts; } - pjson::SchemaOptions optionsWithErrorBudget(size_t maxErrors) { - pjson::SchemaOptions opts; + pjson_test::SchemaOptions optionsWithErrorBudget(size_t maxErrors) { + pjson_test::SchemaOptions opts; opts.maxErrors = maxErrors; return opts; } @@ -203,7 +204,7 @@ TEST(schema_vocab_ref_local_defs_and_definitions) { "additionalProperties": false })"; CHECK(validates(defsSchema, R"({"id":7})")); - std::vector errors; + std::vector errors; CHECK(!validates(defsSchema, R"({"id":0})", errors)); CHECK(hasErrorAtWithMessage(errors, "/id", "minimum")); @@ -223,7 +224,7 @@ TEST(schema_vocab_ref_local_defs_and_definitions) { } TEST(schema_vocab_ref_unresolved_malformed_and_nonlocal) { - std::vector errors; + std::vector errors; CHECK(!validates(R"({"$ref":"#/$defs/missing"})", "1", errors)); CHECK(hasErrorAt(errors, std::string(""))); @@ -232,16 +233,16 @@ TEST(schema_vocab_ref_unresolved_malformed_and_nonlocal) { errors.clear(); CHECK(!validates(R"({"$ref":"#/$defs/bad~2token"})", "1", errors)); CHECK(hasErrorAt(errors, std::string(""))); - CHECK(hasMessageContaining(errors, "malformed")); + CHECK(hasMessageContaining(errors, "unresolved")); errors.clear(); CHECK(!validates(R"({"$ref":"https://example.com/schema.json#/$defs/x"})", "1", errors)); CHECK(hasErrorAt(errors, std::string(""))); - CHECK(hasMessageContaining(errors, "non-local")); + CHECK(hasMessageContaining(errors, "no resolver")); } TEST(schema_vocab_ref_cycle_is_reported) { - std::vector errors; + std::vector errors; CHECK(!validates(R"({"$ref":"#"})", R"({"anything":1})", errors)); CHECK(hasErrorAt(errors, std::string(""))); CHECK(hasMessageContaining(errors, "cycle")); @@ -259,8 +260,8 @@ TEST(schema_vocab_ref_validation_depth_budget) { } })"; - pjson::SchemaOptions shallow = optionsWithDepthBudget(2); - std::vector errors; + pjson_test::SchemaOptions shallow = optionsWithDepthBudget(2); + std::vector errors; CHECK(!validates(kRecursiveNodeSchema, data, errors, shallow)); CHECK(hasMessageContaining(errors, "depth")); } @@ -277,67 +278,67 @@ TEST(schema_vocab_ref_resolution_budget) { } })"; - pjson::SchemaOptions limited = optionsWithRefBudget(2); - std::vector errors; + pjson_test::SchemaOptions limited = optionsWithRefBudget(2); + std::vector errors; CHECK(!validates(kRecursiveNodeSchema, data, errors, limited)); CHECK(hasMessageContaining(errors, "ref")); CHECK(hasMessageContaining(errors, "budget")); } TEST(schema_vocab_ref_chain_still_obeys_depth_budget) { - pjson::SchemaOptions opts = optionsWithDepthBudget(4); + pjson_test::SchemaOptions opts = optionsWithDepthBudget(4); opts.maxRefResolutions = 16; const pjson schema = makeReferenceChainSchema(4); pjson instance; instance = int64_t(1); - std::vector errors; - CHECK(!instance.validate(schema, errors, opts)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(instance, schema, errors, opts)); CHECK(hasMessageContaining(errors, "depth")); CHECK(hasMessageContaining(errors, "budget")); } TEST(schema_vocab_ref_zero_depth_uses_hard_ceiling) { - pjson::SchemaOptions opts = optionsWithDepthBudget(0); - CHECK_EQ(pjson::SchemaOptions().maxValidationDepth, size_t(64)); - const pjson schema = makeNestedPropertySchema(64); - const pjson instance = makeNestedPropertyInstance(64); + pjson_test::SchemaOptions opts = optionsWithDepthBudget(0); + CHECK_EQ(pjson_test::SchemaOptions().maxValidationDepth, size_t(32)); + const pjson schema = makeNestedPropertySchema(32); + const pjson instance = makeNestedPropertyInstance(32); - std::vector errors; - CHECK(!instance.validate(schema, errors, opts)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(instance, schema, errors, opts)); CHECK(hasMessageContaining(errors, "depth")); } TEST(schema_vocab_ref_requested_depth_is_clamped_to_hard_ceiling) { - pjson::SchemaOptions opts = optionsWithDepthBudget(2048); - const pjson withinLimitSchema = makeNestedPropertySchema(63); - const pjson withinLimitInstance = makeNestedPropertyInstance(63); - const pjson schema = makeNestedPropertySchema(64); - const pjson instance = makeNestedPropertyInstance(64); - - CHECK(withinLimitInstance.validate(withinLimitSchema, opts)); - std::vector errors; - CHECK(!instance.validate(schema, errors, opts)); + pjson_test::SchemaOptions opts = optionsWithDepthBudget(2048); + const pjson withinLimitSchema = makeNestedPropertySchema(31); + const pjson withinLimitInstance = makeNestedPropertyInstance(31); + const pjson schema = makeNestedPropertySchema(32); + const pjson instance = makeNestedPropertyInstance(32); + + CHECK(pjson_test::schemaValidate(withinLimitInstance, withinLimitSchema, opts)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(instance, schema, errors, opts)); CHECK(hasMessageContaining(errors, "depth")); } TEST(schema_vocab_ref_zero_resolution_budget_allows_hard_ceiling) { - pjson::SchemaOptions opts = optionsWithRefBudget(0); + pjson_test::SchemaOptions opts = optionsWithRefBudget(0); const pjson schema = makeBranchingWorkSchema(10); pjson instance; instance = int64_t(1); - CHECK(instance.validate(schema, opts)); + CHECK(pjson_test::schemaValidate(instance, schema, opts)); } TEST(schema_vocab_ref_zero_resolution_budget_uses_hard_ceiling) { - pjson::SchemaOptions opts = optionsWithRefBudget(0); + pjson_test::SchemaOptions opts = optionsWithRefBudget(0); const pjson schema = makeBranchingWorkSchema(11); pjson instance; instance = int64_t(1); - std::vector errors; - CHECK(!instance.validate(schema, errors, opts)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(instance, schema, errors, opts)); CHECK(hasMessageContaining(errors, "ref")); CHECK(hasMessageContaining(errors, "budget")); } @@ -353,7 +354,7 @@ TEST(schema_vocab_ref_ignores_sibling_keywords_draft07) { })"; CHECK(validates(schema, R"("ok")")); - std::vector errors; + std::vector errors; CHECK(!validates(schema, "5", errors)); CHECK(hasMessageContaining(errors, "string")); CHECK(!hasMessageContaining(errors, "expected type integer")); @@ -373,7 +374,7 @@ TEST(schema_vocab_pattern_properties_basic_matching) { })"; CHECK(validates(schema, R"({"S_COUNT":1,"plain":"x"})")); - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"S_COUNT":"bad"})", errors)); CHECK(hasErrorAt(errors, "/S_COUNT")); } @@ -389,7 +390,7 @@ TEST(schema_vocab_pattern_properties_and_properties_both_apply) { } })"; - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"S_NAME":5})", errors)); CHECK(hasErrorAtWithMessage(errors, "/S_NAME", "string")); } @@ -403,7 +404,7 @@ TEST(schema_vocab_property_names_reports_property_path) { })"; CHECK(validates(schema, R"({"OK":1,"ALSO_OK":2})")); - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"bad/key":1})", errors)); CHECK(hasErrorAt(errors, "/bad~1key")); CHECK(hasMessageContaining(errors, "pattern")); @@ -418,7 +419,7 @@ TEST(schema_vocab_dependent_required) { })"; CHECK(validates(schema, R"({"credit_card":"1234","billing_address":"x","name":"Ada"})")); - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"credit_card":"1234"})", errors)); CHECK(hasErrorAt(errors, std::string(""))); CHECK(hasMessageContaining(errors, "billing_address")); @@ -434,7 +435,7 @@ TEST(schema_vocab_dependencies_array_form) { })"; CHECK(validates(schema, R"({"credit_card":"1234","billing_address":"x"})")); - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"credit_card":"1234"})", errors)); CHECK(hasErrorAt(errors, std::string(""))); CHECK(hasMessageContaining(errors, "billing_address")); @@ -455,7 +456,7 @@ TEST(schema_vocab_dependencies_schema_form) { CHECK(validates(schema, R"({"credit_card":"1234","billing_address":"123 Main"})")); - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"credit_card":"1234"})", errors)); CHECK(hasErrorAt(errors, std::string(""))); CHECK(hasMessageContaining(errors, "billing_address")); @@ -476,7 +477,7 @@ TEST(schema_vocab_additional_properties_schema_applies_only_to_unmatched_keys) { CHECK(validates(schema, R"({"declared":"ok","extra":2})")); - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"declared":"ok","extra":"bad"})", errors)); CHECK(hasErrorAtWithMessage(errors, "/extra", "integer")); CHECK(!hasErrorAt(errors, "/declared")); @@ -496,7 +497,7 @@ TEST(schema_vocab_object_keyword_interactions) { CHECK(validates(schema, R"({"fixed":"ok","dyn_count":3})")); - std::vector errors; + std::vector errors; CHECK(!validates(schema, R"({"fixed":"ok","dyn_count":"bad","extra":1})", errors)); CHECK(hasErrorAtWithMessage(errors, "/dyn_count", "integer")); CHECK(hasErrorAtWithMessage(errors, "/extra", "additional property")); @@ -546,7 +547,7 @@ TEST(schema_vocab_format_ipv6) { } TEST(schema_vocab_format_ipv6_rejects_ipv4_prefix_before_compression) { - std::vector errors; + std::vector errors; CHECK(!validates(R"({"format":"ipv6"})", R"("192.0.2.128::")", errors)); CHECK(hasMessageContaining(errors, "format") || hasMessageContaining(errors, "ipv6")); } @@ -561,11 +562,11 @@ TEST(schema_vocab_format_uuid) { TEST(schema_vocab_format_unknown_is_ignored_and_disable_option_skips_known_formats) { CHECK(validates(R"({"type":"string","format":"unknown-future-format"})", R"("anything")")); - pjson::SchemaOptions disabled = optionsWithFormatValidation(false); + pjson_test::SchemaOptions disabled = optionsWithFormatValidation(false); CHECK(validates(R"({"type":"string","format":"date"})", R"("not-a-date")", disabled)); - pjson::SchemaOptions enabled = optionsWithFormatValidation(true); - std::vector errors; + pjson_test::SchemaOptions enabled = optionsWithFormatValidation(true); + std::vector errors; CHECK(!validates(R"({"type":"string","format":"date"})", R"("not-a-date")", errors, enabled)); CHECK(hasMessageContaining(errors, "format")); } @@ -588,7 +589,7 @@ TEST(schema_multiple_of_precision_decimal_exact_cases) { } TEST(schema_multiple_of_precision_decimal_traps) { - std::vector errors; + std::vector errors; CHECK(!validates(R"({"multipleOf":0.1})", "0.30000000000000004", errors)); CHECK(hasMessageContaining(errors, "multiple")); @@ -609,7 +610,7 @@ TEST(schema_multiple_of_non_positive_schema_values_are_ignored) { } TEST(schema_multiple_of_precision_overflow_case_from_official_suite) { - std::vector errors; + std::vector errors; CHECK(!validates(R"({"type":"integer","multipleOf":0.123456789})", "1e308", errors)); CHECK(hasMessageContaining(errors, "multiple")); } @@ -620,17 +621,17 @@ TEST(schema_multiple_of_precision_exact_mixed_numeric_const_enum) { pjson exactInt; exactInt = int64_t(9007199254740993LL); - CHECK(exactInt.validate(constSchema)); + CHECK(pjson_test::schemaValidate(exactInt, constSchema)); pjson roundedDouble; roundedDouble = double(9007199254740992.0); - CHECK(!roundedDouble.validate(constSchema)); + CHECK(!pjson_test::schemaValidate(roundedDouble, constSchema)); pjson enumSchema; enumSchema["enum"][0] = int64_t(9007199254740993LL); enumSchema["enum"][1] = int64_t(5); - CHECK(exactInt.validate(enumSchema)); - CHECK(!roundedDouble.validate(enumSchema)); + CHECK(pjson_test::schemaValidate(exactInt, enumSchema)); + CHECK(!pjson_test::schemaValidate(roundedDouble, enumSchema)); } TEST(schema_validation_work_budget) { @@ -645,26 +646,26 @@ TEST(schema_validation_work_budget) { })"; const char* data = R"({"items":[1,2,3,4,5,6,7,8,9,10]})"; - pjson::SchemaOptions constrained = optionsWithWorkBudget(1); - std::vector errors; + pjson_test::SchemaOptions constrained = optionsWithWorkBudget(1); + std::vector errors; CHECK(!validates(schema, data, errors, constrained)); CHECK(hasMessageContaining(errors, "work") || hasMessageContaining(errors, "budget")); } TEST(schema_validation_work_budget_charges_deep_equality_unicode_and_additional_properties) { - pjson::SchemaOptions tiny = optionsWithWorkBudget(16); - std::vector errors; + pjson_test::SchemaOptions tiny = optionsWithWorkBudget(16); + std::vector errors; const pjson deep = makeDeepValue(32, int64_t(1)); pjson constSchema; constSchema["const"] = deep; - CHECK(!deep.validate(constSchema, errors, tiny)); + CHECK(!pjson_test::schemaValidate(deep, constSchema, errors, tiny)); CHECK(hasMessageContaining(errors, "work") || hasMessageContaining(errors, "budget")); errors.clear(); pjson enumSchema; enumSchema["enum"][0] = deep; - CHECK(!deep.validate(enumSchema, errors, tiny)); + CHECK(!pjson_test::schemaValidate(deep, enumSchema, errors, tiny)); CHECK(hasMessageContaining(errors, "work") || hasMessageContaining(errors, "budget")); errors.clear(); @@ -673,7 +674,7 @@ TEST(schema_validation_work_budget_charges_deep_equality_unicode_and_additional_ pjson duplicateDeep; duplicateDeep[0] = deep; duplicateDeep[1] = deep; - CHECK(!duplicateDeep.validate(uniqueSchema, errors, tiny)); + CHECK(!pjson_test::schemaValidate(duplicateDeep, uniqueSchema, errors, tiny)); CHECK(hasMessageContaining(errors, "work") || hasMessageContaining(errors, "budget")); errors.clear(); @@ -684,7 +685,7 @@ TEST(schema_validation_work_budget_charges_deep_equality_unicode_and_additional_ for (size_t i = 0; i < 32; ++i) unicode += "\xF0\x9F\x98\x80"; unicodeValue = unicode; - CHECK(!unicodeValue.validate(unicodeSchema, errors, tiny)); + CHECK(!pjson_test::schemaValidate(unicodeValue, unicodeSchema, errors, tiny)); CHECK(hasMessageContaining(errors, "work") || hasMessageContaining(errors, "budget")); errors.clear(); @@ -693,7 +694,7 @@ TEST(schema_validation_work_budget_charges_deep_equality_unicode_and_additional_ pjson manyProperties; for (size_t i = 0; i < 32; ++i) manyProperties["key" + std::to_string(i)] = static_cast(i); - CHECK(!manyProperties.validate(additionalSchema, errors, tiny)); + CHECK(!pjson_test::schemaValidate(manyProperties, additionalSchema, errors, tiny)); CHECK(hasMessageContaining(errors, "work") || hasMessageContaining(errors, "budget")); } @@ -705,20 +706,20 @@ TEST(schema_additional_properties_large_object_stays_within_work_budget) { for (size_t i = 0; i < propertyCount; ++i) instance["key" + std::to_string(i)] = static_cast(i); - pjson::SchemaOptions options = optionsWithWorkBudget(propertyCount * 4U + 16U); - CHECK(instance.validate(schema, options)); + pjson_test::SchemaOptions options = optionsWithWorkBudget(propertyCount * 4U + 16U); + CHECK(pjson_test::schemaValidate(instance, schema, options)); } TEST(schema_validation_zero_work_budget_uses_hard_ceiling) { - pjson::SchemaOptions opts = optionsWithWorkBudget(0); - opts.maxValidationDepth = 64; + pjson_test::SchemaOptions opts = optionsWithWorkBudget(0); + opts.maxValidationDepth = 32; opts.maxRefResolutions = 2000000; const pjson schema = makeBranchingWorkSchema(20); pjson instance; instance = int64_t(1); - std::vector errors; - CHECK(!instance.validate(schema, errors, opts)); + std::vector errors; + CHECK(!pjson_test::schemaValidate(instance, schema, errors, opts)); CHECK(hasMessageContaining(errors, "work") || hasMessageContaining(errors, "budget")); } @@ -735,20 +736,20 @@ TEST(schema_validation_error_budget) { })"; const char* data = R"({"a":"x","b":"y"})"; - pjson::SchemaOptions constrained = optionsWithErrorBudget(1); - std::vector errors; + pjson_test::SchemaOptions constrained = optionsWithErrorBudget(1); + std::vector errors; CHECK(!validates(schema, data, errors, constrained)); CHECK(errors.size() <= size_t(1)); } TEST(schema_anyof_scratch_errors_do_not_consume_public_error_budget) { - pjson::SchemaOptions options = optionsWithErrorBudget(1); + pjson_test::SchemaOptions options = optionsWithErrorBudget(1); const char* matchingLast = R"({"anyOf":[{"type":"string"},{"minimum":10},{"const":7},{"type":"integer"}]})"; const char* matchingFirst = R"({"anyOf":[{"type":"integer"},{"type":"string"},{"minimum":10},{"const":7}]})"; - std::vector errors; + std::vector errors; CHECK(validates(matchingLast, "5", errors, options)); CHECK(errors.empty()); CHECK(validates(matchingFirst, "5", errors, options)); @@ -756,13 +757,13 @@ TEST(schema_anyof_scratch_errors_do_not_consume_public_error_budget) { } TEST(schema_oneof_scratch_errors_do_not_consume_public_error_budget) { - pjson::SchemaOptions options = optionsWithErrorBudget(1); + pjson_test::SchemaOptions options = optionsWithErrorBudget(1); const char* matchingLast = R"({"oneOf":[{"type":"string"},{"minimum":10},{"const":7},{"type":"integer"}]})"; const char* matchingFirst = R"({"oneOf":[{"type":"integer"},{"type":"string"},{"minimum":10},{"const":7}]})"; - std::vector errors; + std::vector errors; CHECK(validates(matchingLast, "5", errors, options)); CHECK(errors.empty()); CHECK(validates(matchingFirst, "5", errors, options)); @@ -770,7 +771,7 @@ TEST(schema_oneof_scratch_errors_do_not_consume_public_error_budget) { } TEST(schema_not_scratch_error_leaves_budget_for_later_real_failure) { - pjson::SchemaOptions options = optionsWithErrorBudget(1); + pjson_test::SchemaOptions options = optionsWithErrorBudget(1); const char* schema = R"({ "allOf": [ {"not": {"type": "string"}}, @@ -778,7 +779,7 @@ TEST(schema_not_scratch_error_leaves_budget_for_later_real_failure) { ] })"; - std::vector errors; + std::vector errors; CHECK(!validates(schema, "5", errors, options)); CHECK_EQ(errors.size(), size_t(1)); CHECK(hasMessageContaining(errors, "string")); @@ -799,12 +800,12 @@ TEST(schema_speculative_branches_discard_large_hidden_error_sets) { pjson instance; instance.resetTo(pjson::jsonObject); - pjson::SchemaOptions options; + pjson_test::SchemaOptions options; options.maxErrors = 1; options.maxValidationWork = requiredCount * 4; - std::vector errors; + std::vector errors; - CHECK(instance.validate(anyOfSchema, errors, options)); + CHECK(pjson_test::schemaValidate(instance, anyOfSchema, errors, options)); CHECK(errors.empty()); } @@ -812,8 +813,8 @@ TEST(schema_validation_zero_error_budget_uses_hard_ceiling) { const char* schema = R"({"type":"object","required":["a","b","c"]})"; const char* data = R"({})"; - pjson::SchemaOptions opts = optionsWithErrorBudget(0); - std::vector errors; + pjson_test::SchemaOptions opts = optionsWithErrorBudget(0); + std::vector errors; CHECK(!validates(schema, data, errors, opts)); CHECK(!errors.empty()); CHECK(errors.size() <= size_t(100)); diff --git a/pjsontest/src/tests_serialize_access.cpp b/pjsontest/src/tests_serialize_access.cpp index 74f6b20..63167d8 100644 --- a/pjsontest/src/tests_serialize_access.cpp +++ b/pjsontest/src/tests_serialize_access.cpp @@ -17,10 +17,12 @@ // #include "pjson.h" #include "test_harness.h" +#include "test_util.h" #include #include #include +#include #include #include #include @@ -217,22 +219,9 @@ TEST(serialize_options_empty_containers_stay_inline) { pjson::SerializeOptions options = pjson::SerializeOptions::prettyPrinted(); options.indentWidth = 1; - CHECK_EQ(value.toString(options), std::string("{\n \"array\": [],\n \"object\": {}\n}")); -} - -TEST(serialize_options_key_order_applies_at_every_depth) { - pjson value; - value["a"]["a"] = static_cast(1); - value["a"]["z"] = static_cast(2); - value["z"] = static_cast(3); - - pjson::SerializeOptions ascending; - CHECK_EQ(value.toString(ascending), std::string("{\"a\":{\"a\":1,\"z\":2},\"z\":3}")); - - pjson::SerializeOptions descending; - descending.keyOrder = pjson::SerializeOptions::DescendingKeys; - CHECK_EQ(value.toString(descending), std::string("{\"z\":3,\"a\":{\"z\":2,\"a\":1}}")); - CHECK_EQ(streamed(value, descending), value.toString(descending)); + const std::string output = value.toString(options); + CHECK(output == std::string("{\n \"array\": [],\n \"object\": {}\n}") || + output == std::string("{\n \"object\": {},\n \"array\": []\n}")); } TEST(serialize_options_ascii_only_values_and_keys) { @@ -250,7 +239,7 @@ TEST(serialize_options_ascii_only_values_and_keys) { CHECK(isAscii(text)); CHECK_EQ(streamed(value, options), text); - pjson::unique_ptr reparsed = pjson::parse(text); + pjson_test::Parsed reparsed = pjson_test::parse(text); CHECK(reparsed != nullptr); if (reparsed) CHECK(*reparsed == value); @@ -355,6 +344,13 @@ TEST(value_find_index_const_and_wrong_types_do_not_mutate) { "const indexed lookup must return const pjson*"); CHECK(constArray.find(0) != nullptr); CHECK(constArray.find(1) == nullptr); + const size_t zero = 0; + const size_t one = 1; + static_assert(std::is_same::value, + "const size_t lookup must return const pjson*"); + CHECK(constArray.findIndex(zero) != nullptr); + CHECK(constArray.findIndex(one) == nullptr); + CHECK(constArray.findIndex(std::numeric_limits::max()) == nullptr); pjson empty; empty.resetTo(pjson::jsonArray); diff --git a/pjsontest/src/tests_serialize_limits.cpp b/pjsontest/src/tests_serialize_limits.cpp new file mode 100644 index 0000000..588e5b8 --- /dev/null +++ b/pjsontest/src/tests_serialize_limits.cpp @@ -0,0 +1,171 @@ +// +// Copyright 2025 ByteDance Ltd. and/or its affiliates. All rights reserved. +// 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. +// +//===----------------------------------------------------------------------===// +// PJSON-SER-001: valid output and an +// overflow-safe output-size limit tested at limit-1, limit, and limit+1. +// +#include "pjson.h" +#include "test_harness.h" +#include "test_util.h" + +#include +#include +#include + +using namespace ByteDance; + +//===----------------------------------------------------------------------===// +// The output-size limit is exact: exactly-limit succeeds, limit-plus-one fails, +// both for toString() and write(). This confirms the boundary arithmetic is +// off-by-one-safe. +//===----------------------------------------------------------------------===// +TEST(output_size_limit_boundary) { + // A five-element array of single-digit ints serializes to "[1,2,3,4,5]" (11 bytes). + pjson arr; + for (int64_t i = 1; i <= 5; ++i) + arr += i; + const std::string full = arr.toString(); + const size_t exact = full.size(); + CHECK_EQ(exact, size_t(11)); + + pjson::SerializeOptions atLimit; + atLimit.maxOutputBytes = exact; // limit == output size: succeeds + CHECK_EQ(arr.toString(atLimit), full); + + pjson::SerializeOptions belowLimit; + belowLimit.maxOutputBytes = exact - 1; // limit-1: must fail + bool threw = false; + try { + (void)arr.toString(belowLimit); + } catch (const std::length_error&) { + threw = true; + } + CHECK(threw); + + pjson::SerializeOptions abovePlusOne; + abovePlusOne.maxOutputBytes = exact + 1; // limit+1: comfortably succeeds + CHECK_EQ(arr.toString(abovePlusOne), full); + + // write() enforces the same budget through failbit. + std::ostringstream tooSmall; + arr.write(tooSmall, belowLimit); + CHECK(tooSmall.fail()); + + std::ostringstream justRight; + arr.write(justRight, atLimit); + CHECK(!justRight.fail()); + CHECK_EQ(justRight.str(), full); +} + +//===----------------------------------------------------------------------===// +// toString() and write() are byte-for-byte equivalent for the same options. +//===----------------------------------------------------------------------===// +TEST(tostring_and_write_are_equivalent) { + pjson_test::Parsed doc = + pjson_test::parse("{\"b\":[1,2,{\"x\":true}],\"a\":\"hi\",\"n\":18446744073709551615}"); + CHECK(doc != nullptr); + if (!doc) + return; + + const pjson::SerializeOptions options[] = { + pjson::SerializeOptions(), + pjson::SerializeOptions::prettyPrinted(), + }; + for (const pjson::SerializeOptions& opt : options) { + const std::string viaString = doc->toString(opt); + std::ostringstream viaStream; + doc->write(viaStream, opt); + CHECK(!viaStream.fail()); + CHECK_EQ(viaString, viaStream.str()); + } +} + +//===----------------------------------------------------------------------===// +// Native object order is unspecified, but output remains valid and reparses to +// a structurally equal document. +//===----------------------------------------------------------------------===// +TEST(native_object_order_round_trips) { + pjson obj = pjson::object(); + obj["c"] = int64_t(3); + obj["a"] = int64_t(1); + obj["b"] = int64_t(2); + + const std::string text = obj.toString(); + pjson_test::Parsed reparsed = pjson_test::parse(text); + CHECK(reparsed != nullptr); + if (reparsed) + CHECK(*reparsed == obj); +} + +TEST(structured_serialization_success_and_output_limit) { + pjson value; + value["answer"] = int64_t(42); + + pjson::SerializeError error; + std::string output = "old"; + CHECK(value.toString(output, error)); + CHECK_EQ(error.code, pjson::SerializeError::None); + CHECK(error.message.empty()); + CHECK_EQ(output, std::string("{\"answer\":42}")); + + pjson::SerializeOptions limited; + limited.maxOutputBytes = output.size() - 1; + output = "preserved"; + CHECK(!value.toString(output, error, limited)); + CHECK_EQ(error.code, pjson::SerializeError::OutputLimit); + CHECK(!error.message.empty()); + CHECK_EQ(output, std::string("preserved")); +} + +TEST(structured_serialization_classifies_invalid_utf8_and_nonfinite) { + pjson::SerializeError error; + std::string output = "unchanged"; + + pjson invalidUtf8; + invalidUtf8 = std::string("\xC0\xAF", 2); + CHECK(!invalidUtf8.toString(output, error)); + CHECK_EQ(error.code, pjson::SerializeError::InvalidUtf8); + CHECK_EQ(output, std::string("unchanged")); + + pjson nonFinite; + nonFinite = std::numeric_limits::infinity(); + CHECK(!nonFinite.toString(output, error)); + CHECK_EQ(error.code, pjson::SerializeError::NonFiniteNumber); + CHECK_EQ(output, std::string("unchanged")); +} + +TEST(structured_stream_serialization_reports_logical_and_physical_failure) { + pjson value; + value["key"] = "value"; + pjson::SerializeError error; + + pjson::SerializeOptions limited; + limited.maxOutputBytes = 1; + std::ostringstream logical; + CHECK(!value.write(logical, error, limited)); + CHECK_EQ(error.code, pjson::SerializeError::OutputLimit); + CHECK(logical.str().empty()); + + std::ostringstream throwingLogical; + throwingLogical.exceptions(std::ios::failbit); + CHECK(!value.write(throwingLogical, error, limited)); + CHECK_EQ(error.code, pjson::SerializeError::OutputLimit); + CHECK(throwingLogical.str().empty()); + + std::ostringstream physical; + physical.setstate(std::ios::badbit); + CHECK(!value.write(physical, error)); + CHECK_EQ(error.code, pjson::SerializeError::StreamFailure); +} diff --git a/pjsontest/src/tests_storage.cpp b/pjsontest/src/tests_storage.cpp index 6a54aa5..759224a 100644 --- a/pjsontest/src/tests_storage.cpp +++ b/pjsontest/src/tests_storage.cpp @@ -13,11 +13,12 @@ // limitations under the License. // //===----------------------------------------------------------------------===// -// Storage-focused tests: inline scalar copy/move/swap behavior, transitions -// between inline and heap-backed kinds, and noexcept trait guarantees. +// Storage-focused tests: scalar copy/move/swap behavior, transitions between +// scalar and container kinds, and ABI/noexcept guarantees. // #include "pjson.h" #include "test_harness.h" +#include "test_util.h" #include #include @@ -26,8 +27,12 @@ using namespace ByteDance; +static_assert(std::is_nothrow_default_constructible::value, + "pjson null construction must remain noexcept"); static_assert(std::is_nothrow_move_constructible::value, "pjson move construction must remain noexcept"); +static_assert(sizeof(pjson) == sizeof(void*) * 2, "pjson ABI must remain a two-pointer handle"); +static_assert(alignof(pjson) == alignof(void*), "pjson ABI alignment must remain pointer-aligned"); static_assert(noexcept(std::declval().swap(std::declval())), "pjson::swap must remain noexcept"); @@ -60,7 +65,7 @@ namespace { } // namespace //===----------------------------------------------------------------------===// -// Copy and move preserve inline scalar values and reset moved-from sources +// Copy and move preserve scalar values and reset moved-from sources //===----------------------------------------------------------------------===// TEST(storage_copy_constructs_inline_scalars) { @@ -268,7 +273,7 @@ TEST(storage_scalar_type_transitions_preserve_behavior) { } TEST(storage_scalar_parse_copy_move_and_serialize_round_trip) { - pjson::unique_ptr parsed = pjson::parse(R"({"i":1,"d":2.5,"b":true})"); + pjson_test::Parsed parsed = pjson_test::parse(R"({"i":1,"d":2.5,"b":true})"); CHECK(parsed != nullptr); expectInt((*parsed)["i"], int64_t(1)); expectDouble((*parsed)["d"], 2.5); @@ -279,5 +284,8 @@ TEST(storage_scalar_parse_copy_move_and_serialize_round_trip) { pjson moved(std::move(copied)); CHECK(moved == *parsed); CHECK(copied.isNull()); - CHECK_EQ(moved.toString(), std::string("{\"b\":true,\"d\":2.5,\"i\":1}")); + pjson_test::Parsed expected = pjson_test::parse(R"({"b":true,"d":2.5,"i":1})"); + CHECK(expected != nullptr); + if (expected != nullptr) + CHECK(moved == *expected); } diff --git a/pjsontest/src/tests_streaming.cpp b/pjsontest/src/tests_streaming.cpp index ae11e83..c81155b 100644 --- a/pjsontest/src/tests_streaming.cpp +++ b/pjsontest/src/tests_streaming.cpp @@ -16,6 +16,7 @@ // Streaming SAX parsing and direct ostream serialization tests. // #include "pjson.h" +#include "pjson_parser.h" #include "test_harness.h" #include "test_util.h" @@ -34,7 +35,7 @@ using pjson_test::valueInt; namespace { // Captures the SAX callback stream in a compact form suitable for exact ordering checks. - struct RecordingHandler : pjson::SaxHandler { + struct RecordingHandler : pJsonParser::SaxHandler { std::vector events; bool onNull() override { @@ -149,7 +150,7 @@ namespace { bool onString(const std::string&) override { throw std::bad_alloc(); } }; - struct NumberHandler : pjson::SaxHandler { + struct NumberHandler : pJsonParser::SaxHandler { bool sawDouble = false; double value = 1.0; @@ -203,6 +204,19 @@ namespace { ChunkedStreamBuf _buf; }; + class ThrowingInputStreamBuf : public std::streambuf { + protected: + int_type underflow() override { throw std::runtime_error("input failure"); } + }; + + struct ThrowingIStream : std::istream { + ThrowingIStream() + : std::istream(&_buf) {} + + private: + ThrowingInputStreamBuf _buf; + }; + // Accepts at most `limit` output bytes, then reports a short write to its ostream. class FailingStreamBuf : public std::stringbuf { public: @@ -255,7 +269,7 @@ namespace { TEST(streaming_sax_scalar_events) { RecordingHandler h; - CHECK(pjson::parseSax(" [null,true,false,1,2.5,\"x\"] ", h)); + CHECK(pJsonParser().parseSax(" [null,true,false,1,2.5,\"x\"] ", h)); CHECK_EQ(h.events.size(), size_t(8)); CHECK_EQ(h.events[0], std::string("start-array")); CHECK_EQ(h.events[1], std::string("null")); @@ -269,7 +283,7 @@ TEST(streaming_sax_scalar_events) { TEST(streaming_sax_object_order_and_empty_containers) { RecordingHandler h; - CHECK(pjson::parseSax("{\"a\":{},\"b\":[],\"c\":{\"d\":[1]}}", h)); + CHECK(pJsonParser().parseSax("{\"a\":{},\"b\":[],\"c\":{\"d\":[1]}}", h)); const std::vector want = { "start-object", "key:a", "start-object", "end-object", "key:b", "start-array", "end-array", "key:c", "start-object", "key:d", @@ -283,8 +297,8 @@ TEST(streaming_sax_chunked_stream_boundaries) { const std::string doc = "{\"msg\":\"hello\",\"arr\":[1,2,3],\"nested\":{\"ok\":true}}"; ChunkedIStream in(doc, 1); RecordingHandler h; - pjson::ParseError err; - CHECK(pjson::parseSaxStream(in, h, err)); + pJsonParser::Error err; + CHECK(pJsonParser().parseSaxStream(in, h, err)); CHECK(err.ok); CHECK_EQ(h.events.front(), std::string("start-object")); CHECK_EQ(h.events.back(), std::string("end-object")); @@ -299,8 +313,8 @@ TEST(streaming_sax_utf8_escape_and_number_chunk_boundaries) { for (size_t chunk = 1; chunk <= 4; ++chunk) { ChunkedIStream in(doc, chunk); RecordingHandler h; - pjson::ParseError err; - CHECK(pjson::parseSaxStream(in, h, err)); + pJsonParser::Error err; + CHECK(pJsonParser().parseSaxStream(in, h, err)); CHECK(err.ok); CHECK(std::find(h.events.begin(), h.events.end(), std::string("string:\xC3\xA9")) != h.events.end()); @@ -315,8 +329,8 @@ TEST(streaming_sax_crlf_split_reports_coordinates) { const std::string doc = "{\r\n\"a\": [1,\r\n]}"; ChunkedIStream in(doc, 1); RecordingHandler h; - pjson::ParseError err; - CHECK(!pjson::parseSaxStream(in, h, err)); + pJsonParser::Error err; + CHECK(!pJsonParser().parseSaxStream(in, h, err)); CHECK_EQ(err.line, size_t(3)); CHECK_EQ(err.column, size_t(1)); } @@ -325,18 +339,18 @@ TEST(streaming_sax_duplicate_key_policies) { const std::string doc = "{\"a\":1,\"a\":2}"; RecordingHandler keepFirst; - pjson::ParseOptions first; - first.duplicateKeys = pjson::ParseOptions::KeepFirstDuplicate; - CHECK(pjson::parseSax(doc, keepFirst, first)); + pJsonParser::Options first; + first.duplicateKeys = pJsonParser::Options::KeepFirstDuplicate; + CHECK(pJsonParser(first).parseSax(doc, keepFirst)); const std::vector wantFirst = {"start-object", "key:a", "int:1", "end-object"}; CHECK_EQ(keepFirst.events.size(), wantFirst.size()); for (size_t i = 0; i < wantFirst.size(); ++i) CHECK_EQ(keepFirst.events[i], wantFirst[i]); RecordingHandler keepLast; - pjson::ParseOptions last; - last.duplicateKeys = pjson::ParseOptions::KeepLastDuplicate; - CHECK(pjson::parseSax(doc, keepLast, last)); + pJsonParser::Options last; + last.duplicateKeys = pJsonParser::Options::KeepLastDuplicate; + CHECK(pJsonParser(last).parseSax(doc, keepLast)); CHECK_EQ(keepLast.events.size(), size_t(6)); CHECK_EQ(keepLast.events[1], std::string("key:a")); CHECK_EQ(keepLast.events[2], std::string("int:1")); @@ -344,14 +358,14 @@ TEST(streaming_sax_duplicate_key_policies) { CHECK_EQ(keepLast.events[4], std::string("int:2")); RecordingHandler reject; - pjson::ParseError err; - CHECK(!pjson::parseSax(doc, reject, err)); + pJsonParser::Error err; + CHECK(!pJsonParser().parseSax(doc, reject, err)); CHECK(!err.ok); CHECK(err.message.find("duplicate") != std::string::npos); ChunkedIStream streamed(doc, 1); RecordingHandler streamReject; - CHECK(!pjson::parseSaxStream(streamed, streamReject, err)); + CHECK(!pJsonParser().parseSaxStream(streamed, streamReject, err)); CHECK_EQ(err.offset, size_t(7)); CHECK_EQ(err.line, size_t(1)); CHECK_EQ(err.column, size_t(8)); @@ -359,8 +373,8 @@ TEST(streaming_sax_duplicate_key_policies) { TEST(streaming_sax_errors_report_line_and_column) { RecordingHandler h; - pjson::ParseError err; - CHECK(!pjson::parseSax("{\r\n \"a\": [1,\r\n}", h, err)); + pJsonParser::Error err; + CHECK(!pJsonParser().parseSax("{\r\n \"a\": [1,\r\n}", h, err)); CHECK(!err.ok); CHECK_EQ(err.line, size_t(3)); CHECK_EQ(err.column, size_t(1)); @@ -369,41 +383,59 @@ TEST(streaming_sax_errors_report_line_and_column) { TEST(streaming_sax_cancel_and_throw_become_parse_error) { CancelAfterNHandler cancel(3); - pjson::ParseError err; - CHECK(!pjson::parseSax("[1,2,3]", cancel, err)); + pJsonParser::Error err; + CHECK(!pJsonParser().parseSax("[1,2,3]", cancel, err)); CHECK(!err.ok); + CHECK_EQ(err.code, pJsonParser::Error::CallbackError); CHECK(err.message.find("aborted") != std::string::npos); ThrowingHandler throwing; - CHECK(!pjson::parseSax("{\"a\":1}", throwing, err)); + CHECK(!pJsonParser().parseSax("{\"a\":1}", throwing, err)); CHECK(!err.ok); + CHECK_EQ(err.code, pJsonParser::Error::CallbackError); CHECK(err.message.find("exception") != std::string::npos); ChunkedIStream throwingStream("{\"a\":1}", 1); ThrowingHandler streamThrowing; - CHECK(!pjson::parseSaxStream(throwingStream, streamThrowing, err)); + CHECK(!pJsonParser().parseSaxStream(throwingStream, streamThrowing, err)); CHECK(!err.ok); + CHECK_EQ(err.code, pJsonParser::Error::CallbackError); CHECK(err.message.empty() || err.message.find("exception") != std::string::npos); BadAllocHandler allocationFailure; - CHECK(!pjson::parseSax("\"value\"", allocationFailure, err)); + CHECK(!pJsonParser().parseSax("\"value\"", allocationFailure, err)); CHECK(!err.ok); + CHECK_EQ(err.code, pJsonParser::Error::AllocationFailure); CHECK(err.message.empty() || err.message.find("memory") != std::string::npos); ChunkedIStream stream("\"value\"", 1); BadAllocHandler streamedAllocationFailure; - CHECK(!pjson::parseSaxStream(stream, streamedAllocationFailure, err)); + CHECK(!pJsonParser().parseSaxStream(stream, streamedAllocationFailure, err)); CHECK(!err.ok); + CHECK_EQ(err.code, pJsonParser::Error::AllocationFailure); CHECK(err.message.empty() || err.message.find("memory") != std::string::npos); } TEST(streaming_sax_null_stream_buffer_reports_read_failure) { std::istream input(nullptr); RecordingHandler handler; - pjson::ParseError error; + pJsonParser::Error error; + + CHECK(!pJsonParser().parseSaxStream(input, handler, error)); + CHECK(!error.ok); + CHECK_EQ(error.code, pJsonParser::Error::StreamError); + CHECK(error.message.find("stream read failed") != std::string::npos); + CHECK(handler.events.empty()); +} + +TEST(streaming_sax_streambuf_exception_reports_stream_error) { + ThrowingIStream input; + RecordingHandler handler; + pJsonParser::Error error; - CHECK(!pjson::parseSaxStream(input, handler, error)); + CHECK(!pJsonParser().parseSaxStream(input, handler, error)); CHECK(!error.ok); + CHECK_EQ(error.code, pJsonParser::Error::StreamError); CHECK(error.message.find("stream read failed") != std::string::npos); CHECK(handler.events.empty()); } @@ -411,27 +443,29 @@ TEST(streaming_sax_null_stream_buffer_reports_read_failure) { TEST(streaming_sax_number_range_matches_dom_parser) { const char* overflows[] = {"1e400", "-1e400"}; for (size_t i = 0; i < sizeof(overflows) / sizeof(overflows[0]); ++i) { - CHECK(pjson::parse(overflows[i]) == nullptr); + CHECK(pjson_test::parse(overflows[i]) == nullptr); NumberHandler handler; - pjson::ParseError err; - CHECK(!pjson::parseSax(overflows[i], handler, err)); + pJsonParser::Error err; + CHECK(!pJsonParser().parseSax(overflows[i], handler, err)); CHECK(!err.ok); CHECK(!handler.sawDouble); ChunkedIStream stream(overflows[i], 1); NumberHandler streamHandler; - CHECK(!pjson::parseSaxStream(stream, streamHandler, err)); + CHECK(!pJsonParser().parseSaxStream(stream, streamHandler, err)); CHECK(!err.ok); CHECK(!streamHandler.sawDouble); } const char* accepted[] = {"1e-400", "4.9406564584124654e-324"}; + pJsonParser::Options lossy; + lossy.numberPolicy = pJsonParser::Options::AllowLossyNumbers; for (size_t i = 0; i < sizeof(accepted) / sizeof(accepted[0]); ++i) { - pjson::unique_ptr dom = pjson::parse(accepted[i]); + pjson_test::Parsed dom = pjson_test::parse(accepted[i], lossy); CHECK(dom != nullptr); NumberHandler handler; - pjson::ParseError err; - CHECK(pjson::parseSax(accepted[i], handler, err)); + pJsonParser::Error err; + CHECK(pJsonParser(lossy).parseSax(accepted[i], handler, err)); CHECK(err.ok); CHECK(handler.sawDouble); double domValue = 1.0; @@ -440,7 +474,7 @@ TEST(streaming_sax_number_range_matches_dom_parser) { ChunkedIStream stream(accepted[i], 1); NumberHandler streamHandler; - CHECK(pjson::parseSaxStream(stream, streamHandler, err)); + CHECK(pJsonParser(lossy).parseSaxStream(stream, streamHandler, err)); CHECK_EQ(err.message, std::string()); CHECK(streamHandler.sawDouble); CHECK_EQ(streamHandler.value, domValue); @@ -451,28 +485,28 @@ TEST(streaming_sax_max_input_bytes_and_max_nodes_on_stream) { const std::string doc = "[1,2,3,4]"; ChunkedIStream in1(doc, 2); RecordingHandler h1; - pjson::ParseOptions bytes; + pJsonParser::Options bytes; bytes.maxInputBytes = 4; - pjson::ParseError err; - CHECK(!pjson::parseSaxStream(in1, h1, err, bytes)); + pJsonParser::Error err; + CHECK(!pJsonParser(bytes).parseSaxStream(in1, h1, err)); CHECK(!err.ok); CHECK_EQ(err.offset, size_t(4)); CHECK(err.message.find("maxInputBytes") != std::string::npos); ChunkedIStream in2(doc, 2); RecordingHandler h2; - pjson::ParseOptions nodes; + pJsonParser::Options nodes; nodes.maxNodes = 3; - CHECK(!pjson::parseSaxStream(in2, h2, err, nodes)); + CHECK(!pJsonParser(nodes).parseSaxStream(in2, h2, err)); CHECK(!err.ok); CHECK(err.message.find("node budget") != std::string::npos); const std::string nested = "[[1]]"; ChunkedIStream in3(nested, 1); RecordingHandler h3; - pjson::ParseOptions depth; + pJsonParser::Options depth; depth.maxDepth = 0; // same effective minimum limit as the DOM parser - CHECK(!pjson::parseSaxStream(in3, h3, err, depth)); + CHECK(!pJsonParser(depth).parseSaxStream(in3, h3, err)); CHECK(err.message.find("depth") != std::string::npos); } @@ -491,7 +525,7 @@ TEST(streaming_sax_large_stream_does_not_need_full_buffer) { ChunkedIStream in(doc, 7); RecordingHandler h; - CHECK(pjson::parseSaxStream(in, h)); + CHECK(pJsonParser().parseSaxStream(in, h)); CHECK_EQ(h.events.front(), std::string("start-array")); CHECK_EQ(h.events.back(), std::string("end-array")); CHECK_EQ(h.events.size(), size_t(2002)); diff --git a/scripts/benchmark-aux-metrics.py b/scripts/benchmark-aux-metrics.py new file mode 100644 index 0000000..e5be5d9 --- /dev/null +++ b/scripts/benchmark-aux-metrics.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +"""Record portable build-artifact sizes alongside a benchmark report.""" + +import argparse +import json +from pathlib import Path + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("report", help="pjson-benchmark v1 JSON report") + parser.add_argument("--artifact", action="append", default=[], metavar="PATH") + parser.add_argument("--output", required=True) + args = parser.parse_args() + + with open(args.report, encoding="utf-8") as stream: + benchmark = json.load(stream) + if benchmark.get("format") != "pjson-benchmark" or benchmark.get("format_version") != 1: + raise SystemExit("unsupported benchmark report format") + + artifacts = [] + for item in args.artifact: + path = Path(item) + if not path.is_file(): + raise SystemExit(f"artifact is not a file: {item}") + artifacts.append({"path": item, "bytes": path.stat().st_size}) + + output = { + "format": "pjson-benchmark-aux", + "format_version": 1, + "source": benchmark.get("source", {}), + "environment": benchmark.get("environment", {}), + "build": benchmark.get("build", {}), + "artifacts": artifacts, + "notes": [ + "Artifact sizes are filesystem byte counts.", + "Peak RSS and allocation counts are intentionally omitted unless a controlled platform-specific collector supplies them.", + ], + } + with open(args.output, "w", encoding="utf-8") as stream: + json.dump(output, stream, indent=2, sort_keys=True) + stream.write("\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/compare-benchmarks.py b/scripts/compare-benchmarks.py new file mode 100644 index 0000000..8b56566 --- /dev/null +++ b/scripts/compare-benchmarks.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +"""Compare two pjson-benchmark v1 reports without overclaiming precision.""" + +import argparse +import json +import math +import sys + + +def load(path): + with open(path, encoding="utf-8") as stream: + report = json.load(stream) + if report.get("format") != "pjson-benchmark" or report.get("format_version") != 1: + raise ValueError(f"{path}: unsupported benchmark report format") + return report + + +def environment_key(report): + environment = report.get("environment", {}) + build = report.get("build", {}) + return { + "label": environment.get("label"), + "operating_system": environment.get("operating_system"), + "architecture": environment.get("architecture"), + "cpu": environment.get("cpu"), + "allocator": environment.get("allocator"), + "compiler_id": build.get("compiler_id"), + "compiler_version": build.get("compiler_version"), + "build_type": build.get("type"), + "flags": build.get("flags"), + } + + +def source_key(report): + source = report.get("source", {}) + return source.get("fingerprint") or source.get("commit") + + +def indexed(report): + rows = report.get("results") + if not isinstance(rows, list) or not rows: + raise ValueError("benchmark report has no results") + result = {} + for row in rows: + key = (row["library"], row["workload"], row["operation"]) + if key in result: + raise ValueError(f"benchmark report has duplicate case: {key!r}") + median = float(row["median_ns"]) + if not math.isfinite(median) or median < 0.0: + raise ValueError(f"benchmark case has invalid median_ns: {key!r}") + result[key] = row + return result + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("baseline") + parser.add_argument("candidate") + parser.add_argument("--threshold-percent", type=float, default=10.0) + parser.add_argument("--allow-environment-mismatch", action="store_true") + parser.add_argument("--fail-on-regression", action="store_true") + args = parser.parse_args() + + baseline = load(args.baseline) + candidate = load(args.candidate) + before_environment = environment_key(baseline) + after_environment = environment_key(candidate) + differences = [ + key for key in before_environment if before_environment[key] != after_environment[key] + ] + if differences and not args.allow_environment_mismatch: + print("benchmark reports are not from comparable environments:", file=sys.stderr) + for key in differences: + print( + f" {key}: {before_environment[key]!r} != {after_environment[key]!r}", + file=sys.stderr, + ) + return 2 + if source_key(baseline) == source_key(candidate): + print("warning: reports have the same source fingerprint", file=sys.stderr) + + before = indexed(baseline) + after = indexed(candidate) + before_keys = set(before) + after_keys = set(after) + if before_keys != after_keys: + print("benchmark reports do not contain the same cases:", file=sys.stderr) + for key in sorted(before_keys - after_keys): + print(f" missing from candidate: {key!r}", file=sys.stderr) + for key in sorted(after_keys - before_keys): + print(f" missing from baseline: {key!r}", file=sys.stderr) + return 2 + common = sorted(before_keys) + + regressions = 0 + print("library workload operation baseline_us candidate_us change status") + for key in common: + old = float(before[key]["median_ns"]) + new = float(after[key]["median_ns"]) + if old == 0.0: + change = 0.0 if new == 0.0 else math.inf + else: + change = ((new / old) - 1.0) * 100.0 + status = "REGRESSION" if change > args.threshold_percent else "ok" + if status == "REGRESSION": + regressions += 1 + print( + f"{key[0]} {key[1]} {key[2]} {old / 1000:.2f} {new / 1000:.2f} " + f"{change:+.1f}% {status}" + ) + + print( + f"compared={len(common)} regressions={regressions} " + f"threshold={args.threshold_percent:.1f}%" + ) + return 1 if regressions and args.fail_on_regression else 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError) as error: + print(error, file=sys.stderr) + sys.exit(2) diff --git a/scripts/fetch-json-schema-test-suite.sh b/scripts/fetch-json-schema-test-suite.sh index 6f18618..534ccfb 100755 --- a/scripts/fetch-json-schema-test-suite.sh +++ b/scripts/fetch-json-schema-test-suite.sh @@ -96,6 +96,11 @@ if [ ! -d "${DEST}/tests/draft7" ]; then exit 1 fi +if [ ! -d "${DEST}/tests/draft2020-12" ]; then + echo "Expected tests/draft2020-12 directory not found under ${DEST}" >&2 + exit 1 +fi + if [ "$(git -C "${DEST}" rev-parse HEAD)" != "${PINNED_COMMIT}" ]; then echo "JSON-Schema-Test-Suite checkout is not at the pinned commit ${PINNED_COMMIT}" >&2 exit 1 @@ -106,4 +111,4 @@ echo "Pinned commit: ${PINNED_COMMIT}" echo "Run tests with:" echo " export PJSON_JSON_SCHEMA_TEST_SUITE_DIR=${DEST}" echo " ctest --test-dir ${REPO_ROOT}/out/build-debug" -echo " -R schema_official_draft7_optional -V" +echo " -R 'schema_official_(draft7|draft2020)_optional' -V" diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt index 8eea462..a8e2bef 100644 --- a/test_package/CMakeLists.txt +++ b/test_package/CMakeLists.txt @@ -15,3 +15,7 @@ find_package(pjson CONFIG REQUIRED) add_executable(pjson_package_test src/pjson_package_test.cpp) target_compile_features(pjson_package_test PRIVATE cxx_std_11) target_link_libraries(pjson_package_test PRIVATE pjson::pjson) +set_target_properties(pjson_package_test PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES +) diff --git a/test_package/src/pjson_package_test.cpp b/test_package/src/pjson_package_test.cpp index 8bddc23..2741d89 100644 --- a/test_package/src/pjson_package_test.cpp +++ b/test_package/src/pjson_package_test.cpp @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include +#include // ---- Conan package consumer smoke test --------------------------------- @@ -11,12 +13,15 @@ int main() { ByteDance::pjson value; value["packaged"] = true; + value["answer"] = 42; + value["values"] = std::vector({1, 2, 3}); - const ByteDance::pjson::unique_ptr parsed = ByteDance::pjson::parse(value.toString()); + ByteDance::pJsonParser::Error error; + const ByteDance::pjson parsed = ByteDance::pJsonParser().parse(value.toString(), error); bool packaged = false; // A successful package preserves the sentinel property through a round // trip and keeps the public header macro in sync with the linked library. - return parsed && parsed->tryGet("packaged", packaged) && packaged && + return error.ok && parsed.tryGet("packaged", packaged) && packaged && std::string(ByteDance::pjson::getVersion()) == PJSON_VERSION ? 0 : 1; diff --git a/tests/install-consumer/CMakeLists.txt b/tests/install-consumer/CMakeLists.txt index 384bdef..5475f1e 100644 --- a/tests/install-consumer/CMakeLists.txt +++ b/tests/install-consumer/CMakeLists.txt @@ -12,10 +12,10 @@ option(PJSON_CONSUMER_USE_PKGCONFIG "Consume pjson through pkg-config" OFF) if(PJSON_CONSUMER_USE_PKGCONFIG) find_package(PkgConfig REQUIRED) - pkg_check_modules(pjson REQUIRED IMPORTED_TARGET pjson>=1.0) + pkg_check_modules(pjson REQUIRED IMPORTED_TARGET pjson>=2.0) set(PJSON_CONSUMER_TARGET PkgConfig::pjson) else() - find_package(pjson 1.0 CONFIG REQUIRED) + find_package(pjson 2.0 CONFIG REQUIRED) set(PJSON_CONSUMER_TARGET pjson::pjson) endif() @@ -27,6 +27,8 @@ set_target_properties(pjson_install_consumer PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN YES ) # Register a runtime check in addition to proving that discovery and linking diff --git a/tests/install-consumer/main.cpp b/tests/install-consumer/main.cpp index 7f08dce..a13bd2a 100644 --- a/tests/install-consumer/main.cpp +++ b/tests/install-consumer/main.cpp @@ -1,10 +1,21 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include +#include #include #include #include +#include + +static_assert(PJSON_ABI_VERSION == 2, "unexpected pjson ABI generation"); +static_assert(sizeof(ByteDance::pjson) == sizeof(void*) * 2, + "installed pjson must use the two-pointer ABI"); +static_assert(sizeof(ByteDance::pJsonParser) == sizeof(void*), + "installed parser must use the one-pointer ABI"); +static_assert(sizeof(ByteDance::pJsonSchemaValidator) == sizeof(void*), + "installed schema validator must use the one-pointer ABI"); // ---- Installed-package consumer smoke test ----------------------------- @@ -12,24 +23,64 @@ // metadata, linkage, parsing, typed access, and compact serialization. int main() { using ByteDance::pjson; + using ByteDance::pJsonParser; + using ByteDance::pJsonSchemaValidator; // The public macro and linked library function must identify the same // release; this also detects stale headers paired with a different binary. - if (std::strcmp(PJSON_VERSION, "1.0.0") != 0 || - std::strcmp(pjson::getVersion(), "1.0.0") != 0) { + if (std::strcmp(PJSON_VERSION, "2.0.0") != 0 || + std::strcmp(pjson::getVersion(), "2.0.0") != 0) { std::cerr << "unexpected pjson version" << std::endl; return 1; } // A compact round trip covers the main installed API without relying on // any source-tree-only headers or test helpers. - pjson::unique_ptr document = pjson::parse("{\"answer\":42}"); + pJsonParser::Error error; + pjson document = pJsonParser().parse("{\"answer\":42}", error); + pjson built; + built["answer"] = 42; + built["values"] = std::vector({1, 2, 3}); + built["long"] = 4L; + built["unsignedLong"] = 5UL; + built["longLong"] = 6LL; + built["unsignedLongLong"] = 7ULL; + built["longDouble"] = 8.5L; + pJsonParser::Error expectedError; + pjson expectedBuilt = + pJsonParser().parse("{\"answer\":42,\"values\":[1,2,3],\"long\":4,\"unsignedLong\":5," + "\"longLong\":6,\"unsignedLongLong\":7,\"longDouble\":8.5}", + expectedError); int64_t answer = 0; - if (!document || !document->tryGet("answer", answer) || answer != 42 || - document->toString() != "{\"answer\":42}") { + if (!error.ok || !document.tryGet("answer", answer) || answer != 42 || + document.toString() != "{\"answer\":42}" || !expectedError.ok || built != expectedBuilt) { std::cerr << "installed pjson failed its consumer smoke test" << std::endl; return 1; } + // The standalone schema validator ships in its own installed header and + // consumes only the public API; confirm an external consumer can compile a + // schema and validate against it. + pJsonParser::Error schemaError; + pjson schema = + pJsonParser().parse("{\"type\":\"object\",\"required\":[\"answer\"]}", schemaError); + if (!schemaError.ok) { + std::cerr << "installed pjson_schema failed to parse its schema" << std::endl; + return 1; + } + pJsonSchemaValidator validator(schema); + if (!validator.isSchemaValid() || + validator.dialect() != pJsonSchemaValidator::documentedSubsetDialectUri()) { + std::cerr << "installed pjson_schema failed its dialect contract" << std::endl; + return 1; + } + std::vector schemaErrors; + pjson missing = pJsonParser().parse("{}", schemaError); + if (!validator.validate(document) || validator.validate(missing, schemaErrors) || + schemaErrors.empty()) { + std::cerr << "installed pjson_schema failed its consumer smoke test" << std::endl; + return 1; + } + return 0; } diff --git a/tests/test_benchmark_tools.py b/tests/test_benchmark_tools.py new file mode 100644 index 0000000..a8ae083 --- /dev/null +++ b/tests/test_benchmark_tools.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2026 ByteDance Ltd. and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 + +import json +import subprocess +import sys +import tempfile +from pathlib import Path + + +def report(label, median): + return { + "format": "pjson-benchmark", + "format_version": 1, + "source": {"commit": "abc", "fingerprint": "abc:123"}, + "environment": { + "label": label, + "operating_system": "test", + "architecture": "test", + "cpu": "test", + "allocator": "test", + }, + "build": { + "compiler_id": "test", + "compiler_version": "1", + "type": "Release", + "flags": "-O2", + }, + "results": [ + { + "library": "pjson", + "workload": "small", + "operation": "parse", + "median_ns": median, + } + ], + } + + +def run(command, expected): + result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + if result.returncode != expected: + raise SystemExit( + f"expected exit {expected}, got {result.returncode}: {result.stdout}{result.stderr}" + ) + + +def main(): + root = Path(__file__).resolve().parents[1] + comparator = root / "scripts" / "compare-benchmarks.py" + with tempfile.TemporaryDirectory() as directory: + directory = Path(directory) + baseline = directory / "baseline.json" + candidate = directory / "candidate.json" + mismatch = directory / "mismatch.json" + missing = directory / "missing.json" + duplicate = directory / "duplicate.json" + zero = directory / "zero.json" + baseline.write_text(json.dumps(report("controlled", 100.0)), encoding="utf-8") + candidate.write_text(json.dumps(report("controlled", 120.0)), encoding="utf-8") + mismatch.write_text(json.dumps(report("different", 100.0)), encoding="utf-8") + missing_report = report("controlled", 120.0) + missing_report["results"] = [] + missing.write_text(json.dumps(missing_report), encoding="utf-8") + duplicate_report = report("controlled", 120.0) + duplicate_report["results"].append(dict(duplicate_report["results"][0])) + duplicate.write_text(json.dumps(duplicate_report), encoding="utf-8") + zero.write_text(json.dumps(report("controlled", 0.0)), encoding="utf-8") + run([sys.executable, str(comparator), str(baseline), str(candidate)], 0) + run( + [ + sys.executable, + str(comparator), + str(baseline), + str(candidate), + "--threshold-percent", + "10", + "--fail-on-regression", + ], + 1, + ) + run([sys.executable, str(comparator), str(baseline), str(mismatch)], 2) + run([sys.executable, str(comparator), str(baseline), str(missing)], 2) + run([sys.executable, str(comparator), str(baseline), str(duplicate)], 2) + run( + [ + sys.executable, + str(comparator), + str(zero), + str(candidate), + "--fail-on-regression", + ], + 1, + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())