Author SHA1 Message Date
weaselbot bf3f2fe810 Avoid nullptr subtraction using intptr_t casts
Andrew's review on the previous fix noted that the nullptr checks produced slightly worse codegen. Replace the pointer subtraction with intptr_t subtraction, which avoids the undefined behaviour of subtracting two null pointers without introducing extra branches.
2026-06-29 15:14:36 -04:00
weaselbot bd53e57b8e Avoid nullptr subtraction when flushing scalars at EOF
When a scalar ends exactly at a chunk boundary, Parser3::parse resets
dataBegin and writeBuf to the new buf at the start of every call. On the
EOF call buf is null, so both pointers become null. The final
flushNumber/flushString then computed len as buf - dataBegin, i.e.
nullptr - nullptr, which is undefined behaviour in C++.

Compute the flush length safely: if dataBegin is null (or, for raw mode,
buf is null), treat the length as zero. Use an empty string literal as a
non-null data pointer for the zero-length, done=true callback so callers
still receive the completion signal.

Add a regression test covering a number that fills its chunk exactly and
is finalized by an EOF call.

Fixes #40
2026-06-29 13:57:58 -04:00
andrew 08b864d31b Reject invalid UTF-16 surrogate pairs and lone low surrogates for clarity
The string unescape logic treated any code unit in the surrogate range

(0xD800-0xDFFF) as the start of a surrogate pair. The concrete example

from issue #39 (\uDC00\uDC00) was already rejected by the existing

0x10FFFF bounds check, but the check was misleading and would accept a

lone low surrogate as a regular BMP code point if the second surrogate

happened to be in a narrower range. Clean this up so the intent is

obvious.

Changes in src/parser3.h:

- Fast path (n_string2): only treat high surrogates (0xD800-0xDBFF) as

the start of a surrogate pair, and explicitly reject lone low surrogates

(0xDC00-0xDFFF).

- Slow path (t_hex2): same high-surrogate check, with explicit rejection

of lone low surrogates.

The t_hex3 path already validates that the second code unit is a low

surrogate (0xDC00-0xDFFF), so no change is needed there.

Also commit the current fuzzer corpus.

Closes #39
2026-06-29 13:37:58 -04:00
andrew 4ce2002663 Merge pull request 'schemagen: reject scalar root values when root schema is object/array' (#32) from weaselbot/weaseljson:weaselbot/issue-16 into main
Reviewed-on: weaselab/weaseljson#32
2026-06-24 19:34:06 +00:00
weaselbot df693ef4c9 schemagen: reject scalar root values when root schema is object/array
The generated RootBuilder crashed (undefined behavior on std::vector::back())
when a JSON document's root value was a scalar or null while the schema
declared a non-nullable object or array root. The stack starts empty for
object/array roots, but cbStringData, cbNumberData, and cbBool called
stack_.back() without checking for an empty stack.

Add an empty-stack guard to the three scalar callbacks so they reject
instead of crashing. cbNull already handles the empty-stack case.

Regression tests added for:
- non-nullable object root rejecting null, boolean, number, and string roots
- nullable object root rejecting scalar roots
- nullable array root rejecting scalar roots

Closes #16
2026-06-24 14:30:36 -04:00
andrew e5cbaac401 Merge pull request 'schemagen: cache non-object $defs entries to avoid duplicate types' (#31) from weaselbot/weaseljson:weaselbot/issue-17 into main
Reviewed-on: weaselab/weaseljson#31
2026-06-24 17:02:38 +00:00
weaselbot e155e0bbf4 schemagen: restore nullable tuple return for object schemas
The previous change to cache object definitions started returning the
bare TObj from build_type for object schemas, discarding the nullable
flag. This caused nullable root objects to be emitted as plain structs
instead of std::optional<RootInner>, breaking the nullable root tests.

Return the (TObj, nullable) tuple so callers (including the root
emitter) see the correct nullability again.
2026-06-24 09:51:19 -04:00
weaselbot 717f30099f schemagen: cache non-object $defs entries to avoid duplicate types
Extend the existing per-definition cache (`self._building`) to enum,
array, and scalar $defs, not just object definitions. This ensures
that multiple $refs to the same non-object definition reuse the same
C++ type instead of generating Role, Role2, Role3, etc.

- Cache the built (type, nullable) tuple under defname for enum,
  scalar, and array definitions.
- Pre-register array definitions before recursing into items so $ref
  cycles resolve to the same TArr instance.
- Store object definitions as (TObj, nullable) tuples so nullable object
  $defs also preserve their nullability when referenced.

Add a regression test for issue #17 covering reused enum and array-of-enum
$defs.
2026-06-23 22:16:34 -04:00
andrew 1404ebdfbd Merge pull request 'fix schemagen integer parsing boundary bugs' (#30) from weaselbot/weaseljson:weaselbot/issue-19 into main
Reviewed-on: weaselab/weaseljson#30
2026-06-24 00:40:09 +00:00
weaselbot 38079cc278 schemagen: parse integer fallback exactly for decimal/exponent forms
Replace the double-based fallback in generated integer slots with a
string-to-int64 parser that handles decimal points and exponents
without losing precision near the int64 boundaries.

The old path used std::from_chars<double> and compared against
±9223372036854775808.0, which rounds the int64 max and min so that
valid values are rejected and out-of-range negatives are accepted.

The new helper:
- Parses sign, integer part, optional fraction, and optional exponent.
- Strips trailing zeros to cancel fractional places.
- Rejects non-integral values and overflow using exact uint64_t
  arithmetic.

Adds regression tests covering root integer and object-field integer
boundary values, including the cases from issue #19.
2026-06-23 18:36:53 -04:00
andrew ceb4bc1041 Merge pull request 'schemagen: drop support for additionalProperties: true' (#28) from weaselbot/weaseljson:weaselbot/issue-20 into main
Reviewed-on: weaselab/weaseljson#28
2026-06-23 20:56:58 +00:00
weaselbot 16f13c241c schemagen: drop support for additionalProperties: true
Following review feedback, the generator no longer supports permissive
objects. Changes:

- Reject `additionalProperties: true` at generation time.
- Treat an absent `additionalProperties` as `false`, so every object is
  strict by default and unknown keys are rejected during parsing.
- Remove the now-dead permissive-object infrastructure: `Kind::Skip`,
  `Cat::Skip`, `kSkip`, the per-frame `unknown` key set, and `isStrict()`.
- Update the README feature/rejection tables accordingly.
- Remove the permissive "loose" object from example.schema.json and the
  associated tests from test_gen.cpp.
- Add Python unit tests verifying the new `additionalProperties` behavior.

All tests pass (`ctest --output-on-failure`).
2026-06-23 15:11:48 -04:00
weaselbot ab95fefb09 schemagen: reject duplicate unknown keys in non-strict objects
Track unknown keys in a per-object unordered_set so that permissive
objects (additionalProperties absent/true) still reject duplicate keys,
matching the README guarantee.

- Add std::unordered_set<std::string> to Frame.
- Insert unknown keys in cbKeyData and reject duplicates before skipping.
- Add a permissive "loose" subobject to example.schema.json.
- Test single unknown key accepted and duplicate unknown/known keys rejected.
2026-06-23 15:08:48 -04:00
andrew e8830e27e9 Merge pull request 'schemagen: reserve generated Root/Skip/RootScalar and avoid Kind/ArrN collisions' (#27) from weaselbot/weaseljson:weaselbot/issue-21 into main
Reviewed-on: weaselab/weaseljson#27
2026-06-23 17:48:57 +00:00
andrew 5427db4b3d Merge pull request 'schemagen: fix nullable root types (#13)' (#29) from weaselbot/weaseljson:weaselbot/issue-13 into main
Reviewed-on: weaselab/weaseljson#29
2026-06-23 17:43:19 +00:00
weaselbot b5491afb38 schemagen: fix nullable root types (#13)
The C++ code generator now handles schemas where the top-level type is
nullable ("type": ["object", "null"], ["string", "null"], or
["array", "null"]).

Changes to weaseljson_schemagen.py:
- Rename the inner object struct when the root is a nullable object, so
  the `using Root = std::optional<...>` alias no longer conflicts with
  `struct Root`.
- Treat nullable root objects and arrays as container roots, emplacing
  the inner value before pushing the root frame and pointing the frame at
  the contained value.
- For nullable root scalars/enums, engage() now returns a pointer to the
  value inside the optional rather than to the optional wrapper itself.
- cbNull() now safely accepts a top-level null when the root is nullable
  and rejects it otherwise.

Regression tests added:
- nullable_object.schema.json + test_nullable_root.cpp
- nullable_string.schema.json
- nullable_array.schema.json

Closes #13
2026-06-23 12:58:28 -04:00
weaselbot 46ff8e2164 schemagen: reserve generated Root/Skip/RootScalar and avoid Kind/ArrN collisions
Make the type-name allocator aware of the identifiers the generator emits
itself (`Root` alias, `Skip`/`RootScalar` Kind enumerators) so user `$defs`
names can no longer collide with them.  Array-kind names (`Arr0`, `Arr1`, ...)
are now allocated only after checking for object/enum names, preventing
duplicate `Kind` enumerators when a schema defines e.g. `Arr0`.

Add Python regression tests that also syntax-check the generated headers
with a C++ compiler.

Closes #21
2026-06-23 12:23:09 -04:00
andrew 8d37b9b602 Merge pull request 'Reject negative lengths in WeaselJsonParser_parse' (#25) from weaselbot/weaseljson:weaselbot/issue-24 into main
Reviewed-on: weaselab/weaseljson#25
2026-06-22 18:53:20 +00:00
andrew 3d7dc97471 Merge pull request 'check WeaselJsonParser_create return value in Python bindings' (#26) from weaselbot/weaseljson:weaselbot/issue-23 into main
Reviewed-on: weaselab/weaseljson#26
2026-06-22 18:52:46 +00:00
weaselbot a26e101191 check WeaselJsonParser_create return value in Python bindings
Raise ValueError from __init__ when the C constructor returns NULL,
instead of storing a NULL pointer that segfaults on parse()/reset().
Add defensive RuntimeError checks in parse() and reset() for closed
or failed parsers.

Add a test covering stackSize values that the C API rejects (-1, 0, 1, 2).

Fixes #23
2026-06-22 02:29:13 -04:00
weaselbot 43e3c9904f Reject negative lengths in WeaselJsonParser_parse
`Parser3::parse` previously formed `buf + len` immediately, so passing a
negative `len` from the C API caused undefined pointer arithmetic. Add an
explicit `len < 0` check that returns `WeaselJson_REJECT` (and makes the
rejected state sticky) before any `buf + len` computation.

Also document the non-negative length precondition in the public header
and add a regression test.

Closes #24
2026-06-22 02:26:30 -04:00
andrew 5e18347e35 Merge pull request 'schemagen: add C++20 keywords to the C++ keyword allow-list' (#22) from weaselbot/weaseljson:weaselbot/issue-18 into main
Reviewed-on: weaselab/weaseljson#22
2026-06-22 00:54:04 +00:00
weaselbot 9cd74631b6 schemagen: add C++20 keywords to the C++ keyword allow-list
Add concept, consteval, constinit, co_await, co_return, co_yield,
requires, module, and import to _CPP_KEYWORDS so property names that
happen to be C++20 keywords get sanitized with a trailing underscore.

Closes #18
2026-06-21 12:52:07 -04:00
andrew 05185eb3ee Merge pull request 'Make parser reject state sticky after WeaselJson_REJECT' (#15) from weaselbot/weaseljson:weaselbot/issue-12 into main
Reviewed-on: weaselab/weaseljson#15
2026-06-19 17:17:10 +00:00
weaselbot 9839104635 Make parser reject state sticky after WeaselJson_REJECT
After the parser returns WeaselJson_REJECT once, subsequent calls to
WeaselJsonParser_parse (including the final len==0 EOF call) must keep
returning WeaselJson_REJECT instead of potentially reporting OK.

- Add a `rejected` flag to Parser3.
- Clear the flag in reset().
- Check the flag at the start of parse() and immediately return REJECT.
- Set the flag whenever a continuation returns REJECT.

Add a test covering the exact reproduction from issue #12.
2026-06-19 13:08:28 -04:00
andrew b641489a59 Merge pull request 'schemagen: support objects with >32 properties' (#9) from weaselbot/weaseljson:weaselbot/issue-3 into main
Reviewed-on: weaselab/weaseljson#9
2026-06-18 22:23:52 +00:00
weaselbot 2ad15708eb ci: register schemagen regression tests with ctest
Remove the standalone "Run schemagen tests" workflow step and instead
add the big-schema regression test to contrib/schemagen/CMakeLists.txt
so ctest picks it up alongside schemagen_example. Update README to
document both `ctest` and the convenience `./run_tests.sh`.

Closes #3
2026-06-18 16:32:50 -04:00
weaselbot ca474e3f99 schemagen: support objects with more than 32 properties
Replace the 32-bit `uint32_t seen` bitmask with a `std::vector<uint64_t>`
bitset sized to the actual field count.  `requiredMask` is now a vector of
the same word count, and required-field checks use per-word masking so that
optional fields do not cause false rejections.

Adds big.schema.json + test_big.cpp regression tests covering the issue
reproducer (40 required properties, missing/duplicate at index 32), plus
run_tests.sh to exercise both test_gen.cpp and test_big.cpp.

Fixes #3
2026-06-18 16:31:26 -04:00
44 changed files with 2372 additions and 112 deletions
+4
View File
@@ -1,2 +1,6 @@
build build
.cache .cache
contrib/schemagen/gen.h
contrib/schemagen/big.h
contrib/schemagen/test_gen
contrib/schemagen/test_big
+70
View File
@@ -12,6 +12,8 @@ add_test(
set(SCHEMAGEN_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/weaseljson_schemagen.py) set(SCHEMAGEN_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/weaseljson_schemagen.py)
set(EXAMPLE_SCHEMA ${CMAKE_CURRENT_SOURCE_DIR}/example.schema.json) set(EXAMPLE_SCHEMA ${CMAKE_CURRENT_SOURCE_DIR}/example.schema.json)
set(GEN_H ${CMAKE_CURRENT_BINARY_DIR}/gen.h) set(GEN_H ${CMAKE_CURRENT_BINARY_DIR}/gen.h)
set(BIG_SCHEMA ${CMAKE_CURRENT_SOURCE_DIR}/big.schema.json)
set(BIG_H ${CMAKE_CURRENT_BINARY_DIR}/big.h)
add_custom_command( add_custom_command(
OUTPUT ${GEN_H} OUTPUT ${GEN_H}
@@ -20,7 +22,15 @@ add_custom_command(
DEPENDS ${SCHEMAGEN_SCRIPT} ${EXAMPLE_SCHEMA} DEPENDS ${SCHEMAGEN_SCRIPT} ${EXAMPLE_SCHEMA}
COMMENT "Generating gen.h from example.schema.json") COMMENT "Generating gen.h from example.schema.json")
add_custom_command(
OUTPUT ${BIG_H}
COMMAND ${Python3_EXECUTABLE} ${SCHEMAGEN_SCRIPT} ${BIG_SCHEMA} -o ${BIG_H}
--namespace big_schema
DEPENDS ${SCHEMAGEN_SCRIPT} ${BIG_SCHEMA}
COMMENT "Generating big.h from big.schema.json")
add_custom_target(schemagen_gen_h DEPENDS ${GEN_H}) add_custom_target(schemagen_gen_h DEPENDS ${GEN_H})
add_custom_target(schemagen_big_h DEPENDS ${BIG_H})
add_executable(schemagen_example ${CMAKE_CURRENT_SOURCE_DIR}/test_gen.cpp) add_executable(schemagen_example ${CMAKE_CURRENT_SOURCE_DIR}/test_gen.cpp)
target_include_directories(schemagen_example target_include_directories(schemagen_example
@@ -29,7 +39,67 @@ target_link_libraries(schemagen_example PRIVATE ${PROJECT_NAME})
target_compile_options(schemagen_example PRIVATE -Wno-switch-enum) target_compile_options(schemagen_example PRIVATE -Wno-switch-enum)
add_dependencies(schemagen_example schemagen_gen_h) add_dependencies(schemagen_example schemagen_gen_h)
add_executable(schemagen_big ${CMAKE_CURRENT_SOURCE_DIR}/test_big.cpp)
target_include_directories(schemagen_big PRIVATE include
${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(schemagen_big PRIVATE ${PROJECT_NAME})
target_compile_options(schemagen_big PRIVATE -Wno-switch-enum)
add_dependencies(schemagen_big schemagen_big_h)
set(NULLABLE_OBJECT_SCHEMA
${CMAKE_CURRENT_SOURCE_DIR}/nullable_object.schema.json)
set(NULLABLE_STRING_SCHEMA
${CMAKE_CURRENT_SOURCE_DIR}/nullable_string.schema.json)
set(NULLABLE_ARRAY_SCHEMA
${CMAKE_CURRENT_SOURCE_DIR}/nullable_array.schema.json)
set(NULLABLE_OBJECT_H ${CMAKE_CURRENT_BINARY_DIR}/nullable_object.h)
set(NULLABLE_STRING_H ${CMAKE_CURRENT_BINARY_DIR}/nullable_string.h)
set(NULLABLE_ARRAY_H ${CMAKE_CURRENT_BINARY_DIR}/nullable_array.h)
add_custom_command(
OUTPUT ${NULLABLE_OBJECT_H}
COMMAND ${Python3_EXECUTABLE} ${SCHEMAGEN_SCRIPT} ${NULLABLE_OBJECT_SCHEMA} -o
${NULLABLE_OBJECT_H} --namespace nullable_object
DEPENDS ${SCHEMAGEN_SCRIPT} ${NULLABLE_OBJECT_SCHEMA}
COMMENT "Generating nullable_object.h")
add_custom_command(
OUTPUT ${NULLABLE_STRING_H}
COMMAND ${Python3_EXECUTABLE} ${SCHEMAGEN_SCRIPT} ${NULLABLE_STRING_SCHEMA} -o
${NULLABLE_STRING_H} --namespace nullable_string
DEPENDS ${SCHEMAGEN_SCRIPT} ${NULLABLE_STRING_SCHEMA}
COMMENT "Generating nullable_string.h")
add_custom_command(
OUTPUT ${NULLABLE_ARRAY_H}
COMMAND ${Python3_EXECUTABLE} ${SCHEMAGEN_SCRIPT} ${NULLABLE_ARRAY_SCHEMA} -o
${NULLABLE_ARRAY_H} --namespace nullable_array
DEPENDS ${SCHEMAGEN_SCRIPT} ${NULLABLE_ARRAY_SCHEMA}
COMMENT "Generating nullable_array.h")
add_custom_target(
schemagen_nullable_h DEPENDS ${NULLABLE_OBJECT_H} ${NULLABLE_STRING_H}
${NULLABLE_ARRAY_H})
add_executable(schemagen_nullable_root
${CMAKE_CURRENT_SOURCE_DIR}/test_nullable_root.cpp)
target_include_directories(schemagen_nullable_root
PRIVATE include ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(schemagen_nullable_root PRIVATE ${PROJECT_NAME})
target_compile_options(schemagen_nullable_root PRIVATE -Wno-switch-enum)
add_dependencies(schemagen_nullable_root schemagen_nullable_h)
add_test( add_test(
NAME schemagen_example NAME schemagen_example
COMMAND schemagen_example COMMAND schemagen_example
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
add_test(
NAME schemagen_big
COMMAND schemagen_big
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
add_test(
NAME schemagen_nullable_root
COMMAND schemagen_nullable_root
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+30 -5
View File
@@ -45,7 +45,7 @@ into the result, so it is non-movable.
| `$ref` to `$defs`/`definitions` | the referenced named struct | | `$ref` to `$defs`/`definitions` | the referenced named struct |
| recursive `$ref` | `std::unique_ptr<T>` (cycle broken) | | recursive `$ref` | `std::unique_ptr<T>` (cycle broken) |
| `additionalProperties: false` | unknown keys rejected | | `additionalProperties: false` | unknown keys rejected |
| `additionalProperties` absent / `true` | unknown keys' values skipped | | `additionalProperties` absent / `true` | not supported (rejected at generation) |
## Schema violations (rejected at parse time) ## Schema violations (rejected at parse time)
@@ -55,15 +55,15 @@ into the result, so it is non-movable.
- value not in a string `enum` - value not in a string `enum`
- a number not representable in the target type (e.g. `1.5` for an `integer`) - a number not representable in the target type (e.g. `1.5` for an `integer`)
- duplicate object keys - duplicate object keys
- unknown key under `additionalProperties: false` - unknown key in any object
## Not supported (rejected at generation time, no fallback) ## Not supported (rejected at generation time, no fallback)
`oneOf` / `anyOf` / `allOf` / `not` / `if`-`then`-`else`, `oneOf` / `anyOf` / `allOf` / `not` / `if`-`then`-`else`,
`patternProperties`, `additionalProperties` with a schema (typed map), `patternProperties`, `additionalProperties` with a schema (typed map),
`prefixItems` (tuples), `const`, `dependentSchemas`/`dependentRequired`, `additionalProperties: true`, `prefixItems` (tuples), `const`,
union `type` lists other than `["T", "null"]`, non-string enums, and remote `dependentSchemas`/`dependentRequired`, union `type` lists other than
(`$ref` to other documents). `["T", "null"]`, non-string enums, and remote (`$ref` to other documents).
## Notes ## Notes
@@ -73,3 +73,28 @@ union `type` lists other than `["T", "null"]`, non-string enums, and remote
cannot hold partial digits. cannot hold partial digits.
- `test_gen.cpp` generates from `example.schema.json` and exercises the parser - `test_gen.cpp` generates from `example.schema.json` and exercises the parser
byte-by-byte (covering chunked strings/numbers), plus the rejection cases. byte-by-byte (covering chunked strings/numbers), plus the rejection cases.
## Testing
The schemagen tests are registered with CTest and run as part of the default
`ctest` invocation from the build directory:
```sh
cmake -S . -B build
make -C build -j "$(nproc)"
cd build
ctest --output-on-failure
```
For local development you can also use the convenience script:
```sh
cd contrib/schemagen
./run_tests.sh
```
Both regenerate the example parser (`gen.h`) and a regression parser with 40
required properties (`big.h`), compile `test_gen.cpp` and `test_big.cpp`, and run
them. `test_big.cpp` specifically covers issue #3: it checks that a 40-property
object accepts all fields, rejects a missing field at index 32, and rejects
duplicate keys around the 32-bit boundary.
+168
View File
@@ -0,0 +1,168 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"p0": {
"type": "string"
},
"p1": {
"type": "string"
},
"p2": {
"type": "string"
},
"p3": {
"type": "string"
},
"p4": {
"type": "string"
},
"p5": {
"type": "string"
},
"p6": {
"type": "string"
},
"p7": {
"type": "string"
},
"p8": {
"type": "string"
},
"p9": {
"type": "string"
},
"p10": {
"type": "string"
},
"p11": {
"type": "string"
},
"p12": {
"type": "string"
},
"p13": {
"type": "string"
},
"p14": {
"type": "string"
},
"p15": {
"type": "string"
},
"p16": {
"type": "string"
},
"p17": {
"type": "string"
},
"p18": {
"type": "string"
},
"p19": {
"type": "string"
},
"p20": {
"type": "string"
},
"p21": {
"type": "string"
},
"p22": {
"type": "string"
},
"p23": {
"type": "string"
},
"p24": {
"type": "string"
},
"p25": {
"type": "string"
},
"p26": {
"type": "string"
},
"p27": {
"type": "string"
},
"p28": {
"type": "string"
},
"p29": {
"type": "string"
},
"p30": {
"type": "string"
},
"p31": {
"type": "string"
},
"p32": {
"type": "string"
},
"p33": {
"type": "string"
},
"p34": {
"type": "string"
},
"p35": {
"type": "string"
},
"p36": {
"type": "string"
},
"p37": {
"type": "string"
},
"p38": {
"type": "string"
},
"p39": {
"type": "string"
}
},
"required": [
"p0",
"p1",
"p2",
"p3",
"p4",
"p5",
"p6",
"p7",
"p8",
"p9",
"p10",
"p11",
"p12",
"p13",
"p14",
"p15",
"p16",
"p17",
"p18",
"p19",
"p20",
"p21",
"p22",
"p23",
"p24",
"p25",
"p26",
"p27",
"p28",
"p29",
"p30",
"p31",
"p32",
"p33",
"p34",
"p35",
"p36",
"p37",
"p38",
"p39"
]
}
@@ -0,0 +1,9 @@
{
"type": [
"array",
"null"
],
"items": {
"type": "integer"
}
}
@@ -0,0 +1,15 @@
{
"type": [
"object",
"null"
],
"additionalProperties": false,
"required": [
"x"
],
"properties": {
"x": {
"type": "string"
}
}
}
@@ -0,0 +1,6 @@
{
"type": [
"string",
"null"
]
}
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# Build and run the schemagen regression tests.
# Expects weaseljson to be built at ../../build (the default CMake build dir).
set -euo pipefail
cd "$(dirname "$0")"
python3 weaseljson_schemagen.py example.schema.json -o gen.h --namespace weasel_schema
python3 weaseljson_schemagen.py big.schema.json -o big.h --namespace big_schema
g++ -std=c++20 -I../../include -I. test_gen.cpp \
-L../../build -lweaseljson \
-Wl,-rpath,'$ORIGIN'/../../build \
-o test_gen
g++ -std=c++20 -I../../include -I. test_big.cpp \
-L../../build -lweaseljson \
-Wl,-rpath,'$ORIGIN'/../../build \
-o test_big
./test_gen
./test_big
+140
View File
@@ -0,0 +1,140 @@
// Regression test for issue #3: objects with more than 32 properties.
#include <cassert>
#include <cstdio>
#include <string>
#include "big.h"
using namespace big_schema;
static WeaselJsonStatus parseStrided(RootBuilder &b, std::string in) {
for (size_t i = 0; i < in.size(); ++i) {
char c = in[i];
WeaselJsonStatus s = b.feed(&c, 1);
if (s != WeaselJson_AGAIN)
return s;
}
return b.finish();
}
static int failures = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
++failures; \
} \
} while (0)
static void expectReject(const std::string &json, const char *what) {
RootBuilder b;
WeaselJsonStatus s = parseStrided(b, json);
if (s == WeaselJson_REJECT) {
printf("ok reject: %s\n", what);
} else {
printf("FAIL expected reject (%s) got status %d for: %s\n", what, s,
json.c_str());
++failures;
}
}
static std::string all40() {
std::string json = "{";
for (int i = 0; i < 40; ++i) {
if (i)
json += ",";
json += "\"p" + std::to_string(i) + "\":\"v" + std::to_string(i) + "\"";
}
json += "}";
return json;
}
int main() {
// All 40 required fields present -> accepted, values land in right slots.
{
RootBuilder b;
std::string json = all40();
WeaselJsonStatus s = parseStrided(b, json);
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
Root r = b.take();
CHECK(r.p0 == "v0");
CHECK(r.p31 == "v31");
CHECK(r.p32 == "v32");
CHECK(r.p39 == "v39");
printf("ok all 40 required fields accepted\n");
}
}
// Missing field p32 (index 32) must be detected, not aliased to bit 0.
{
std::string json = all40();
// Remove the p32 entry: find its substring and erase it.
std::string entry = "\"p32\":\"v32\"";
size_t pos = json.find(entry);
assert(pos != std::string::npos);
// Remove the trailing comma before it if present, or the leading comma
// after it.
if (pos > 0 && json[pos - 1] == ',') {
json.erase(pos - 1, entry.size() + 1);
} else if (pos + entry.size() < json.size() &&
json[pos + entry.size()] == ',') {
json.erase(pos, entry.size() + 1);
} else {
json.erase(pos, entry.size());
}
expectReject(json, "missing required field p32 (index 32)");
}
// Missing p0 still rejected.
{
std::string json = all40();
std::string entry = "\"p0\":\"v0\"";
size_t pos = json.find(entry);
assert(pos != std::string::npos);
if (json[pos + entry.size()] == ',') {
json.erase(pos, entry.size() + 1);
} else {
json.erase(pos, entry.size());
}
expectReject(json, "missing required field p0");
}
// Duplicate keys p0 and p32 must both be detected.
{
std::string json = all40();
// Insert p32 again right after the existing p32 entry.
std::string entry = "\"p32\":\"v32\"";
size_t pos = json.find(entry);
assert(pos != std::string::npos);
json.insert(pos + entry.size(), ",\"p32\":\"dup\"");
expectReject(json, "duplicate key p32 (index 32)");
}
// Duplicate keys p0 and p31 cover boundary bits.
{
std::string json = all40();
std::string entry = "\"p0\":\"v0\"";
size_t pos = json.find(entry);
assert(pos != std::string::npos);
json.insert(pos + entry.size(), ",\"p0\":\"dup\"");
expectReject(json, "duplicate key p0");
}
// Duplicate p31/p32 around the 32-bit boundary.
{
std::string json = all40();
std::string entry = "\"p31\":\"v31\"";
size_t pos = json.find(entry);
assert(pos != std::string::npos);
json.insert(pos + entry.size(), ",\"p31\":\"dup\"");
expectReject(json, "duplicate key p31");
}
if (failures == 0) {
printf("\nALL TESTS PASSED\n");
return 0;
}
printf("\n%d FAILURE(S)\n", failures);
return 1;
}
+4
View File
@@ -184,6 +184,10 @@ int main() {
expectReject(R"({"name":"x","age":1,"address":{"zip":5}})", expectReject(R"({"name":"x","age":1,"address":{"zip":5}})",
"missing required nested 'city'"); "missing required nested 'city'");
expectReject(R"([1,2,3])", "array where object expected (root)"); expectReject(R"([1,2,3])", "array where object expected (root)");
expectReject("null", "null where object expected (root)");
expectReject("true", "boolean where object expected (root)");
expectReject("123", "number where object expected (root)");
expectReject(R"("hi")", "string where object expected (root)");
expectReject(R"({"name":"x","age":1,)", "truncated / invalid json"); expectReject(R"({"name":"x","age":1,)", "truncated / invalid json");
if (failures == 0) { if (failures == 0) {
+198
View File
@@ -0,0 +1,198 @@
// Regression test for issue #13: nullable root types.
#include <cassert>
#include <cstdio>
#include <string>
#include "nullable_array.h"
#include "nullable_object.h"
#include "nullable_string.h"
static int failures = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
++failures; \
} \
} while (0)
static WeaselJsonStatus parseStrided(nullable_object::RootBuilder &b,
std::string in) {
for (size_t i = 0; i < in.size(); ++i) {
char c = in[i];
WeaselJsonStatus s = b.feed(&c, 1);
if (s != WeaselJson_AGAIN)
return s;
}
return b.finish();
}
static WeaselJsonStatus parseStrided(nullable_string::RootBuilder &b,
std::string in) {
for (size_t i = 0; i < in.size(); ++i) {
char c = in[i];
WeaselJsonStatus s = b.feed(&c, 1);
if (s != WeaselJson_AGAIN)
return s;
}
return b.finish();
}
static WeaselJsonStatus parseStrided(nullable_array::RootBuilder &b,
std::string in) {
for (size_t i = 0; i < in.size(); ++i) {
char c = in[i];
WeaselJsonStatus s = b.feed(&c, 1);
if (s != WeaselJson_AGAIN)
return s;
}
return b.finish();
}
static void expectReject(nullable_object::RootBuilder &b, std::string in,
const char *what) {
WeaselJsonStatus s = parseStrided(b, in);
if (s == WeaselJson_REJECT) {
printf("ok reject: %s\n", what);
} else {
printf("FAIL expected reject (%s) got status %d for: %s\n", what, s,
in.c_str());
++failures;
}
}
static void expectReject(nullable_array::RootBuilder &b, std::string in,
const char *what) {
WeaselJsonStatus s = parseStrided(b, in);
if (s == WeaselJson_REJECT) {
printf("ok reject: %s\n", what);
} else {
printf("FAIL expected reject (%s) got status %d for: %s\n", what, s,
in.c_str());
++failures;
}
}
int main() {
// ---- nullable root object: valid document ----
{
nullable_object::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, R"({"x":"hello"})");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_object::Root r = b.take();
CHECK(r.has_value());
CHECK(r->x == "hello");
printf("ok nullable root object accepts object\n");
}
}
// ---- nullable root object: null document ----
{
nullable_object::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, "null");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_object::Root r = b.take();
CHECK(!r.has_value());
printf("ok nullable root object accepts null\n");
}
}
// ---- nullable root object: schema checks still run ----
{
nullable_object::RootBuilder b;
expectReject(b, R"({"x":"hello","extra":1})",
"unknown key in strict nullable root object");
}
{
nullable_object::RootBuilder b;
expectReject(b, R"({})", "missing required field in nullable root object");
}
// ---- nullable root string: valid value ----
{
nullable_string::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, R"("hello")");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_string::Root r = b.take();
CHECK(r.has_value());
CHECK(*r == "hello");
printf("ok nullable root string accepts string\n");
}
}
// ---- nullable root string: null value ----
{
nullable_string::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, "null");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_string::Root r = b.take();
CHECK(!r.has_value());
printf("ok nullable root string accepts null\n");
}
}
// ---- nullable root array: valid value ----
{
nullable_array::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, "[1,2,3]");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_array::Root r = b.take();
CHECK(r.has_value());
CHECK(r->size() == 3);
CHECK((*r)[0] == 1 && (*r)[1] == 2 && (*r)[2] == 3);
printf("ok nullable root array accepts array\n");
}
}
// ---- nullable root array: null value ----
{
nullable_array::RootBuilder b;
WeaselJsonStatus s = parseStrided(b, "null");
CHECK(s == WeaselJson_OK);
if (s == WeaselJson_OK) {
nullable_array::Root r = b.take();
CHECK(!r.has_value());
printf("ok nullable root array accepts null\n");
}
}
// ---- nullable root object: scalar values rejected ----
{
nullable_object::RootBuilder b;
expectReject(b, "true", "boolean where nullable object expected (root)");
}
{
nullable_object::RootBuilder b;
expectReject(b, "123", "number where nullable object expected (root)");
}
{
nullable_object::RootBuilder b;
expectReject(b, R"("hi")", "string where nullable object expected (root)");
}
// ---- nullable root array: scalar values rejected ----
{
nullable_array::RootBuilder b;
expectReject(b, "true", "boolean where nullable array expected (root)");
}
{
nullable_array::RootBuilder b;
expectReject(b, "123", "number where nullable array expected (root)");
}
{
nullable_array::RootBuilder b;
expectReject(b, R"("hi")", "string where nullable array expected (root)");
}
if (failures == 0) {
printf("\nALL TESTS PASSED\n");
return 0;
}
printf("\n%d FAILURE(S)\n", failures);
return 1;
}
+420
View File
@@ -3,9 +3,12 @@
import json import json
import os import os
import re
import shutil
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
import textwrap
import unittest import unittest
SCRIPT = os.path.join(os.path.dirname(__file__), "weaseljson_schemagen.py") SCRIPT = os.path.join(os.path.dirname(__file__), "weaseljson_schemagen.py")
@@ -49,5 +52,422 @@ class SchemagenEnumTest(unittest.TestCase):
self.assertIn("enum class Role : int { admin, user, guest };", stdout) self.assertIn("enum class Role : int { admin, user, guest };", stdout)
class SchemagenKeywordTest(unittest.TestCase):
def run_schemagen(self, schema, args=None):
"""Run schemagen on a schema dict. Returns (returncode, stdout, stderr)."""
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fp:
json.dump(schema, fp)
schema_path = fp.name
try:
cmd = [sys.executable, SCRIPT, schema_path]
if args:
cmd.extend(args)
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
return result.returncode, result.stdout, result.stderr
finally:
os.unlink(schema_path)
def test_cpp20_keywords_are_sanitized(self):
"""C++20 keywords used as JSON property names must be suffixed."""
keywords = [
"concept",
"consteval",
"constinit",
"co_await",
"co_return",
"co_yield",
"requires",
"module",
"import",
]
schema = {"type": "object", "properties": {}}
for kw in keywords:
schema["properties"][kw] = {"type": "string"}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertEqual(rc, 0, msg=stderr)
for kw in keywords:
self.assertIn(f"std::optional<std::string> {kw}_;", stdout)
self.assertNotIn(f"std::optional<std::string> {kw};", stdout)
class SchemagenCollisionTest(unittest.TestCase):
"""Regression tests for issue #21: generated Root alias / Kind enum collisions."""
def setUp(self):
self.repo_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
self.include_dir = os.path.join(self.repo_root, "include")
self.compiler = shutil.which("c++")
def generate_and_compile(self, schema):
"""Run schemagen on schema and syntax-check the resulting header."""
with tempfile.TemporaryDirectory() as tmpdir:
schema_path = os.path.join(tmpdir, "schema.json")
with open(schema_path, "w") as fp:
json.dump(schema, fp)
header_path = os.path.join(tmpdir, "gen.h")
cmd = [
sys.executable,
SCRIPT,
schema_path,
"-o",
header_path,
"--namespace",
"test_schema",
]
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
self.assertEqual(result.returncode, 0, msg=result.stderr)
if self.compiler:
cpp_path = os.path.join(tmpdir, "test.cpp")
with open(cpp_path, "w") as fp:
fp.write(
'#include "gen.h"\n'
"int main() {\n"
" test_schema::RootBuilder b;\n"
" test_schema::Root r = b.take();\n"
" (void)r;\n"
"}\n"
)
comp = subprocess.run(
[
self.compiler,
"-std=c++20",
"-fsyntax-only",
"-I",
self.include_dir,
"-I",
tmpdir,
cpp_path,
],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(comp.returncode, 0, msg=comp.stderr)
with open(header_path) as fp:
return fp.read()
def test_root_alias_does_not_collide_with_user_type(self):
schema = {
"type": "array",
"items": {"$ref": "#/$defs/Root"},
"$defs": {"Root": {"enum": ["a", "b"]}},
}
out = self.generate_and_compile(schema)
self.assertIn("enum class Root1 : int { a, b };", out)
self.assertIn("using Root = std::vector<Root1>;", out)
self.assertNotIn("using Root = std::vector<Root>;", out)
def test_kind_enum_does_not_duplicate_arr0(self):
schema = {
"type": "object",
"properties": {
"arr": {"type": "array", "items": {"type": "string"}},
"obj": {"$ref": "#/$defs/Arr0"},
},
"$defs": {"Arr0": {"type": "object", "properties": {}}},
}
out = self.generate_and_compile(schema)
self.assertIn("struct Arr0", out)
m = re.search(r"enum class Kind : uint8_t \{([^}]+)\}", out)
self.assertIsNotNone(m)
enumerators = [e.strip() for e in m.group(1).split(",")]
self.assertIn("Arr0", enumerators)
self.assertIn("Arr1", enumerators)
self.assertEqual(len(enumerators), len(set(enumerators)))
def test_skip_user_type_is_allowed(self):
schema = {
"type": "array",
"items": {"$ref": "#/$defs/Skip"},
"$defs": {"Skip": {"type": "object", "properties": {}}},
}
out = self.generate_and_compile(schema)
self.assertIn("struct Skip", out)
self.assertNotIn("struct Skip1", out)
m = re.search(r"enum class Kind : uint8_t \{([^}]+)\}", out)
self.assertIsNotNone(m)
enumerators = [e.strip() for e in m.group(1).split(",")]
self.assertIn("Skip", enumerators)
self.assertIn("Arr0", enumerators)
self.assertEqual(len(enumerators), len(set(enumerators)))
def test_non_object_defs_reused_across_refs(self):
"""Regression test for issue #17: enum and array $defs must be reused."""
schema = {
"type": "object",
"properties": {
"role1": {"$ref": "#/$defs/Role"},
"role2": {"$ref": "#/$defs/Role"},
"roles1": {"$ref": "#/$defs/Roles"},
"roles2": {"$ref": "#/$defs/Roles"},
},
"$defs": {
"Role": {"enum": ["admin", "user"]},
"Roles": {"type": "array", "items": {"$ref": "#/$defs/Role"}},
},
}
out = self.generate_and_compile(schema)
# Exactly one Role enum is generated.
self.assertIn("enum class Role : int { admin, user };", out)
self.assertNotIn("enum class Role1 : int", out)
self.assertNotIn("enum class Role2 : int", out)
# The array of enum is represented by a single kind.
m = re.search(r"enum class Kind : uint8_t \{([^}]+)\}", out)
self.assertIsNotNone(m)
enumerators = [e.strip() for e in m.group(1).split(",")]
self.assertEqual(enumerators.count("Arr0"), 1)
# All four fields use the same C++ types.
self.assertIn("std::optional<Role> role1;", out)
self.assertIn("std::optional<Role> role2;", out)
self.assertIn("std::optional<std::vector<Role>> roles1;", out)
self.assertIn("std::optional<std::vector<Role>> roles2;", out)
class SchemagenAdditionalPropertiesTest(unittest.TestCase):
def run_schemagen(self, schema, args=None):
"""Run schemagen on a schema dict. Returns (returncode, stdout, stderr)."""
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fp:
json.dump(schema, fp)
schema_path = fp.name
try:
cmd = [sys.executable, SCRIPT, schema_path]
if args:
cmd.extend(args)
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
return result.returncode, result.stdout, result.stderr
finally:
os.unlink(schema_path)
def test_additional_properties_true_rejected(self):
schema = {
"type": "object",
"additionalProperties": True,
"properties": {"name": {"type": "string"}},
}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertNotEqual(rc, 0)
self.assertIn("additionalProperties: true is not supported", stderr)
def test_additional_properties_absent_defaults_to_strict(self):
schema = {
"type": "object",
"properties": {"name": {"type": "string"}},
}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertEqual(rc, 0, msg=stderr)
# The generated parser should reject unknown keys. Verify the key-matching
# helper returns -1 for an unknown key and cbKeyData rejects it.
self.assertIn("int matchKey(Kind k, std::string_view key) const {", stdout)
self.assertNotIn("bool isStrict(Kind k) const", stdout)
def test_additional_properties_false_accepted(self):
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"name": {"type": "string"}},
}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertEqual(rc, 0, msg=stderr)
class SchemagenIntegerBoundaryTest(unittest.TestCase):
"""Regression tests for issue #19: integer slot parsing near int64 boundaries."""
def setUp(self):
self.repo_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
self.include_dir = os.path.join(self.repo_root, "include")
self.lib_src = os.path.join(self.repo_root, "src", "lib.cpp")
self.compiler = shutil.which("c++")
def _compile_harness(self, tmpdir, schema, harness):
schema_path = os.path.join(tmpdir, "schema.json")
with open(schema_path, "w") as fp:
json.dump(schema, fp)
header_path = os.path.join(tmpdir, "gen.h")
result = subprocess.run(
[
sys.executable,
SCRIPT,
schema_path,
"-o",
header_path,
"--namespace",
"test_schema",
],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 0, msg=result.stderr)
lib_obj = os.path.join(tmpdir, "lib.o")
comp_lib = subprocess.run(
[
self.compiler,
"-std=c++20",
"-I",
self.include_dir,
"-I",
os.path.join(self.repo_root, "third_party", "include"),
"-I",
os.path.join(self.repo_root, "third_party", "valgrind"),
"-c",
self.lib_src,
"-o",
lib_obj,
],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(comp_lib.returncode, 0, msg=comp_lib.stderr)
cpp_path = os.path.join(tmpdir, "test.cpp")
with open(cpp_path, "w") as fp:
fp.write(harness)
exe_path = os.path.join(tmpdir, "test")
comp = subprocess.run(
[
self.compiler,
"-std=c++20",
"-I",
self.include_dir,
"-I",
tmpdir,
lib_obj,
cpp_path,
"-o",
exe_path,
],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(comp.returncode, 0, msg=comp.stderr)
run = subprocess.run([exe_path], capture_output=True, text=True, check=False)
self.assertEqual(run.returncode, 0, msg=run.stdout + run.stderr)
def _build_cases_array(self, name, entries):
lines = [
f" struct {name}Case {{ const char *s; WeaselJsonStatus expected; long long v; }};"
]
lines.append(f" {name}Case {name}_cases[] = {{")
for s, exp, v in entries:
val = f"{v}LL" if isinstance(v, int) else v
lines.append(f' {{ R"({s})", {exp}, {val} }},')
lines.append(" };")
return "\n".join(lines)
def test_integer_boundary_root(self):
if not self.compiler:
self.skipTest("C++ compiler not available")
cases = [
("9223372036854775807.0", "WeaselJson_OK", 9223372036854775807),
("9223372036854775807", "WeaselJson_OK", 9223372036854775807),
("9223372036854775806.0", "WeaselJson_OK", 9223372036854775806),
("9223372036854775808", "WeaselJson_REJECT", 0),
("-9223372036854775808", "WeaselJson_OK", "-9223372036854775807LL - 1"),
("-9223372036854775808.0", "WeaselJson_OK", "-9223372036854775807LL - 1"),
("-9223372036854775809", "WeaselJson_REJECT", 0),
("1e3", "WeaselJson_OK", 1000),
("2.0", "WeaselJson_OK", 2),
("0.001", "WeaselJson_REJECT", 0),
("1e-3", "WeaselJson_REJECT", 0),
("1000e-3", "WeaselJson_OK", 1),
("100.0e-2", "WeaselJson_OK", 1),
("123.0", "WeaselJson_OK", 123),
("9e18", "WeaselJson_OK", 9000000000000000000),
("10e18", "WeaselJson_REJECT", 0),
]
cases_src = self._build_cases_array("root", cases)
harness = textwrap.dedent(
f"""
#include "gen.h"
#include <cstdio>
#include <cstring>
{cases_src}
int main() {{
for (const auto \u0026c : root_cases) {{
test_schema::RootBuilder b;
char buf[512];
std::strncpy(buf, c.s, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\\0';
WeaselJsonStatus st = b.feed(buf, std::strlen(buf));
st = b.finish();
if (st != c.expected) {{
std::printf("root case %s expected %d got %d\\n", c.s, c.expected, st);
return 1;
}}
if (st == WeaselJson_OK \u0026\u0026 b.take() != c.v) {{
std::printf("root case %s value mismatch\\n", c.s);
return 2;
}}
}}
return 0;
}}
"""
)
with tempfile.TemporaryDirectory() as tmpdir:
self._compile_harness(tmpdir, {"type": "integer"}, harness)
def test_integer_boundary_object_field(self):
if not self.compiler:
self.skipTest("C++ compiler not available")
cases = [
('{"age":9223372036854775807.0}', "WeaselJson_OK", 9223372036854775807),
('{"age":-9223372036854775809}', "WeaselJson_REJECT", 0),
('{"age":1e3}', "WeaselJson_OK", 1000),
('{"age":2.0}', "WeaselJson_OK", 2),
('{"age":0.001}', "WeaselJson_REJECT", 0),
(
'{"age":-9223372036854775808.0}',
"WeaselJson_OK",
"-9223372036854775807LL - 1",
),
]
cases_src = self._build_cases_array("object", cases)
harness = textwrap.dedent(
f"""
#include "gen.h"
#include <cstdio>
#include <cstring>
{cases_src}
int main() {{
for (const auto \u0026c : object_cases) {{
test_schema::RootBuilder b;
char buf[512];
std::strncpy(buf, c.s, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\\0';
WeaselJsonStatus st = b.feed(buf, std::strlen(buf));
st = b.finish();
if (st != c.expected) {{
std::printf("object case %s expected %d got %d\\n", c.s, c.expected, st);
return 3;
}}
if (st == WeaselJson_OK \u0026\u0026 b.take().age != c.v) {{
std::printf("object case %s value mismatch\\n", c.s);
return 4;
}}
}}
return 0;
}}
"""
)
schema = {
"type": "object",
"properties": {"age": {"type": "integer"}},
"required": ["age"],
}
with tempfile.TemporaryDirectory() as tmpdir:
self._compile_harness(tmpdir, schema, harness)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+242 -94
View File
@@ -60,7 +60,6 @@ class ObjectType:
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
self.fields = [] # list[Field] self.fields = [] # list[Field]
self.strict = False # additionalProperties: false
class EnumType: class EnumType:
@@ -133,9 +132,15 @@ _CPP_KEYWORDS = {
"catch", "catch",
"char", "char",
"class", "class",
"concept",
"const", "const",
"consteval",
"constinit",
"constexpr", "constexpr",
"continue", "continue",
"co_await",
"co_return",
"co_yield",
"decltype", "decltype",
"default", "default",
"delete", "delete",
@@ -152,9 +157,11 @@ _CPP_KEYWORDS = {
"friend", "friend",
"goto", "goto",
"if", "if",
"import",
"inline", "inline",
"int", "int",
"long", "long",
"module",
"namespace", "namespace",
"new", "new",
"not", "not",
@@ -166,6 +173,7 @@ _CPP_KEYWORDS = {
"public", "public",
"register", "register",
"return", "return",
"requires",
"short", "short",
"signed", "signed",
"sizeof", "sizeof",
@@ -207,12 +215,26 @@ class Builder:
base = camel(hint) base = camel(hint)
name = base name = base
i = 1 i = 1
while name in self._used_names: while self._name_taken(name):
candidate = f"{base}{i}"
# A numeric suffix can itself land on a reserved generated name
# (e.g. hint "Arr0" -> "Arr01"). Use an underscore separator so
# we never loop through the reserved block.
if self._is_reserved(candidate):
candidate = f"{base}_{i}"
name = candidate
i += 1 i += 1
name = f"{base}{i}"
self._used_names.add(name) self._used_names.add(name)
return name return name
def _name_taken(self, name):
return name in self._used_names or self._is_reserved(name)
@staticmethod
def _is_reserved(name):
"""Names generated internally that must not collide with user types."""
return name in ("Root", "RootScalar")
def ref_name(self, ref): def ref_name(self, ref):
if not ref.startswith("#/"): if not ref.startswith("#/"):
raise GenError(f"only local $ref supported, got: {ref}") raise GenError(f"only local $ref supported, got: {ref}")
@@ -228,7 +250,6 @@ class Builder:
if defname not in self.defs: if defname not in self.defs:
raise GenError(f"$ref to unknown def: {defname}") raise GenError(f"$ref to unknown def: {defname}")
node = self.defs[defname] node = self.defs[defname]
# For objects we must register the name before recursing into fields.
return self.build_type(node, defname, defname=defname) return self.build_type(node, defname, defname=defname)
def build_type(self, node, hint, defname=None): def build_type(self, node, hint, defname=None):
@@ -257,6 +278,10 @@ class Builder:
if "$ref" in node: if "$ref" in node:
return self.build_def(self.ref_name(node["$ref"])) return self.build_def(self.ref_name(node["$ref"]))
# Non-object $defs entries must reuse the same type for every $ref.
if defname is not None and defname in self._building:
return self._building[defname]
# nullability via type lists: ["string", "null"] # nullability via type lists: ["string", "null"]
nullable = False nullable = False
typ = node.get("type") typ = node.get("type")
@@ -275,18 +300,29 @@ class Builder:
raise GenError("only non-empty string enums are supported") raise GenError("only non-empty string enums are supported")
name = defname and self.unique_name(defname) or self.unique_name(hint) name = defname and self.unique_name(defname) or self.unique_name(hint)
self.enums[name] = EnumType(name, list(vals)) self.enums[name] = EnumType(name, list(vals))
return (TEnum(name), nullable) result = (TEnum(name), nullable)
if defname is not None:
self._building[defname] = result
return result
if typ == "object" or (typ is None and "properties" in node): if typ == "object" or (typ is None and "properties" in node):
return (self._build_object(node, hint, defname), nullable) return (self._build_object(node, hint, defname, nullable), nullable)
if typ == "array": if typ == "array":
if "items" not in node or not isinstance(node["items"], dict): if "items" not in node or not isinstance(node["items"], dict):
raise GenError("arrays require a single 'items' schema") raise GenError("arrays require a single 'items' schema")
# Register the array before building its items so $ref cycles back
# to this definition resolve to the same TArr instance.
t = TArr(None, False)
result = (t, nullable)
if defname is not None:
self._building[defname] = result
elem, elem_nullable = self._unpack( elem, elem_nullable = self._unpack(
self.build_type(node["items"], hint + "Item") self.build_type(node["items"], hint + "Item")
) )
return (TArr(elem, elem_nullable), nullable) t.elem = elem
t.elem_nullable = elem_nullable
return result
scalar = { scalar = {
"string": "str", "string": "str",
@@ -295,7 +331,10 @@ class Builder:
"boolean": "bool", "boolean": "bool",
}.get(typ) }.get(typ)
if scalar: if scalar:
return (TScalar(scalar), nullable) result = (TScalar(scalar), nullable)
if defname is not None:
self._building[defname] = result
return result
if typ == "null": if typ == "null":
raise GenError("'null'-only types are not supported") raise GenError("'null'-only types are not supported")
@@ -309,19 +348,21 @@ class Builder:
return result return result
return (result, False) return (result, False)
def _build_object(self, node, hint, defname): def _build_object(self, node, hint, defname, nullable=False):
name = self.unique_name(defname or hint) name = self.unique_name(defname or hint)
obj = ObjectType(name) obj = ObjectType(name)
self.objects[name] = obj self.objects[name] = obj
# register for $ref cycles before building fields # register for $ref cycles before building fields
tobj = TObj(name)
if defname is not None: if defname is not None:
self._building[defname] = TObj(name) self._building[defname] = (tobj, nullable)
ap = node.get("additionalProperties", True) ap = node.get("additionalProperties", False)
if ap is True:
raise GenError("additionalProperties: true is not supported")
if isinstance(ap, dict): if isinstance(ap, dict):
raise GenError( raise GenError(
"additionalProperties with a schema (typed map) is not " "supported yet" "additionalProperties with a schema (typed map) is not " "supported yet"
) )
obj.strict = ap is False
required = set(node.get("required", [])) required = set(node.get("required", []))
props = node.get("properties", {}) props = node.get("properties", {})
seen_cpp = set() seen_cpp = set()
@@ -335,7 +376,7 @@ class Builder:
cpp = f"{base}{i}" cpp = f"{base}{i}"
seen_cpp.add(cpp) seen_cpp.add(cpp)
obj.fields.append(Field(key, cpp, ty, key in required, nullable)) obj.fields.append(Field(key, cpp, ty, key in required, nullable))
return TObj(name) return tobj
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -426,6 +467,7 @@ class Emitter:
self.kind_order = [] # all Kind enumerators in declaration order self.kind_order = [] # all Kind enumerators in declaration order
self.root_ty = None self.root_ty = None
self.root_nullable = False self.root_nullable = False
self._arr_counter = 0
# -- type strings ------------------------------------------------------- # -- type strings -------------------------------------------------------
def base_cpp(self, ty): def base_cpp(self, ty):
@@ -456,11 +498,19 @@ class Emitter:
def arr_kind(self, tarr): def arr_kind(self, tarr):
sig = self.base_cpp(tarr) sig = self.base_cpp(tarr)
if sig not in self.arr_kinds: if sig not in self.arr_kinds:
name = f"Arr{len(self.arr_kinds)}" name = self._fresh_arr_kind_name()
self.arr_kinds[sig] = name self.arr_kinds[sig] = name
self.arr_types.append((name, tarr)) self.arr_types.append((name, tarr))
self.b._used_names.add(name)
return self.arr_kinds[sig] return self.arr_kinds[sig]
def _fresh_arr_kind_name(self):
while True:
name = f"Arr{self._arr_counter}"
self._arr_counter += 1
if not self.b._name_taken(name):
return name
def cat(self, ty): def cat(self, ty):
if isinstance(ty, TScalar): if isinstance(ty, TScalar):
return {"str": "Str", "int": "Int", "dbl": "Dbl", "bool": "Bool"}[ty.kind] return {"str": "Str", "int": "Int", "dbl": "Dbl", "bool": "Bool"}[ty.kind]
@@ -484,6 +534,18 @@ class Emitter:
self.root_ty, self.root_nullable = Builder._unpack( self.root_ty, self.root_nullable = Builder._unpack(
self.b.build_type(self.b.root_schema, "Root") self.b.build_type(self.b.root_schema, "Root")
) )
# A nullable root object would otherwise produce
# using Root = std::optional<Root>;
# which conflicts with the struct named Root. Rename the inner struct.
if isinstance(self.root_ty, TObj) and self.root_nullable:
old_name = self.root_ty.name
new_name = self.b.unique_name("RootInner")
obj = self.b.objects.pop(old_name)
obj.name = new_name
self.b.objects[new_name] = obj
self.root_ty = TObj(new_name)
break_cycles(self.b.objects) break_cycles(self.b.objects)
# register all array kinds (walk every field + root) # register all array kinds (walk every field + root)
@@ -573,20 +635,17 @@ namespace {ns} {{"""
def _kind_enum(self): def _kind_enum(self):
kinds = list(self.b.objects.keys()) kinds = list(self.b.objects.keys())
kinds += [n for n, _ in self.arr_types] kinds += [n for n, _ in self.arr_types]
root_is_container = ( root_is_container = isinstance(self.root_ty, (TObj, TArr))
isinstance(self.root_ty, (TObj, TArr)) and not self.root_nullable
)
if not root_is_container: if not root_is_container:
kinds.append("RootScalar") kinds.append("RootScalar")
kinds.append("Skip")
self.kind_order = kinds self.kind_order = kinds
return " enum class Kind : uint8_t { " + ", ".join(kinds) + " };" return " enum class Kind : uint8_t { " + ", ".join(kinds) + " };"
def _root_info(self): def _root_info(self):
"""Return (root_cat, root_container_kind_or_None).""" """Return (root_cat, root_container_kind_or_None)."""
if isinstance(self.root_ty, TObj) and not self.root_nullable: if isinstance(self.root_ty, TObj):
return ("Obj", f"Kind::{self.root_ty.name}") return ("Obj", f"Kind::{self.root_ty.name}")
if isinstance(self.root_ty, TArr) and not self.root_nullable: if isinstance(self.root_ty, TArr):
return ("Arr", f"Kind::{self.arr_kind(self.root_ty)}") return ("Arr", f"Kind::{self.arr_kind(self.root_ty)}")
return (self.cat(self.root_ty), None) return (self.cat(self.root_ty), None)
@@ -656,6 +715,11 @@ namespace {ns} {{"""
lines.append(" }") lines.append(" }")
root_cat, root_kind = self._root_info() root_cat, root_kind = self._root_info()
if root_kind is None: if root_kind is None:
if self.root_nullable:
lines.append(
" case Kind::RootScalar: { if (!result_) result_.emplace(); return &*result_; }"
)
else:
lines.append(" case Kind::RootScalar: return &result_;") lines.append(" case Kind::RootScalar: return &result_;")
lines.append(" default: return nullptr;") lines.append(" default: return nullptr;")
lines.append(" }") lines.append(" }")
@@ -690,19 +754,35 @@ namespace {ns} {{"""
lines.append(" }") lines.append(" }")
return "\n".join(lines) return "\n".join(lines)
def _bitmask(self, obj, pred): def _fieldcount(self):
mask = 0 lines = [" int fieldCount(Kind k) const {", " switch (k) {"]
for i, fld in enumerate(obj.fields): for name, obj in self.b.objects.items():
if pred(fld): lines.append(f" case Kind::{name}: return {len(obj.fields)};")
mask |= 1 << i lines.append(" default: return 0;")
return mask lines.append(" }")
lines.append(" }")
return "\n".join(lines)
def _reqmask(self): def _reqmask(self):
lines = [" uint32_t requiredMask(Kind k) const {", " switch (k) {"] lines = [
" const std::vector<uint64_t> &requiredMask(Kind k) const {",
" switch (k) {",
]
for name, obj in self.b.objects.items(): for name, obj in self.b.objects.items():
m = self._bitmask(obj, lambda f: f.required) words = (len(obj.fields) + 63) // 64
lines.append(f" case Kind::{name}: return {hex(m)}u;") req = [0] * words
lines.append(" default: return 0u;") for i, fld in enumerate(obj.fields):
if fld.required:
req[i >> 6] |= 1 << (i & 63)
init = ", ".join(f"{hex(w)}u" for w in req) if words else ""
lines.append(f" case Kind::{name}: {{")
lines.append(f" static const std::vector<uint64_t> m = {{ {init} }};")
lines.append(" return m;")
lines.append(" }")
lines.append(" default: {")
lines.append(" static const std::vector<uint64_t> empty;")
lines.append(" return empty;")
lines.append(" }")
lines.append(" }") lines.append(" }")
lines.append(" }") lines.append(" }")
return "\n".join(lines) return "\n".join(lines)
@@ -746,17 +826,6 @@ namespace {ns} {{"""
lines.append(" }") lines.append(" }")
return "\n".join(lines) return "\n".join(lines)
def _is_strict(self):
strict = [n for n, o in self.b.objects.items() if o.strict]
if not strict:
return " bool isStrict(Kind) const { return false; }"
cases = " ".join(f"case Kind::{n}:" for n in strict)
return (
" bool isStrict(Kind k) const {\n"
f" switch (k) {{ {cases} return true; default: return false; }}\n"
" }"
)
def _enum_name_arrays(self): def _enum_name_arrays(self):
out = [] out = []
for e in self.b.enums.values(): for e in self.b.enums.values():
@@ -782,22 +851,30 @@ namespace {ns} {{"""
lines = [" if (error_) return;"] lines = [" if (error_) return;"]
if root_kind is not None and root_cat == event_cat: if root_kind is not None and root_cat == event_cat:
lines.append(" if (stack_.empty()) {") lines.append(" if (stack_.empty()) {")
lines.append(f" stack_.push_back(Frame{{{root_kind}, &result_}});") if self.root_nullable:
lines.append(" result_.emplace();")
dest = "&*result_"
else:
dest = "&result_"
lines.append(f" stack_.push_back(Frame{{{root_kind}, {dest}}});")
if event_cat == "Obj":
lines.append(" {")
lines.append(f" int n = fieldCount({root_kind});")
lines.append(" stack_.back().seen.assign((n + 63) / 64, 0);")
lines.append(" }")
lines.append(" return;") lines.append(" return;")
lines.append(" }") lines.append(" }")
else: else:
lines.append(" if (stack_.empty()) { reject(); return; }") lines.append(" if (stack_.empty()) { reject(); return; }")
lines.append(" Frame &f = stack_.back();") lines.append(" Frame &f = stack_.back();")
lines.append(
" if (f.kind == Kind::Skip) { stack_.push_back(Frame{Kind::Skip, nullptr}); return; }"
)
lines.append(" SlotInfo si = slotInfoG(f);") lines.append(" SlotInfo si = slotInfoG(f);")
lines.append(
" if (si.cat == Cat::Skip) { stack_.push_back(Frame{Kind::Skip, nullptr}); return; }"
)
lines.append(f" if (si.cat != Cat::{event_cat}) {{ reject(); return; }}") lines.append(f" if (si.cat != Cat::{event_cat}) {{ reject(); return; }}")
lines.append(" void *p = engage(f, true);") lines.append(" void *p = engage(f, true);")
lines.append(" stack_.push_back(Frame{si.child, p});") lines.append(" stack_.push_back(Frame{si.child, p});")
lines.append(" if (isObjectKind(si.child)) {")
lines.append(" int n = fieldCount(si.child);")
lines.append(" stack_.back().seen.assign((n + 63) / 64, 0);")
lines.append(" }")
return "\n".join(lines) return "\n".join(lines)
def _builder(self, order): def _builder(self, order):
@@ -805,6 +882,9 @@ namespace {ns} {{"""
root_cat, root_kind = self._root_info() root_cat, root_kind = self._root_info()
begin_obj = self._begin_container("Obj", root_kind) begin_obj = self._begin_container("Obj", root_kind)
begin_arr = self._begin_container("Arr", root_kind) begin_arr = self._begin_container("Arr", root_kind)
null_at_root = (
"done_ = true; return;" if self.root_nullable else "reject(); return;"
)
return f""" return f"""
class RootBuilder {{ class RootBuilder {{
@@ -838,7 +918,7 @@ public:
private: private:
{kind_enum} {kind_enum}
enum class Cat {{ Reject, Str, Int, Dbl, Bool, Enum, Obj, Arr, Skip }}; enum class Cat {{ Reject, Str, Int, Dbl, Bool, Enum, Obj, Arr }};
struct SlotInfo {{ struct SlotInfo {{
Cat cat; Cat cat;
Kind child{{}}; Kind child{{}};
@@ -849,11 +929,10 @@ private:
struct Frame {{ struct Frame {{
Kind kind; Kind kind;
void *dest; void *dest;
int field = -1; // object: selected field (-1 want key, -2 skip) int field = -1; // object: selected field (-1 means waiting for key)
uint32_t seen = 0; // bitmask of populated fields std::vector<uint64_t> seen; // populated field bitset (object frames)
}}; }};
static constexpr int kWantKey = -1; static constexpr int kWantKey = -1;
static constexpr int kSkip = -2;
{self._enum_name_arrays()} {self._enum_name_arrays()}
@@ -868,10 +947,93 @@ private:
void reject() {{ error_ = true; }} void reject() {{ error_ = true; }}
// Wrap the generated slotInfo() with the generic key/skip states. // Parse a JSON number text as int64_t. Accepts optional decimal point and
// exponent only when the mathematical value is an integer that fits in a
// signed 64-bit range.
static bool parseJsonInt64(const char *b, const char *e, int64_t &out) {{
const char *p = b;
bool neg = false;
if (p < e) {{
if (*p == '-') {{ neg = true; ++p; }}
else if (*p == '+') return false;
}}
const char *intStart = p;
while (p < e && *p >= '0' && *p <= '9') ++p;
const char *intEnd = p;
int fracDigits = 0;
const char *fracStart = p;
if (p < e && *p == '.') {{
++p;
fracStart = p;
while (p < e && *p >= '0' && *p <= '9') {{ ++p; ++fracDigits; }}
if (fracStart == p) return false;
}}
int64_t exp = 0;
bool expNeg = false;
if (p < e && (*p == 'e' || *p == 'E')) {{
++p;
if (p < e && (*p == '-' || *p == '+')) {{ expNeg = (*p == '-'); ++p; }}
if (p == e || *p < '0' || *p > '9') return false;
while (p < e && *p >= '0' && *p <= '9') {{
int digit = *p - '0';
if (exp <= (INT64_MAX - digit) / 10) exp = exp * 10 + digit;
else exp = INT64_MAX;
++p;
}}
if (expNeg) exp = -exp;
}}
if (p != e) return false;
if (intStart == intEnd) return false;
std::string digits;
digits.reserve((intEnd - intStart) + fracDigits);
for (const char *q = intStart; q < intEnd; ++q) digits.push_back(*q);
for (int i = 0; i < fracDigits; ++i) digits.push_back(fracStart[i]);
int64_t trim = 0;
while (!digits.empty() && digits.back() == '0') {{
digits.pop_back();
++trim;
}}
int64_t finalExp = exp - fracDigits + trim;
if (finalExp < 0) return false;
while (!digits.empty() && digits.front() == '0') digits.erase(digits.begin());
if (digits.empty()) {{ out = 0; return true; }}
constexpr uint64_t kMaxNeg = 9223372036854775808ULL;
constexpr uint64_t kMaxPos = 9223372036854775807ULL;
const uint64_t limit = neg ? kMaxNeg : kMaxPos;
if (finalExp > 19) return false;
int64_t maxSig = 19 - finalExp;
uint64_t mag = 0;
int64_t sigDigits = 0;
for (char ch : digits) {{
uint64_t d = static_cast<uint64_t>(ch - '0');
if (sigDigits >= maxSig) return false;
if (mag > (limit - d) / 10) return false;
mag = mag * 10 + d;
++sigDigits;
}}
for (int64_t i = 0; i < finalExp; ++i) {{
if (mag > limit / 10) return false;
mag *= 10;
}}
if (mag > limit) return false;
if (neg) {{
if (mag == kMaxNeg) {{
out = INT64_MIN;
}} else {{
out = -static_cast<int64_t>(mag);
}}
}} else {{
out = static_cast<int64_t>(mag);
}}
return true;
}}
// Wrap the generated slotInfo() with the generic key state.
SlotInfo slotInfoG(const Frame &f) {{ SlotInfo slotInfoG(const Frame &f) {{
if (isObjectKind(f.kind)) {{ if (isObjectKind(f.kind)) {{
if (f.field == kSkip) return SlotInfo{{Cat::Skip}};
if (f.field < 0) return SlotInfo{{Cat::Reject}}; if (f.field < 0) return SlotInfo{{Cat::Reject}};
}} }}
return slotInfo(f); return slotInfo(f);
@@ -885,7 +1047,7 @@ private:
scratch_.clear(); scratch_.clear();
{(" if (p.kind == Kind::RootScalar) { stack_.pop_back(); done_ = true; return; }" if root_kind is None else "")} {(" if (p.kind == Kind::RootScalar) { stack_.pop_back(); done_ = true; return; }" if root_kind is None else "")}
if (isObjectKind(p.kind)) {{ if (isObjectKind(p.kind)) {{
if (p.field >= 0) p.seen |= (1u << p.field); if (p.field >= 0) p.seen[p.field >> 6] |= (1ull << (p.field & 63));
p.field = kWantKey; p.field = kWantKey;
}} }}
}} }}
@@ -895,16 +1057,14 @@ private:
}} }}
void cbEndObject() {{ void cbEndObject() {{
if (error_) return; if (error_) return;
Frame f = stack_.back(); Frame &f = stack_.back();
if (f.kind == Kind::Skip) {{ if (isObjectKind(f.kind)) {{
stack_.pop_back(); const auto &req = requiredMask(f.kind);
if (stack_.empty() || stack_.back().kind != Kind::Skip) valueComplete(); bool missing = false;
return; for (size_t i = 0; i < req.size(); ++i) {{
if ((f.seen[i] & req[i]) != req[i]) {{ missing = true; break; }}
}} }}
if (isObjectKind(f.kind) && if (missing) {{ reject(); return; }}
(f.seen & requiredMask(f.kind)) != requiredMask(f.kind)) {{
reject();
return;
}} }}
stack_.pop_back(); stack_.pop_back();
valueComplete(); valueComplete();
@@ -915,11 +1075,6 @@ private:
void cbEndArray() {{ void cbEndArray() {{
if (error_) return; if (error_) return;
Frame f = stack_.back(); Frame f = stack_.back();
if (f.kind == Kind::Skip) {{
stack_.pop_back();
if (stack_.empty() || stack_.back().kind != Kind::Skip) valueComplete();
return;
}}
stack_.pop_back(); stack_.pop_back();
valueComplete(); valueComplete();
}} }}
@@ -931,22 +1086,21 @@ private:
scratch_.append(buf, len); scratch_.append(buf, len);
if (!done) return; if (!done) return;
int idx = matchKey(f.kind, scratch_); int idx = matchKey(f.kind, scratch_);
scratch_.clear();
if (idx < 0) {{ if (idx < 0) {{
if (isStrict(f.kind)) {{ reject(); return; }} reject(); return; // unknown key
f.field = kSkip; }}
return; scratch_.clear();
if (f.seen[idx >> 6] & (1ull << (idx & 63))) {{
reject(); return; // duplicate key
}} }}
if (f.seen & (1u << idx)) {{ reject(); return; }} // duplicate key
f.field = idx; f.field = idx;
}} }}
void cbStringData(const char *buf, int len, int done) {{ void cbStringData(const char *buf, int len, int done) {{
if (error_) return; if (error_) return;
if (stack_.empty()) {{ reject(); return; }}
Frame &f = stack_.back(); Frame &f = stack_.back();
if (f.kind == Kind::Skip) return;
SlotInfo si = slotInfoG(f); SlotInfo si = slotInfoG(f);
if (si.cat == Cat::Skip) {{ if (done) valueComplete(); return; }}
if (si.cat == Cat::Str) {{ if (si.cat == Cat::Str) {{
auto *s = (std::string *)engage(f, !started_); auto *s = (std::string *)engage(f, !started_);
s->append(buf, len); s->append(buf, len);
@@ -971,10 +1125,9 @@ private:
void cbNumberData(const char *buf, int len, int done) {{ void cbNumberData(const char *buf, int len, int done) {{
if (error_) return; if (error_) return;
if (stack_.empty()) {{ reject(); return; }}
Frame &f = stack_.back(); Frame &f = stack_.back();
if (f.kind == Kind::Skip) return;
SlotInfo si = slotInfoG(f); SlotInfo si = slotInfoG(f);
if (si.cat == Cat::Skip) {{ if (done) valueComplete(); return; }}
if (si.cat != Cat::Int && si.cat != Cat::Dbl) {{ reject(); return; }} if (si.cat != Cat::Int && si.cat != Cat::Dbl) {{ reject(); return; }}
scratch_.append(buf, len); scratch_.append(buf, len);
started_ = true; started_ = true;
@@ -989,16 +1142,11 @@ private:
*(int64_t *)p = v; // plain integer literal: parsed exactly *(int64_t *)p = v; // plain integer literal: parsed exactly
}} else {{ }} else {{
// JSON Schema "integer" accepts any number with no fractional part, // JSON Schema "integer" accepts any number with no fractional part,
// including exponent/decimal forms like 1e3 or 2.0. Parse those as a // including exponent/decimal forms like 1e3 or 2.0. Parse those
// double and require an integral value within int64 range. // exactly as int64 when the value is integral and in range.
double d = 0; int64_t v2 = 0;
auto rd = std::from_chars(b, e, d); if (!parseJsonInt64(b, e, v2)) {{ reject(); return; }}
if (rd.ec != std::errc() || rd.ptr != e || d != std::trunc(d) || *(int64_t *)p = v2;
d < -9223372036854775808.0 || d >= 9223372036854775808.0) {{
reject();
return;
}}
*(int64_t *)p = (int64_t)d;
}} }}
}} else {{ }} else {{
double v = 0; double v = 0;
@@ -1011,10 +1159,9 @@ private:
void cbBool(bool value) {{ void cbBool(bool value) {{
if (error_) return; if (error_) return;
if (stack_.empty()) {{ reject(); return; }}
Frame &f = stack_.back(); Frame &f = stack_.back();
if (f.kind == Kind::Skip) return;
SlotInfo si = slotInfoG(f); SlotInfo si = slotInfoG(f);
if (si.cat == Cat::Skip) {{ valueComplete(); return; }}
if (si.cat != Cat::Bool) {{ reject(); return; }} if (si.cat != Cat::Bool) {{ reject(); return; }}
*(bool *)engage(f, true) = value; *(bool *)engage(f, true) = value;
valueComplete(); valueComplete();
@@ -1026,10 +1173,11 @@ private:
void cbNull() {{ void cbNull() {{
if (error_) return; if (error_) return;
if (stack_.empty()) {{
{null_at_root}
}}
Frame &f = stack_.back(); Frame &f = stack_.back();
if (f.kind == Kind::Skip) return;
SlotInfo si = slotInfoG(f); SlotInfo si = slotInfoG(f);
if (si.cat == Cat::Skip) {{ valueComplete(); return; }}
if (!si.nullable) {{ reject(); return; }} if (!si.nullable) {{ reject(); return; }}
if (isArrayKind(f.kind)) appendNull(f); // keep null array elements if (isArrayKind(f.kind)) appendNull(f); // keep null array elements
valueComplete(); // leave optional empty / pointer null valueComplete(); // leave optional empty / pointer null
@@ -1062,11 +1210,11 @@ private:
{self._matchkey()} {self._matchkey()}
{self._fieldcount()}
{self._reqmask()} {self._reqmask()}
{self._is_object_kind()} {self._is_object_kind()}
{self._is_strict()}
}}; }};
""" """
@@ -0,0 +1 @@
"d\ud82d\udd9c8\ud8ed
@@ -0,0 +1,43 @@
[4,[5,[5,[4,[6,[5,[5,55,5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[[[[5,5,[5,[5,[[null,[461,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[␍[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-41,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-.41.8,[8,[7,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[5,[5,[[null,5,[1.0e+9, ""[[{},{}:0 -1.0e028 },[[false, [[false, [[[{},8,
@@ -0,0 +1 @@
[[[{ "min":-6.8, "in":-6.8, "in": -6, "Umin": -6.8, "m":-6.4, "max": 1, "mij": -6.8," ": -6.8, "UUaxax": 1, "mij": -6.8," ": -6.8, "UUax":-6.8, "Tin":-6.8, "mUUUax":-6.8, "maxn":-6.8, "in": -6, "Umin": -6.8, "m":-6.4, "max": 1, "mij": -6.8," ": -6.8, "UUax":-6.8, "Tin":-6.8, "mUUUax":-6.8, "max": -6.8, "min":-6.8, "mUax":-6.8, "man": -6, "mUmin": -6.8, "mi": -6, "Umx":-6.8, "Tin":-6.8, "mUUUax":-6.8, "maxn":-6.8, "in": -6, "Umin": -6.8, "m":-6.4, "max": 1, "mij": -6.8," ": -6.8, "UUax":-6.8, "Tin":-6.8, "mUUUax":-6.8, "max": -6.8, "min":-6.8, "mUax":-6.8, "man": -6, "mUmin": -6.8, "mi": -6, "Umin": -6.8, "m":-6.4, "max": 1, "mij": -6.8," ": -6.8, "UUaxin": -6, "Umin": -6.8, "m":-6.4, 1,334en":-5e556.8, "n": -6
@@ -0,0 +1 @@
["\uDA9D\uDDDDux\uDA9D\uDDDdDux\uDA9D\uDDDduxux\uDA9D\uDDDdDDDux\uDA9D\uDDDeu|\uDA9D\uDDDDDDux\uDA9D\uDDDdDDADDDux\uDA9D\uDDDduxux\uDA9D\uDDDdD\uDA9D\uDDDduuDDA9D\uDA9D\uDDDDD-ux\uDA9D\uDDDdDDAuDDDDDDDux\uDA9D\uDDDduxux\uDA9D\uDDDdDux\uDA0D\uDDDdu|DDux\uDA9D\uDDDddDDDDDux\uDA9D\uDDDduxux\uDA9D\uDDDdDux\uDA9D\uDDDdu|DDux\uDA9D\uDDDddDDux\uDA9D\uDDDdu|\uDA4D\uDDA9D\u³D\uDA9D\uDDDDDDDDD\uDA9D\uDDDduxu\uDA9D\uDDDd:DEux\uDA9D\uDDDdDux\uDA9DDDDux\uDA9D\uDDD­du|\uDA8D\uDDDDDDDDux\uDA9D\uDDDddDDDx\uDA9D\uDDDd\uDA4D\uDDA0DDDux\uDA9D\uDDDdDDA0
@@ -0,0 +1,27 @@
[15,4,5,[[5,5,[4,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,4,[5,[5,[4,[6,[5,[54,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,-418,[8,[[[
[[-41.8,␍␍[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,28,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[5,2,[5,[5,[[null,[413,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[ "\
@@ -0,0 +1,56 @@
[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},3,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[7,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, 8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[8,[8,[8,
[[-41.8,[-41.8,{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[7,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[ [-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-20,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-417.777E-8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ { "$i": 9, "max": 8, "x": 2.0e+28,[[false, [[false, "8[[-418,[8,[4,[5,[5,[4,[6 8, "man": -3.8, "max": 8, "max": 8, "man": ,[ššššššššššš5,5, 5,[5,max
@@ -0,0 +1,54 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[5,[[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,5,4,[5,[5,[4,[6,[5,[5,55,5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[4,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[{"x":[{"id": "xx"}],"x":[{"d":"x"}],""J:"xx"}],,[5,[[5,[4,5[[,5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[462,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,4,[5,[5,[5,ž,[5,[5,[[5,[5,[415,[4,[5,[5,[[5,(((((((((((((((((((((((((((((((((((((((((((((((((((((([[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[4,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[55,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[4,[5,[[null,5,[5,[5,[[5,[5,[415,[4,
[[[[[false, [[f[
4
"p{ },,[5[
[
[{ },[[[6[
]ue,true,[ true,[[-41,[-,[5[[[[[[true,t[,[5rue,[ true,true, true,[[[[[[[[[[ true,true,[[ true,true,[41,,[ true,pr]]]
@@ -0,0 +1,54 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,4,[5,[5,[4,[6,[5,[5,55,5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[4,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,4,[5,[5,[4,[6,[5,[5,55,5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[4,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[{"x":[{"id": {";;" : -8,"8[-5,[-5,5,[-5,5,[-" : 8,"" : -8,"" : -8,"8" : -8,"8;" : -8,"8" : 8,"" : -8,"8[-5,[-5,5,[-5,5,[-" : 8,"" : -8,"" : -8,"8" : -8,"8;" : -8,"8" : 8,"" : -8,"8" :-8,"8" : 8,"" : -8,"" : "" :",5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,4,[5,[5,[5,ž,[5,[5,[[5,[5,[415,[4,[5,[5,[[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,4,[5,[5,[4,[6,[5,[5,55,5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[4,[5,[[null,5,[5,[5,[[5,[5,[415,[4,
[[[[[false, [[f[
4
"p{ },,[5[
[
[{ },[[[6[
][-41,[-,[5[,[5,[41,
@@ -0,0 +1 @@
"\uDBFF\zFEFF
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,55 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[5,[[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[{"x":[{"id": "xx"}],"x":[{"d":"x"}],"":"xx"}],,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,4,[5,[5,[5,ž,[5,[5,[[5,[5,[415,[4,[5,[5,[[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5[[[[[[[[[[[
[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,4,[5,[5,[4,[6,[5,[5,55,5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[4,[5,[[null,5,[5,[5,[[5,[5,[415,[4,
[[[[[false, [[f[
4
"p{ },,[5[
[
[{ },[[[6[
][-41,[-,[5[,[5,[41,
@@ -0,0 +1 @@
{"title":"\u041e\ud41d41d\ud82d\udd29\4d9cu0430 \u;a 4 \u03
@@ -0,0 +1,71 @@
[[[{},{}, [[[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ 8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-417,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[0.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[["\uDA9D\uDDDDux\uDA9D^uDDDdux\uDA9D\uDDA9D\uDA9D\DDDD[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[[
[[-418,[8,[[[
[[-41,[[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1Dux9D\uDA9D\uDDDDDD*x¤Ý¤Ò-ËŠ\u18,DA9D\uDDdDDA0
@@ -0,0 +1,160 @@
[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222241.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[7,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[0,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[7E-5,7E-5,3E8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[0,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[7E-5,7E-5,3E8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222241.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[7,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[0,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[7E-5,7E-5,3E8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[0,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[7E-5,7E-5,3E8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{
}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-20.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[
-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[ [[[ [[-41.8,[3,[8,[[,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222241.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[7,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{
}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-20.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[
-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418[[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false,"\@d83d\u34e55,335223334e55,33e5,334e55,3e5,39990.099999de55,e3339\ud
@@ -0,0 +1,54 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[5,[[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[95,[5,[4,5,[5,5,[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[[[[[[[[[[[[[[[[[[[[[[[[[[{"a0.000000+0000000000000000000000000sd":"s1f", "dfg0000000000000d":"s1f", "d0g00000000000000sd":"s1f", "dff[[[[[[[[[[[[[[[[[0sd":"s1f", "dff[[[[[000000000000000sd":"G1f", "dfg00000000040000sd":"s1f", "d]]]null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,4,[5,[5,[4,[6,[5,[5,55,5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[4,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5]]]]]]]]]]]]]]]]]]]]]]]](]]]]]]]]]]]]]]]]]]]]0]]]]]]000sd":"s1f", "dfg00000000000000sd":,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5"sÌ™,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5415,[4,[5,[[5,[4,[5,[5,4,[5,[5,[4,[6,[5,[5,55,5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[ÝÓ5,[3,[5,5,[5,[5,[[n "0ÿ"fg"]]ull,[415,[[5,[5,[415,4,5,[[5,5,[4,[5,[[null,5,[5,[5,[[5,[5,[415,[4,
[[[[[false, [[f[
4
"p{ },,[5[
[
[{ },[[[6[
][-41,[-,[5[,[5,[41",:]
@@ -0,0 +1,62 @@
{";N" :[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[ 8, [ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[faf
@@ -0,0 +1 @@
"\uDA8D\tD]{DDD
@@ -0,0 +1,42 @@
[4,[5,[5,[4,[6,[5,[5,55,5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,[[5,[5,[415,[4,[5,[5,[3,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[413,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[[5,[4,[5,[5,[5,5,[5,[5,[[5,[5,[415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,5,[5,[5,[[null,5,[5,[5,5,[5,[5,[[5,[5,[3415,[4,[5,[5,[4,[5,[5,[5,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[5,5,[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[␍[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-41,5,[[5,[4,[5,[[5,5,[5,[5,[[null,[415,[4,[5,[5,[4,5,[[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[4,[5,[5,[4,5,[[[[5,5,[5,[5,[[null,[415,[[5,[5,[415,4,5,[[5,[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [{ "min":1.0e+9, ""[[{},{}:0 -1.0e020 },[[false, [[false, [[[{},8,
File diff suppressed because one or more lines are too long
@@ -0,0 +1,71 @@
[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ 8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{},[[[{},8,[[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,␍9777E-1,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[83,6663,66666666,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[[[[[[-41.8,[83,666377777777777, 777728273720, 1, "1", 77777777, 1, "1", 77, 677777777777777777777777777777777777, 1, "1", 7738888888888, 1, "1", 67731, "1-", 7777777777777, 1,7777777777777777777, 1, "1", 7777777, "1", 77, 677777777777777777777777777777777777, 1, 77777777777, 1, "1", 7738888888888, 1, "1",7777777, 7777777777, 777728273720, 1, "1", 777777777777777, ,66666666,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[[
[[-418,[8,[[[
[[-41,[[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
["[-Ò418,
@@ -0,0 +1 @@
{"":"_________..............................._____________@@@@ffa\udafa\udffa2fa[-401.
@@ -0,0 +1,101 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[[[
{"":
5
,[8,
[[-41.8,[-41.8,[8,[8,
[[[[[false, [[f[
4
"p{ },[
[
[{ },[[[6[
][-41,[-[
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
"de39\ud83dLudœ8
@@ -0,0 +1,62 @@
[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[7,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[-418,[8,[[[
[[-41.8,[8,[8,␍9777E-1,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},2,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[8,[8,[[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[7,[[[[41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,[[[[[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-20.8,[8,[8,[[1.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41,{},[[false, [[false, [[[{},8,[-41.8,[8,[8,[[[[[-41.8,[8,[8,[[[[ [[[ [[-41.8,[3,[8,[[[[ [[[
[[-418,[8,[[[
[[-41.8,[8,[8,
[[-41.8,[-41.8,[8,[8,1.8,[-41.8,[8,[
[[-418,[8,[[[[[[-41.8,[8,[8,[[[[ [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, [[[{},8,[-41.8,[8,[8.8,[8,[[[{},{}, [[[{},{},[[false, [[false, [[[{},8, [[[
[[-41,{},[[[[{},{}, [[[{},{},[[false, [[false, "ffa2ffa\udafa\udffad@faa\udff41.8,[8,[8,[[[
[8{},[-[
+2 -1
View File
@@ -64,7 +64,8 @@ void WeaselJsonParser_destroy(WeaselJsonParser *parser);
/** Incrementally parse `len` more bytes starting at `buf`. `buf` may be /** Incrementally parse `len` more bytes starting at `buf`. `buf` may be
* modified. Call with `len` 0 to indicate end of data. `buf` may be null if * modified. Call with `len` 0 to indicate end of data. `buf` may be null if
* `len` is 0 */ * `len` is 0. `len` must not be negative; a negative length is treated as a
* rejected input. */
WeaselJsonStatus WeaselJsonParser_parse(WeaselJsonParser *parser, char *buf, WeaselJsonStatus WeaselJsonParser_parse(WeaselJsonParser *parser, char *buf,
int len); int len);
+36 -10
View File
@@ -83,26 +83,28 @@ struct Parser3 {
[[nodiscard]] WeaselJsonStatus parse(char *buf, int len); [[nodiscard]] WeaselJsonStatus parse(char *buf, int len);
void flushNumber(bool done, char *buf) { void flushNumber(bool done, char *buf) {
int len = buf - dataBegin; int len = (intptr_t)buf - (intptr_t)dataBegin;
assert(len >= 0); assert(len >= 0);
if (done || len > 0) { if (done || len > 0) {
callbacks->on_number_data(userdata, dataBegin, len, done); callbacks->on_number_data(userdata, dataBegin ? dataBegin : "", len,
done);
} }
} }
void flushString(bool done, char *buf) { void flushString(bool done, char *buf) {
int len; int len;
if (!(flags & WeaselJsonRaw)) { if (!(flags & WeaselJsonRaw)) {
len = writeBuf - dataBegin; len = (intptr_t)writeBuf - (intptr_t)dataBegin;
} else { } else {
len = buf - dataBegin; len = (intptr_t)buf - (intptr_t)dataBegin;
} }
assert(len >= 0); assert(len >= 0);
if (done || len > 0) { if (done || len > 0) {
const char *data = dataBegin ? dataBegin : "";
if (inKey) { if (inKey) {
callbacks->on_key_data(userdata, dataBegin, len, done); callbacks->on_key_data(userdata, data, len, done);
} else { } else {
callbacks->on_string_data(userdata, dataBegin, len, done); callbacks->on_string_data(userdata, data, len, done);
} }
} }
} }
@@ -139,6 +141,7 @@ struct Parser3 {
stackPtr = stack(); stackPtr = stack();
std::ignore = push({N_VALUE, N_WHITESPACE, T_EOF}); std::ignore = push({N_VALUE, N_WHITESPACE, T_EOF});
inKey = false; inKey = false;
rejected = false;
utf8Codepoint = 0; utf8Codepoint = 0;
utf16Surrogate = 0; utf16Surrogate = 0;
minCodepoint = 0; minCodepoint = 0;
@@ -161,6 +164,7 @@ struct Parser3 {
NumDfa numDfa; NumDfa numDfa;
Utf8Dfa strDfa; Utf8Dfa strDfa;
bool inKey = false; bool inKey = false;
bool rejected = false;
#ifndef HAS_MUSTTAIL #ifndef HAS_MUSTTAIL
char *stashBufForTrampoline; char *stashBufForTrampoline;
@@ -623,7 +627,7 @@ inline PRESERVE_NONE ContinuationStatus n_string2(Parser3 *self, char *buf,
return WeaselJson_REJECT; return WeaselJson_REJECT;
} }
buf += 4; buf += 4;
if (0xd800 <= codepoint && codepoint <= 0xdfff) { if (0xd800 <= codepoint && codepoint <= 0xdbff) {
// utf-16 surrogate // utf-16 surrogate
int32_t codepoint2 = read4_hex(buf + 2); int32_t codepoint2 = read4_hex(buf + 2);
if (!(buf[0] == '\\' && buf[1] == 'u' && 0xdc00 <= codepoint2 && if (!(buf[0] == '\\' && buf[1] == 'u' && 0xdc00 <= codepoint2 &&
@@ -648,6 +652,8 @@ inline PRESERVE_NONE ContinuationStatus n_string2(Parser3 *self, char *buf,
self->writeBuf[0] = (0b00000111 & codepoint) | 0b11110000; self->writeBuf[0] = (0b00000111 & codepoint) | 0b11110000;
self->writeBuf += 4; self->writeBuf += 4;
} }
} else if (0xdc00 <= codepoint && codepoint <= 0xdfff) {
return WeaselJson_REJECT;
} else { } else {
if (!(self->flags & WeaselJsonRaw)) { if (!(self->flags & WeaselJsonRaw)) {
if (codepoint < 0x80) { if (codepoint < 0x80) {
@@ -791,7 +797,7 @@ inline PRESERVE_NONE ContinuationStatus t_hex2(Parser3 *self, char *buf,
} }
} else { } else {
assert(self->utf8Codepoint < 0x10000); assert(self->utf8Codepoint < 0x10000);
if (0xd800 <= self->utf8Codepoint && self->utf8Codepoint <= 0xdfff) { if (0xd800 <= self->utf8Codepoint && self->utf8Codepoint <= 0xdbff) {
// utf-16 surrogate // utf-16 surrogate
self->utf16Surrogate = self->utf8Codepoint; self->utf16Surrogate = self->utf8Codepoint;
self->utf8Codepoint = 0; self->utf8Codepoint = 0;
@@ -806,6 +812,10 @@ inline PRESERVE_NONE ContinuationStatus t_hex2(Parser3 *self, char *buf,
} }
MUSTTAIL return Parser3::keepGoing(self, buf, bufEnd); MUSTTAIL return Parser3::keepGoing(self, buf, bufEnd);
} }
if (0xdc00 <= self->utf8Codepoint && self->utf8Codepoint <= 0xdfff)
[[unlikely]] {
return WeaselJson_REJECT;
}
if (!(self->flags & WeaselJsonRaw)) { if (!(self->flags & WeaselJsonRaw)) {
bool useTmp = buf - self->writeBuf < 3; bool useTmp = buf - self->writeBuf < 3;
char *p = tmp; char *p = tmp;
@@ -1062,18 +1072,34 @@ constexpr inline struct ContinuationTable {
inline WeaselJsonStatus Parser3::parse(char *buf, int len) { inline WeaselJsonStatus Parser3::parse(char *buf, int len) {
this->dataBegin = this->writeBuf = buf; this->dataBegin = this->writeBuf = buf;
if (this->rejected) [[unlikely]] {
return WeaselJson_REJECT;
}
if (len < 0) [[unlikely]] {
this->rejected = true;
return WeaselJson_REJECT;
}
#ifdef HAS_MUSTTAIL #ifdef HAS_MUSTTAIL
// The continuation returns a value in 0..3 here (kBounce is only used by the // The continuation returns a value in 0..3 here (kBounce is only used by the
// no-musttail trampoline below), so the conversion back to the enum is in // no-musttail trampoline below), so the conversion back to the enum is in
// range. // range.
return WeaselJsonStatus( ContinuationStatus status =
symbolTables.continuations[top()](this, buf, buf + len)); symbolTables.continuations[top()](this, buf, buf + len);
if (status == WeaselJson_REJECT) {
this->rejected = true;
}
return WeaselJsonStatus(status);
#else #else
this->stashBufForTrampoline = buf; this->stashBufForTrampoline = buf;
ContinuationStatus result; ContinuationStatus result;
while ((result = symbolTables.continuations[top()]( while ((result = symbolTables.continuations[top()](
this, stashBufForTrampoline, buf + len)) == kBounce) this, stashBufForTrampoline, buf + len)) == kBounce)
; ;
if (result == WeaselJson_REJECT) {
this->rejected = true;
}
return WeaselJsonStatus(result); return WeaselJsonStatus(result);
#endif #endif
} }
+57
View File
@@ -202,6 +202,31 @@ TEST_CASE("parser3") {
} }
} }
TEST_CASE("rejected state is sticky") {
auto c = noopCallbacks();
auto *parser = WeaselJsonParser_create(1024, &c, nullptr, 0);
REQUIRE(parser != nullptr);
// Feed a valid prefix, then an invalid byte. The parser must reject.
std::string chunk1 = "1";
REQUIRE(WeaselJsonParser_parse(parser, chunk1.data(), chunk1.size()) ==
WeaselJson_AGAIN);
std::string chunk2 = "x";
REQUIRE(WeaselJsonParser_parse(parser, chunk2.data(), chunk2.size()) ==
WeaselJson_REJECT);
// After a reject, every later call must also reject, including the EOF
// finish call that would otherwise return OK.
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) == WeaselJson_REJECT);
// Further data chunks must also stay rejected.
std::string chunk3 = " ";
REQUIRE(WeaselJsonParser_parse(parser, chunk3.data(), chunk3.size()) ==
WeaselJson_REJECT);
WeaselJsonParser_destroy(parser);
}
TEST_CASE("create rejects too-small stack") { TEST_CASE("create rejects too-small stack") {
auto c = noopCallbacks(); auto c = noopCallbacks();
// The parser needs room for the bootstrap symbols pushed by reset(); a stack // The parser needs room for the bootstrap symbols pushed by reset(); a stack
@@ -221,6 +246,20 @@ TEST_CASE("create rejects too-small stack") {
WeaselJsonParser_destroy(parser); WeaselJsonParser_destroy(parser);
} }
TEST_CASE("parse rejects negative length") {
auto c = noopCallbacks();
auto *parser = WeaselJsonParser_create(1024, &c, nullptr, 0);
REQUIRE(parser != nullptr);
// A negative length must not cause pointer arithmetic UB. It should be
// rejected, and the rejected state should remain sticky.
char buf[10] = "hello";
REQUIRE(WeaselJsonParser_parse(parser, buf, -1) == WeaselJson_REJECT);
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) == WeaselJson_REJECT);
WeaselJsonParser_destroy(parser);
}
TEST_CASE("streaming") { testStreaming(json); } TEST_CASE("streaming") { testStreaming(json); }
TEST_CASE("reset clears inKey and transient state") { TEST_CASE("reset clears inKey and transient state") {
@@ -264,6 +303,24 @@ TEST_CASE("reset clears inKey and transient state") {
WeaselJsonParser_destroy(parser); WeaselJsonParser_destroy(parser);
} }
TEST_CASE("scalar ending at chunk boundary is finalized at EOF") {
// A number whose digits exactly fill the first chunk must not invoke
// undefined behaviour on the EOF call, and must still signal completion.
auto c = serializeCallbacks();
SerializeState state;
auto *parser = WeaselJsonParser_create(1024, &c, &state, 0);
REQUIRE(parser != nullptr);
std::string chunk = "123";
REQUIRE(WeaselJsonParser_parse(parser, chunk.data(), chunk.size()) ==
WeaselJson_AGAIN);
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) == WeaselJson_OK);
CHECK(state.result == "(123)");
WeaselJsonParser_destroy(parser);
}
void doTestUnescapingUtf8(std::string const &escaped, void doTestUnescapingUtf8(std::string const &escaped,
std::string const &expected, int stride, int flags) { std::string const &expected, int stride, int flags) {
CAPTURE(escaped); CAPTURE(escaped);
+12
View File
@@ -84,7 +84,19 @@ def test_mixed_values():
assert recorder.events.count("null") == 1 assert recorder.events.count("null") == 1
def test_create_rejects_too_small_stack():
for stack_size in (-1, 0, 1, 2):
try:
parser = weaseljson.WeaselJsonParser(Recorder(), stackSize=stack_size)
except ValueError:
continue
# If creation unexpectedly succeeds, close it cleanly and fail the test.
parser.close()
raise AssertionError(f"expected ValueError for stackSize={stack_size}")
if __name__ == "__main__": if __name__ == "__main__":
test_object_keys_routed_correctly() test_object_keys_routed_correctly()
test_mixed_values() test_mixed_values()
test_create_rejects_too_small_stack()
print("python bindings ok") print("python bindings ok")
+12
View File
@@ -117,12 +117,24 @@ class WeaselJsonParser:
self.voidp_callbacks, self.voidp_callbacks,
0, 0,
) )
if self.p is None:
raise ValueError(
"WeaselJsonParser_create returned NULL; "
"check stackSize (must be positive and large enough) "
"and available memory"
)
def _check_open(self):
if self.p is None:
raise RuntimeError("parser has been closed or creation failed")
def parse(self, data: bytes) -> WeaselJsonStatus: def parse(self, data: bytes) -> WeaselJsonStatus:
self._check_open()
buf = (ctypes.c_ubyte * len(data)).from_buffer(bytearray(data)) buf = (ctypes.c_ubyte * len(data)).from_buffer(bytearray(data))
return self._lib.WeaselJsonParser_parse(self.p, buf, len(data)) return self._lib.WeaselJsonParser_parse(self.p, buf, len(data))
def reset(self): def reset(self):
self._check_open()
self._lib.WeaselJsonParser_reset(self.p) self._lib.WeaselJsonParser_reset(self.p)
def __enter__(self): def __enter__(self):