42 Commits
Author SHA1 Message Date
andrew c44b0262bc Merge pull request 'schemagen: reject absent additionalProperties' (#58) from weaselbot/weaseljson:weaselbot/issue-55 into main
Reviewed-on: weaselab/weaseljson#58
2026-07-20 18:06:53 +00:00
andrew 814b24efba Merge pull request 'Fix schemagen integer parser rejecting zero with negative exponents' (#57) from weaselbot/weaseljson:weaselbot/issue-56 into main
Reviewed-on: weaselab/weaseljson#57
2026-07-20 18:00:36 +00:00
weaselbot 86b58e83cb schemagen: reject absent additionalProperties
Per the README contract, an object schema without an explicit
`additionalProperties` was documented as rejected at generation time,
but the code defaulted it to `false` and silently generated a strict
parser that rejects valid documents with extra properties.

Make the code match the documented contract by raising GenError when
`additionalProperties` is absent, with a clear message instructing the
author to set it explicitly to false. Update the README prose section
to list absent additionalProperties alongside true, and fix existing
schemas and tests to declare additionalProperties explicitly.

Closes #55
2026-07-19 22:06:23 -04:00
weaselbot cdff634057 Fix schemagen integer parser rejecting zero with negative exponents
Move the zero-detection check in the generated parseJsonInt64 before the
finalExp < 0 guard. Previously, valid JSON numbers whose mathematical
value is 0 but written with a large negative exponent (e.g. 0e-2,
0.0e-2, -0e-2, 0e-20) were rejected because the negative-finalExp early
return ran before the all-zero-digits branch could set out = 0. Non-zero
values with negative exponents are still correctly rejected.

Closes #56
2026-07-19 22:00:15 -04:00
andrew 16f5d3cd9d Merge pull request 'Handle null parser in WeaselJsonParser_parse' (#54) from null-parser into main
Reviewed-on: weaselab/weaseljson#54
2026-07-15 22:03:59 +00:00
andrew 6beb538b61 Add missing status enum to weaseljson.py 2026-07-15 17:55:22 -04:00
andrew 6520039dc2 Address review feedback 2026-07-15 14:12:41 -04:00
andrew 93203b14f5 Add missing unlikely annotation 2026-07-15 12:31:17 -04:00
andrew 09c0fb72ca Handle null parser in WeaselJsonParser_parse
Closes #51
2026-07-15 12:31:09 -04:00
andrew 70bb33eeaf Merge pull request 'make WeaselJson_OVERFLOW a terminal state like WeaselJson_REJECT' (#53) from weaselbot/weaseljson:weaselbot/issue-52 into main
Reviewed-on: weaselab/weaseljson#53
2026-07-13 20:58:14 +00:00
andrew 42d37d1fd7 Don't list all terminal statuses for weaseljson
Also simplifies codegen slightly presumably. OK and AGAIN should be the only non-terminal statuses ever.
2026-07-13 16:49:16 -04:00
weaselbot 5e462f1477 make WeaselJson_OVERFLOW a terminal state like WeaselJson_REJECT
When a parse step returned WeaselJson_OVERFLOW, the pushdown stack was
left corrupted (frames had been popped before the failing push), but the
overflow status was not made sticky the way WeaselJson_REJECT is. A
subsequent end-of-data call WeaselJsonParser_parse(parser, nullptr, 0)
could then dispatch through the corrupted stack and return WeaselJson_OK,
accepting an incomplete, invalid too-deeply-nested document as valid JSON.

Add an `overflowed` flag, set it whenever a continuation returns
WeaselJson_OVERFLOW, and short-circuit parse() to return
WeaselJson_OVERFLOW on every subsequent call. reset() clears the flag so a
reused parser can accept a different document. This mirrors the existing
stickiness handling for WeaselJson_REJECT.

Closes #52
2026-07-13 16:07:36 -04:00
andrew f5ceb392c2 Merge pull request 'schemagen: add regression test for nullable cyclic $ref targets' (#50) from weaselbot/weaseljson:weaselbot/issue-14 into main
Reviewed-on: weaselab/weaseljson#50
2026-06-30 16:50:07 +00:00
weaselbot be92755cbf schemagen: add regression test for nullable cyclic $ref targets (issue #14)
The generator already preserves nullability for self-referential object
$defs thanks to prior fixes, but issue #14 had no regression coverage.

Add a test using the exact reproduction schema from the issue and verify
that both the outer and recursive `self` fields accept `null`.
2026-06-30 12:42:03 -04:00
andrew d281c35811 Merge pull request 'schemagen: reject self-referential array $ref cycles instead of crashing' (#49) from weaselbot/weaseljson:weaselbot/issue-33 into main
Reviewed-on: weaselab/weaseljson#49
2026-06-30 16:37:02 +00:00
andrew f6617a1558 Merge pull request 'schemagen: escape control characters in generated C++ string literals' (#48) from weaselbot/weaseljson:weaselbot/issue-35 into main
Reviewed-on: weaselab/weaseljson#48
2026-06-30 16:30:21 +00:00
weaselbot ababd3a8fd schemagen: reject self-referential array $ref cycles instead of crashing
Cyclic array definitions (directly or through a chain of array $defs)
created a self-referential TArr, which then caused infinite recursion in
base_cpp, storage_cpp, and _walk_arrays.

Object-only cycles are already broken with std::unique_ptr, but
array-only cycles have no object edge for break_cycles to cut.

Detect them after building an array's items by following TArr.elem links
and raise a clear GenError so generation fails gracefully rather than
overflowing the Python stack.

Closes #33
2026-06-30 12:29:37 -04:00
weaselbot 681892107f schemagen: escape control characters in generated C++ string literals
Fixes #35.

Add a helper to escape C++ string literals so that JSON control characters
(\n, \r, \t, and other bytes below 0x20) are emitted as escape sequences
instead of raw bytes. Use it for:

- field comments that include the JSON property key
- object key comparison literals in matchKey()
- enum name arrays

Also add regression tests that generate and syntax-check headers for
schemas containing newlines and other control characters in property keys
and enum values.
2026-06-30 12:26:10 -04:00
andrew e22bc039ae Merge pull request 'schemagen: fix quadratic leading-zero strip in integer fallback' (#47) from weaselbot/weaseljson:weaselbot/issue-34 into main
Reviewed-on: weaselab/weaseljson#47
2026-06-30 16:16:44 +00:00
weaselbot ceb16e5405 schemagen: fix quadratic leading-zero strip in integer fallback
Replace the O(k^2) loop that erased leading zeros one byte at a time
from the front of a std::string with a single linear scan and one
erase(0, n) call.

Also adds regression tests for issue #34:
- correctness cases for numbers with leading fractional zeros
- a static check that the generated code no longer contains the
  quadratic pattern
- a large-input case (100k leading zeros) that reproduces the
  vulnerable shape

Closes #34
2026-06-30 12:09:26 -04:00
andrew 241c29073b Merge pull request 'schemagen: handle WeaselJsonParser_create failure in RootBuilder' (#46) from weaselbot/weaseljson:weaselbot/issue-36 into main
Reviewed-on: weaselab/weaseljson#46
2026-06-30 15:35:20 +00:00
weaselbot abeaae7ed7 schemagen: handle WeaselJsonParser_create failure in RootBuilder
If WeaselJsonParser_create returns nullptr (e.g. negative stack size or allocation failure), set the existing error_ flag so that subsequent feed()/finish() calls return WeaselJson_REJECT instead of dereferencing the null parser_.

Also add a regression test in test_gen.cpp that constructs a RootBuilder with an invalid stack size and verifies it rejects without crashing.

Closes #36
2026-06-30 11:26:59 -04:00
andrew 4bd1088018 Merge pull request 'Include <cstdint> in json_value.h for uint8_t' (#45) from weaselbot/weaseljson:weaselbot/issue-37 into main
Reviewed-on: weaselab/weaseljson#45
2026-06-29 18:57:40 +00:00
andrew 82bdc8a080 Merge pull request 'python: raise OSError when shared library is missing' (#44) from weaselbot/weaseljson:weaselbot/issue-38 into main
Reviewed-on: weaselab/weaseljson#44
2026-06-29 18:53:49 +00:00
andrew 6508616edc Merge pull request 'Handle null parser in WeaselJsonParser_reset and _destroy' (#42) from weaselbot/weaseljson:weaselbot/issue-41 into main
Reviewed-on: weaselab/weaseljson#42
2026-06-29 18:26:48 +00:00
weaselbot e5c970a605 Include <cstdint> in json_value.h for uint8_t
`escapeAsJsonString` uses `uint8_t` but the header did not include
`<cstdint>`, making it dependent on other headers to define the type.
Add the missing include so `json_value.h` is self-contained.
2026-06-29 14:04:04 -04:00
weaselbot 96f61665bf python: raise OSError when shared library is missing
Replace sys.exit(1) in WeaselJsonParser.__init__ with an OSError so
callers can handle a missing libweaseljson gracefully. Also add a test
that verifies the constructor raises OSError for a non-existent build
directory.

Closes #38
2026-06-29 14:03:03 -04:00
weaselbot 34fc22a7c2 Handle null parser in WeaselJsonParser_reset and _destroy
WeaselJsonParser_create can return nullptr when allocation fails or the
requested stack size is too small. Previously, passing that nullptr to
WeaselJsonParser_reset or WeaselJsonParser_destroy dereferenced it before
doing any work, causing immediate undefined behavior.

Add an early null check to both functions so they behave like free(nullptr)
(i.e., are a safe no-op). Also add a doctest case covering both a null
returned from create and a literal nullptr.

Closes #41
2026-06-29 13:53:31 -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
44 changed files with 2250 additions and 113 deletions
+48
View File
@@ -46,6 +46,49 @@ 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(
NAME schemagen_example
COMMAND schemagen_example
@@ -55,3 +98,8 @@ 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})
+7 -6
View File
@@ -45,7 +45,7 @@ into the result, so it is non-movable.
| `$ref` to `$defs`/`definitions` | the referenced named struct |
| recursive `$ref` | `std::unique_ptr<T>` (cycle broken) |
| `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)
@@ -55,15 +55,16 @@ into the result, so it is non-movable.
- value not in a string `enum`
- a number not representable in the target type (e.g. `1.5` for an `integer`)
- duplicate object keys
- unknown key under `additionalProperties: false`
- unknown key in any object
## Not supported (rejected at generation time, no fallback)
`oneOf` / `anyOf` / `allOf` / `not` / `if`-`then`-`else`,
`patternProperties`, `additionalProperties` with a schema (typed map),
`prefixItems` (tuples), `const`, `dependentSchemas`/`dependentRequired`,
union `type` lists other than `["T", "null"]`, non-string enums, and remote
(`$ref` to other documents).
`patternProperties`, `additionalProperties` absent or set to `true`,
`additionalProperties` with a schema (typed map), `prefixItems` (tuples),
`const`,
`dependentSchemas`/`dependentRequired`, union `type` lists other than
`["T", "null"]`, non-string enums, and remote (`$ref` to other documents).
## Notes
+1
View File
@@ -56,6 +56,7 @@
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"name"
],
@@ -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"
]
}
+13
View File
@@ -68,6 +68,15 @@ int main() {
expectReject(json, "unknown key in strict root");
}
// ---- invalid stack size is rejected without crashing ----
{
RootBuilder b(-1);
char buf[] = "null";
WeaselJsonStatus s = b.feed(buf, sizeof(buf) - 1);
CHECK(s == WeaselJson_REJECT);
printf("ok invalid stack size rejected, not crashed\n");
}
{
std::string json = R"({
"name": "Ada É",
@@ -184,6 +193,10 @@ int main() {
expectReject(R"({"name":"x","age":1,"address":{"zip":5}})",
"missing required nested 'city'");
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");
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;
}
+672 -6
View File
@@ -8,6 +8,7 @@ import shutil
import subprocess
import sys
import tempfile
import textwrap
import unittest
SCRIPT = os.path.join(os.path.dirname(__file__), "weaseljson_schemagen.py")
@@ -31,6 +32,7 @@ class SchemagenEnumTest(unittest.TestCase):
def test_colliding_enum_values_deduplicate(self):
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"role": {"enum": ["foo-bar", "foo_bar"]}},
}
rc, stdout, stderr = self.run_schemagen(schema)
@@ -44,6 +46,7 @@ class SchemagenEnumTest(unittest.TestCase):
def test_distinct_enum_values_generate(self):
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"role": {"enum": ["admin", "user", "guest"]}},
}
rc, stdout, stderr = self.run_schemagen(schema)
@@ -79,7 +82,7 @@ class SchemagenKeywordTest(unittest.TestCase):
"module",
"import",
]
schema = {"type": "object", "properties": {}}
schema = {"type": "object", "additionalProperties": False, "properties": {}}
for kw in keywords:
schema["properties"][kw] = {"type": "string"}
rc, stdout, stderr = self.run_schemagen(schema)
@@ -163,11 +166,18 @@ class SchemagenCollisionTest(unittest.TestCase):
def test_kind_enum_does_not_duplicate_arr0(self):
schema = {
"type": "object",
"additionalProperties": False,
"properties": {
"arr": {"type": "array", "items": {"type": "string"}},
"obj": {"$ref": "#/$defs/Arr0"},
},
"$defs": {"Arr0": {"type": "object", "properties": {}}},
"$defs": {
"Arr0": {
"type": "object",
"additionalProperties": False,
"properties": {},
}
},
}
out = self.generate_and_compile(schema)
self.assertIn("struct Arr0", out)
@@ -178,21 +188,677 @@ class SchemagenCollisionTest(unittest.TestCase):
self.assertIn("Arr1", enumerators)
self.assertEqual(len(enumerators), len(set(enumerators)))
def test_skip_is_reserved(self):
def test_skip_user_type_is_allowed(self):
schema = {
"type": "array",
"items": {"$ref": "#/$defs/Skip"},
"$defs": {"Skip": {"type": "object", "properties": {}}},
"$defs": {
"Skip": {
"type": "object",
"additionalProperties": False,
"properties": {},
}
},
}
out = self.generate_and_compile(schema)
self.assertIn("struct Skip1", out)
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("Skip1", 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",
"additionalProperties": False,
"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_rejected(self):
schema = {
"type": "object",
"properties": {"name": {"type": "string"}},
}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertNotEqual(rc, 0)
self.assertIn("additionalProperties is required for object schemas", stderr)
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 SchemagenCyclicArrayTest(unittest.TestCase):
"""Regression tests for issue #33: cyclic array $ref targets."""
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_direct_self_referential_array_rejected(self):
schema = {
"type": "array",
"items": {"$ref": "#/$defs/Node"},
"$defs": {
"Node": {
"type": "array",
"items": {"$ref": "#/$defs/Node"},
}
},
}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertNotEqual(rc, 0)
self.assertIn("recursive array type is not supported", stderr)
self.assertIn("Node", stderr)
def test_object_field_to_self_referential_array_rejected(self):
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"items": {"$ref": "#/$defs/Items"}},
"$defs": {
"Items": {
"type": "array",
"items": {"$ref": "#/$defs/Items"},
}
},
}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertNotEqual(rc, 0)
self.assertIn("recursive array type is not supported", stderr)
self.assertIn("Items", stderr)
def test_chain_of_array_refs_rejected(self):
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"x": {"$ref": "#/$defs/A"}},
"$defs": {
"A": {"type": "array", "items": {"$ref": "#/$defs/B"}},
"B": {"type": "array", "items": {"$ref": "#/$defs/A"}},
},
}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertNotEqual(rc, 0)
self.assertIn("recursive array type is not supported", stderr)
def test_non_recursive_array_refs_still_allowed(self):
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"roles": {"$ref": "#/$defs/Roles"}},
"$defs": {
"Role": {"enum": ["admin", "user"]},
"Roles": {
"type": "array",
"items": {"$ref": "#/$defs/Role"},
},
},
}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertEqual(rc, 0, msg=stderr)
self.assertIn("std::optional<std::vector<Role>> roles;", stdout)
class SchemagenCyclicNullableObjectTest(unittest.TestCase):
"""Regression tests for issue #14: nullable cyclic $ref targets."""
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 test_self_referential_nullable_object_accepts_nested_null(self):
"""A nullable object $def must stay nullable on recursive $refs."""
if not self.compiler:
self.skipTest("C++ compiler not available")
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"self": {"$ref": "#/$defs/Self"}},
"$defs": {
"Self": {
"type": ["object", "null"],
"additionalProperties": False,
"properties": {"self": {"$ref": "#/$defs/Self"}},
}
},
}
harness = textwrap.dedent(
"""
#include "gen.h"
#include <cstdio>
#include <cstring>
struct Case { const char *s; WeaselJsonStatus expected; };
int main() {
Case cases[] = {
{ R"({"self": null})", WeaselJson_OK },
{ R"({"self": {"self": null}})", WeaselJson_OK },
{ R"({"self": {"self": {}}})", WeaselJson_OK },
};
for (const auto &c : cases) {
test_schema::RootBuilder b;
char buf[256];
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("case %s expected %d got %d\\n", c.s, c.expected, st);
return 1;
}
}
return 0;
}
"""
)
with tempfile.TemporaryDirectory() as tmpdir:
self._compile_harness(tmpdir, schema, harness)
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),
("0.0001e4", "WeaselJson_OK", 1),
("0.001e3", "WeaselJson_OK", 1),
("123.0", "WeaselJson_OK", 123),
("9e18", "WeaselJson_OK", 9000000000000000000),
("10e18", "WeaselJson_REJECT", 0),
# Issue #56: zero written with a negative exponent must be accepted.
("0e-1", "WeaselJson_OK", 0),
("0e-2", "WeaselJson_OK", 0),
("0.0e-2", "WeaselJson_OK", 0),
("-0e-2", "WeaselJson_OK", 0),
("0e-20", "WeaselJson_OK", 0),
("0.000e-5", "WeaselJson_OK", 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.0001e4}', "WeaselJson_OK", 1),
('{"age":0.001}', "WeaselJson_REJECT", 0),
# Issue #56: zero written with a negative exponent must be accepted.
('{"age":0e-2}', "WeaselJson_OK", 0),
('{"age":-0e-20}', "WeaselJson_OK", 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",
"additionalProperties": False,
"properties": {"age": {"type": "integer"}},
"required": ["age"],
}
with tempfile.TemporaryDirectory() as tmpdir:
self._compile_harness(tmpdir, schema, harness)
def test_integer_no_quadratic_leading_zero_loop(self):
"""Regression test for issue #34: leading-zero stripping must not be quadratic."""
with tempfile.TemporaryDirectory() as tmpdir:
schema_path = os.path.join(tmpdir, "schema.json")
with open(schema_path, "w") as fp:
json.dump({"type": "integer"}, fp)
result = subprocess.run(
[sys.executable, SCRIPT, schema_path],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(result.returncode, 0, msg=result.stderr)
self.assertIn("parseJsonInt64", result.stdout)
self.assertNotIn("digits.erase(digits.begin())", result.stdout)
def test_integer_large_fractional_leading_zeros(self):
"""Numbers with many leading fractional zeros must parse correctly."""
if not self.compiler:
self.skipTest("C++ compiler not available")
harness = textwrap.dedent(
"""
#include "gen.h"
#include <cstdio>
#include <string>
int main() {
const int n = 100000;
std::string s = std::string("0.") + std::string(n - 1, '0') + "1e" + std::to_string(n);
test_schema::RootBuilder b;
WeaselJsonStatus st = b.feed(s.data(), static_cast<int>(s.size()));
st = b.finish();
if (st != WeaselJson_OK) {
std::printf("expected OK, got %d\\n", st);
return 1;
}
if (b.take() != 1) {
std::printf("expected value 1\\n");
return 2;
}
return 0;
}
"""
)
with tempfile.TemporaryDirectory() as tmpdir:
self._compile_harness(tmpdir, {"type": "integer"}, harness)
class SchemagenStringEscapeTest(unittest.TestCase):
"""Regression tests for issue #35: control characters in string literals."""
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_newline_in_property_key(self):
"""A JSON key containing a newline must become a valid C++ literal."""
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"a\nb": {"type": "string"}},
}
out = self.generate_and_compile(schema)
self.assertIn('// "a\\nb"', out)
self.assertIn('if (key == "a\\nb") return 0;', out)
def test_newline_in_enum_value(self):
"""An enum value containing a newline must become a valid C++ literal."""
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"x": {"enum": ["a\nb"]}},
}
out = self.generate_and_compile(schema)
self.assertIn('static constexpr const char *X_names[] = { "a\\nb" };', out)
def test_mixed_control_chars_in_enum_value(self):
"""Mixed control characters in an enum value must be escaped."""
schema = {
"type": "object",
"additionalProperties": False,
"properties": {
"x": {"enum": ["x\ny\rz\tw\vq\x00\x01"]},
},
}
out = self.generate_and_compile(schema)
self.assertIn(
'static constexpr const char *X_names[] = { "x\\ny\\rz\\tw\\vq\\u0000\\u0001" };',
out,
)
def test_backslash_and_quote_still_escaped(self):
"""Existing escaping for backslash and double quote must remain correct."""
schema = {
"type": "object",
"additionalProperties": False,
"properties": {'a"b\\c': {"type": "string"}},
}
out = self.generate_and_compile(schema)
self.assertIn('// "a\\"b\\\\c"', out)
self.assertIn('if (key == "a\\"b\\\\c") return 0;', out)
if __name__ == "__main__":
unittest.main()
+236 -81
View File
@@ -18,6 +18,43 @@ import keyword
import sys
def _escape_cpp_string(s):
"""Return *s* escaped for use inside a C++ double-quoted string literal.
JSON strings may contain control characters; emitting them verbatim into
generated C++ source breaks tokenization. This helper escapes backslashes
and double quotes, maps common control characters to their short escape
sequences, and uses universal character names (\\u00XX) for any other
character below 0x20.
"""
out = []
for ch in s:
cp = ord(ch)
if cp == 0x09:
out.append("\\t")
elif cp == 0x0A:
out.append("\\n")
elif cp == 0x0B:
out.append("\\v")
elif cp == 0x0C:
out.append("\\f")
elif cp == 0x0D:
out.append("\\r")
elif cp == 0x08:
out.append("\\b")
elif cp == 0x07:
out.append("\\a")
elif cp < 0x20:
out.append(f"\\u{cp:04X}")
elif ch == "\\":
out.append("\\\\")
elif ch == '"':
out.append('\\"')
else:
out.append(ch)
return "".join(out)
class GenError(Exception):
pass
@@ -60,7 +97,6 @@ class ObjectType:
def __init__(self, name):
self.name = name
self.fields = [] # list[Field]
self.strict = False # additionalProperties: false
class EnumType:
@@ -234,7 +270,25 @@ class Builder:
@staticmethod
def _is_reserved(name):
"""Names generated internally that must not collide with user types."""
return name in ("Root", "Skip", "RootScalar")
return name in ("Root", "RootScalar")
@staticmethod
def _array_reaches(target, ty):
"""Return True if `target` can be reached from `ty` by following
TArr element types. This detects self-referential array cycles that
cannot be expressed as C++ structs."""
seen = set()
stack = [ty]
while stack:
cur = stack.pop()
if cur is target:
return True
if id(cur) in seen:
continue
seen.add(id(cur))
if isinstance(cur, TArr):
stack.append(cur.elem)
return False
def ref_name(self, ref):
if not ref.startswith("#/"):
@@ -251,7 +305,6 @@ class Builder:
if defname not in self.defs:
raise GenError(f"$ref to unknown def: {defname}")
node = self.defs[defname]
# For objects we must register the name before recursing into fields.
return self.build_type(node, defname, defname=defname)
def build_type(self, node, hint, defname=None):
@@ -280,6 +333,10 @@ class Builder:
if "$ref" in node:
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"]
nullable = False
typ = node.get("type")
@@ -298,18 +355,35 @@ class Builder:
raise GenError("only non-empty string enums are supported")
name = defname and self.unique_name(defname) or self.unique_name(hint)
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):
return (self._build_object(node, hint, defname), nullable)
return (self._build_object(node, hint, defname, nullable), nullable)
if typ == "array":
if "items" not in node or not isinstance(node["items"], dict):
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(
self.build_type(node["items"], hint + "Item")
)
return (TArr(elem, elem_nullable), nullable)
t.elem = elem
t.elem_nullable = elem_nullable
# Self-referential array cycles (directly or through a chain of
# array definitions) cannot be represented as a C++ value type.
if self._array_reaches(t, elem):
raise GenError(
"recursive array type is not supported: " f"{defname or hint!r}"
)
return result
scalar = {
"string": "str",
@@ -318,7 +392,10 @@ class Builder:
"boolean": "bool",
}.get(typ)
if scalar:
return (TScalar(scalar), nullable)
result = (TScalar(scalar), nullable)
if defname is not None:
self._building[defname] = result
return result
if typ == "null":
raise GenError("'null'-only types are not supported")
@@ -332,19 +409,27 @@ class Builder:
return result
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)
obj = ObjectType(name)
self.objects[name] = obj
# register for $ref cycles before building fields
tobj = TObj(name)
if defname is not None:
self._building[defname] = TObj(name)
ap = node.get("additionalProperties", True)
self._building[defname] = (tobj, nullable)
ap = node.get("additionalProperties", None)
if ap is None:
raise GenError(
"additionalProperties is required for object schemas; set it "
"explicitly to false to reject unknown keys (absent "
"additionalProperties is not supported)"
)
if ap is True:
raise GenError("additionalProperties: true is not supported")
if isinstance(ap, dict):
raise GenError(
"additionalProperties with a schema (typed map) is not " "supported yet"
)
obj.strict = ap is False
required = set(node.get("required", []))
props = node.get("properties", {})
seen_cpp = set()
@@ -358,7 +443,7 @@ class Builder:
cpp = f"{base}{i}"
seen_cpp.add(cpp)
obj.fields.append(Field(key, cpp, ty, key in required, nullable))
return TObj(name)
return tobj
# ---------------------------------------------------------------------------
@@ -516,6 +601,18 @@ class Emitter:
self.root_ty, self.root_nullable = Builder._unpack(
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)
# register all array kinds (walk every field + root)
@@ -588,7 +685,8 @@ namespace {ns} {{"""
and f.ty.kind in ("int", "dbl", "bool")
):
init = " = 0" if f.ty.kind != "bool" else " = false"
out.append(f' {store} {f.cpp}{init}; // "{f.key}"')
esc_key = _escape_cpp_string(f.key)
out.append(f' {store} {f.cpp}{init}; // "{esc_key}"')
out.append("};")
out.append("")
return "\n".join(out)
@@ -605,20 +703,17 @@ namespace {ns} {{"""
def _kind_enum(self):
kinds = list(self.b.objects.keys())
kinds += [n for n, _ in self.arr_types]
root_is_container = (
isinstance(self.root_ty, (TObj, TArr)) and not self.root_nullable
)
root_is_container = isinstance(self.root_ty, (TObj, TArr))
if not root_is_container:
kinds.append("RootScalar")
kinds.append("Skip")
self.kind_order = kinds
return " enum class Kind : uint8_t { " + ", ".join(kinds) + " };"
def _root_info(self):
"""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}")
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 (self.cat(self.root_ty), None)
@@ -688,6 +783,11 @@ namespace {ns} {{"""
lines.append(" }")
root_cat, root_kind = self._root_info()
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(" default: return nullptr;")
lines.append(" }")
@@ -714,7 +814,7 @@ namespace {ns} {{"""
for name, obj in self.b.objects.items():
lines.append(f" case Kind::{name}:")
for i, fld in enumerate(obj.fields):
esc = fld.key.replace("\\", "\\\\").replace('"', '\\"')
esc = _escape_cpp_string(fld.key)
lines.append(f' if (key == "{esc}") return {i};')
lines.append(" return -1;")
lines.append(" default: return -1;")
@@ -794,24 +894,10 @@ namespace {ns} {{"""
lines.append(" }")
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):
out = []
for e in self.b.enums.values():
lits = ", ".join(
'"' + v.replace("\\", "\\\\").replace('"', '\\"') + '"'
for v in e.values
)
lits = ", ".join('"' + _escape_cpp_string(v) + '"' for v in e.values)
out.append(
f" static constexpr const char *{e.name}_names[] = {{ {lits} }};"
)
@@ -830,7 +916,12 @@ namespace {ns} {{"""
lines = [" if (error_) return;"]
if root_kind is not None and root_cat == event_cat:
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});")
@@ -841,13 +932,7 @@ namespace {ns} {{"""
else:
lines.append(" if (stack_.empty()) { reject(); return; }")
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(
" 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(" void *p = engage(f, true);")
lines.append(" stack_.push_back(Frame{si.child, p});")
@@ -862,6 +947,9 @@ namespace {ns} {{"""
root_cat, root_kind = self._root_info()
begin_obj = self._begin_container("Obj", 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"""
class RootBuilder {{
@@ -869,6 +957,10 @@ public:
explicit RootBuilder(int stackSize = 1024) {{
cb_ = makeCallbacks();
parser_ = WeaselJsonParser_create(stackSize, &cb_, this, 0);
if (!parser_) {{
error_ = true;
return;
}}
{self._ctor_body()}
}}
~RootBuilder() {{ if (parser_) WeaselJsonParser_destroy(parser_); }}
@@ -895,7 +987,7 @@ public:
private:
{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 {{
Cat cat;
Kind child{{}};
@@ -906,11 +998,10 @@ private:
struct Frame {{
Kind kind;
void *dest;
int field = -1; // object: selected field (-1 want key, -2 skip)
int field = -1; // object: selected field (-1 means waiting for key)
std::vector<uint64_t> seen; // populated field bitset (object frames)
}};
static constexpr int kWantKey = -1;
static constexpr int kSkip = -2;
{self._enum_name_arrays()}
@@ -925,10 +1016,95 @@ private:
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;
size_t leadingZeros = 0;
while (leadingZeros < digits.size() && digits[leadingZeros] == '0') ++leadingZeros;
if (leadingZeros == digits.size()) {{ out = 0; return true; }}
if (finalExp < 0) return false;
if (leadingZeros > 0) digits.erase(0, leadingZeros);
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) {{
if (isObjectKind(f.kind)) {{
if (f.field == kSkip) return SlotInfo{{Cat::Skip}};
if (f.field < 0) return SlotInfo{{Cat::Reject}};
}}
return slotInfo(f);
@@ -953,11 +1129,6 @@ private:
void cbEndObject() {{
if (error_) return;
Frame &f = stack_.back();
if (f.kind == Kind::Skip) {{
stack_.pop_back();
if (stack_.empty() || stack_.back().kind != Kind::Skip) valueComplete();
return;
}}
if (isObjectKind(f.kind)) {{
const auto &req = requiredMask(f.kind);
bool missing = false;
@@ -975,11 +1146,6 @@ private:
void cbEndArray() {{
if (error_) return;
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();
valueComplete();
}}
@@ -991,12 +1157,10 @@ private:
scratch_.append(buf, len);
if (!done) return;
int idx = matchKey(f.kind, scratch_);
scratch_.clear();
if (idx < 0) {{
if (isStrict(f.kind)) {{ reject(); return; }}
f.field = kSkip;
return;
reject(); return; // unknown key
}}
scratch_.clear();
if (f.seen[idx >> 6] & (1ull << (idx & 63))) {{
reject(); return; // duplicate key
}}
@@ -1005,10 +1169,9 @@ private:
void cbStringData(const char *buf, int len, int done) {{
if (error_) return;
if (stack_.empty()) {{ reject(); return; }}
Frame &f = stack_.back();
if (f.kind == Kind::Skip) return;
SlotInfo si = slotInfoG(f);
if (si.cat == Cat::Skip) {{ if (done) valueComplete(); return; }}
if (si.cat == Cat::Str) {{
auto *s = (std::string *)engage(f, !started_);
s->append(buf, len);
@@ -1033,10 +1196,9 @@ private:
void cbNumberData(const char *buf, int len, int done) {{
if (error_) return;
if (stack_.empty()) {{ reject(); return; }}
Frame &f = stack_.back();
if (f.kind == Kind::Skip) return;
SlotInfo si = slotInfoG(f);
if (si.cat == Cat::Skip) {{ if (done) valueComplete(); return; }}
if (si.cat != Cat::Int && si.cat != Cat::Dbl) {{ reject(); return; }}
scratch_.append(buf, len);
started_ = true;
@@ -1051,16 +1213,11 @@ private:
*(int64_t *)p = v; // plain integer literal: parsed exactly
}} else {{
// JSON Schema "integer" accepts any number with no fractional part,
// including exponent/decimal forms like 1e3 or 2.0. Parse those as a
// double and require an integral value within int64 range.
double d = 0;
auto rd = std::from_chars(b, e, d);
if (rd.ec != std::errc() || rd.ptr != e || d != std::trunc(d) ||
d < -9223372036854775808.0 || d >= 9223372036854775808.0) {{
reject();
return;
}}
*(int64_t *)p = (int64_t)d;
// including exponent/decimal forms like 1e3 or 2.0. Parse those
// exactly as int64 when the value is integral and in range.
int64_t v2 = 0;
if (!parseJsonInt64(b, e, v2)) {{ reject(); return; }}
*(int64_t *)p = v2;
}}
}} else {{
double v = 0;
@@ -1073,10 +1230,9 @@ private:
void cbBool(bool value) {{
if (error_) return;
if (stack_.empty()) {{ reject(); return; }}
Frame &f = stack_.back();
if (f.kind == Kind::Skip) return;
SlotInfo si = slotInfoG(f);
if (si.cat == Cat::Skip) {{ valueComplete(); return; }}
if (si.cat != Cat::Bool) {{ reject(); return; }}
*(bool *)engage(f, true) = value;
valueComplete();
@@ -1088,10 +1244,11 @@ private:
void cbNull() {{
if (error_) return;
if (stack_.empty()) {{
{null_at_root}
}}
Frame &f = stack_.back();
if (f.kind == Kind::Skip) return;
SlotInfo si = slotInfoG(f);
if (si.cat == Cat::Skip) {{ valueComplete(); return; }}
if (!si.nullable) {{ reject(); return; }}
if (isArrayKind(f.kind)) appendNull(f); // keep null array elements
valueComplete(); // leave optional empty / pointer null
@@ -1129,8 +1286,6 @@ private:
{self._reqmask()}
{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{},[-[
+3 -1
View File
@@ -38,6 +38,8 @@ enum WeaselJsonStatus {
WeaselJson_REJECT,
/** json is too deeply nested */
WeaselJson_OVERFLOW,
/** Tried to call parse on a null parser */
WeaselJson_NULL,
};
typedef struct WeaselJsonParser WeaselJsonParser;
@@ -65,7 +67,7 @@ void WeaselJsonParser_destroy(WeaselJsonParser *parser);
/** 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
* `len` is 0. `len` must not be negative; a negative length is treated as a
* rejected input. */
* rejected input. Returns WeaselJson_NULL if parser is null */
WeaselJsonStatus WeaselJsonParser_parse(WeaselJsonParser *parser, char *buf,
int len);
+1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
+9
View File
@@ -29,17 +29,26 @@ WeaselJsonParser_create(int stackSize, const WeaselJsonCallbacks *callbacks,
__attribute__((visibility("default"))) void
WeaselJsonParser_reset(WeaselJsonParser *parser) {
if (parser == nullptr) {
return;
}
((Parser3 *)parser)->reset();
}
__attribute__((visibility("default"))) void
WeaselJsonParser_destroy(WeaselJsonParser *parser) {
if (parser == nullptr) {
return;
}
((Parser3 *)parser)->~Parser3();
free(parser);
}
__attribute__((visibility("default"))) WeaselJsonStatus
WeaselJsonParser_parse(WeaselJsonParser *parser, char *buf, int len) {
if (parser == nullptr) [[unlikely]] {
return WeaselJson_NULL;
}
return ((Parser3 *)parser)->parse(buf, len);
}
}
+17 -11
View File
@@ -139,7 +139,7 @@ struct Parser3 {
stackPtr = stack();
std::ignore = push({N_VALUE, N_WHITESPACE, T_EOF});
inKey = false;
rejected = false;
terminalStatus = WeaselJson_OK;
utf8Codepoint = 0;
utf16Surrogate = 0;
minCodepoint = 0;
@@ -162,7 +162,7 @@ struct Parser3 {
NumDfa numDfa;
Utf8Dfa strDfa;
bool inKey = false;
bool rejected = false;
WeaselJsonStatus terminalStatus = WeaselJson_OK;
#ifndef HAS_MUSTTAIL
char *stashBufForTrampoline;
@@ -625,7 +625,7 @@ inline PRESERVE_NONE ContinuationStatus n_string2(Parser3 *self, char *buf,
return WeaselJson_REJECT;
}
buf += 4;
if (0xd800 <= codepoint && codepoint <= 0xdfff) {
if (0xd800 <= codepoint && codepoint <= 0xdbff) {
// utf-16 surrogate
int32_t codepoint2 = read4_hex(buf + 2);
if (!(buf[0] == '\\' && buf[1] == 'u' && 0xdc00 <= codepoint2 &&
@@ -650,6 +650,8 @@ inline PRESERVE_NONE ContinuationStatus n_string2(Parser3 *self, char *buf,
self->writeBuf[0] = (0b00000111 & codepoint) | 0b11110000;
self->writeBuf += 4;
}
} else if (0xdc00 <= codepoint && codepoint <= 0xdfff) [[unlikely]] {
return WeaselJson_REJECT;
} else {
if (!(self->flags & WeaselJsonRaw)) {
if (codepoint < 0x80) {
@@ -793,7 +795,7 @@ inline PRESERVE_NONE ContinuationStatus t_hex2(Parser3 *self, char *buf,
}
} else {
assert(self->utf8Codepoint < 0x10000);
if (0xd800 <= self->utf8Codepoint && self->utf8Codepoint <= 0xdfff) {
if (0xd800 <= self->utf8Codepoint && self->utf8Codepoint <= 0xdbff) {
// utf-16 surrogate
self->utf16Surrogate = self->utf8Codepoint;
self->utf8Codepoint = 0;
@@ -808,6 +810,10 @@ inline PRESERVE_NONE ContinuationStatus t_hex2(Parser3 *self, char *buf,
}
MUSTTAIL return Parser3::keepGoing(self, buf, bufEnd);
}
if (0xdc00 <= self->utf8Codepoint && self->utf8Codepoint <= 0xdfff)
[[unlikely]] {
return WeaselJson_REJECT;
}
if (!(self->flags & WeaselJsonRaw)) {
bool useTmp = buf - self->writeBuf < 3;
char *p = tmp;
@@ -1064,12 +1070,12 @@ constexpr inline struct ContinuationTable {
inline WeaselJsonStatus Parser3::parse(char *buf, int len) {
this->dataBegin = this->writeBuf = buf;
if (this->rejected) [[unlikely]] {
return WeaselJson_REJECT;
if (this->terminalStatus != WeaselJson_OK) [[unlikely]] {
return this->terminalStatus;
}
if (len < 0) [[unlikely]] {
this->rejected = true;
this->terminalStatus = WeaselJson_REJECT;
return WeaselJson_REJECT;
}
@@ -1079,8 +1085,8 @@ inline WeaselJsonStatus Parser3::parse(char *buf, int len) {
// range.
ContinuationStatus status =
symbolTables.continuations[top()](this, buf, buf + len);
if (status == WeaselJson_REJECT) {
this->rejected = true;
if (status > WeaselJson_AGAIN) {
this->terminalStatus = WeaselJsonStatus(status);
}
return WeaselJsonStatus(status);
#else
@@ -1089,8 +1095,8 @@ inline WeaselJsonStatus Parser3::parse(char *buf, int len) {
while ((result = symbolTables.continuations[top()](
this, stashBufForTrampoline, buf + len)) == kBounce)
;
if (result == WeaselJson_REJECT) {
this->rejected = true;
if (result > WeaselJson_AGAIN) {
this->terminalStatus = WeaselJsonStatus(result);
}
return WeaselJsonStatus(result);
#endif
+74
View File
@@ -202,6 +202,62 @@ TEST_CASE("parser3") {
}
}
TEST_CASE("overflow state is sticky") {
auto c = noopCallbacks();
// stackSize 3 is exactly big enough to hold reset()'s bootstrap, but too
// small for nested arrays. Overflows must be terminal like rejects: a later
// end-of-data call must never report OK for an incomplete document.
auto *parser = WeaselJsonParser_create(3, &c, nullptr, 0);
REQUIRE(parser != nullptr);
std::string doc = "[[";
REQUIRE(WeaselJsonParser_parse(parser, doc.data(), doc.size()) ==
WeaselJson_OVERFLOW);
// After overflow, the end-of-data call must not return OK.
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) != WeaselJson_OK);
// It should keep reporting a terminal failure.
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) != WeaselJson_OK);
// Further data chunks must also stay terminal.
std::string more = "]]";
REQUIRE(WeaselJsonParser_parse(parser, more.data(), more.size()) !=
WeaselJson_OK);
WeaselJsonParser_destroy(parser);
}
TEST_CASE("overflow is sticky for nested objects") {
auto c = noopCallbacks();
auto *parser = WeaselJsonParser_create(4, &c, nullptr, 0);
REQUIRE(parser != nullptr);
std::string doc = "{\"a\":{ \"a\":";
REQUIRE(WeaselJsonParser_parse(parser, doc.data(), doc.size()) ==
WeaselJson_OVERFLOW);
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) != WeaselJson_OK);
WeaselJsonParser_destroy(parser);
}
TEST_CASE("reset clears overflow state") {
auto c = noopCallbacks();
auto *parser = WeaselJsonParser_create(3, &c, nullptr, 0);
REQUIRE(parser != nullptr);
std::string doc = "[[";
REQUIRE(WeaselJsonParser_parse(parser, doc.data(), doc.size()) ==
WeaselJson_OVERFLOW);
// After reset the parser should accept a minimal document again.
WeaselJsonParser_reset(parser);
std::string copy = "1";
REQUIRE(WeaselJsonParser_parse(parser, copy.data(), copy.size()) ==
WeaselJson_AGAIN);
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) == WeaselJson_OK);
WeaselJsonParser_destroy(parser);
}
TEST_CASE("rejected state is sticky") {
auto c = noopCallbacks();
auto *parser = WeaselJsonParser_create(1024, &c, nullptr, 0);
@@ -246,6 +302,20 @@ TEST_CASE("create rejects too-small stack") {
WeaselJsonParser_destroy(parser);
}
TEST_CASE("reset and destroy accept null parser") {
// Creation can legitimately fail and return null. The cleanup functions must
// tolerate a null pointer the same way free(nullptr) is a no-op.
auto c = noopCallbacks();
WeaselJsonParser *parser = WeaselJsonParser_create(-1, &c, nullptr, 0);
REQUIRE(parser == nullptr);
WeaselJsonParser_reset(parser); // must not crash
WeaselJsonParser_destroy(parser); // must not crash
// Calling reset/destroy on literal nullptr directly must also be safe.
WeaselJsonParser_reset(nullptr);
WeaselJsonParser_destroy(nullptr);
}
TEST_CASE("parse rejects negative length") {
auto c = noopCallbacks();
auto *parser = WeaselJsonParser_create(1024, &c, nullptr, 0);
@@ -260,6 +330,10 @@ TEST_CASE("parse rejects negative length") {
WeaselJsonParser_destroy(parser);
}
TEST_CASE("Calling parse with nullptr doesn't crash") {
REQUIRE(WeaselJsonParser_parse(nullptr, nullptr, 0) == WeaselJson_NULL);
}
TEST_CASE("streaming") { testStreaming(json); }
TEST_CASE("reset clears inKey and transient state") {
+3
View File
@@ -33,6 +33,9 @@ int main(int argc, char **argv) {
case WeaselJson_REJECT:
case WeaselJson_OVERFLOW:
return 1;
case WeaselJson_NULL:
fprintf(stderr, "parse called with a null parser\n");
return 1;
}
if (l == 0) {
return 1;
+12
View File
@@ -95,8 +95,20 @@ def test_create_rejects_too_small_stack():
raise AssertionError(f"expected ValueError for stackSize={stack_size}")
def test_missing_library_raises_oserror():
try:
weaseljson.WeaselJsonParser(
weaseljson.WeaselJsonCallbacksBase(),
build_dir="/nonexistent",
)
except OSError:
return
raise AssertionError("expected OSError when the shared library is missing")
if __name__ == "__main__":
test_object_keys_routed_correctly()
test_mixed_values()
test_create_rejects_too_small_stack()
test_missing_library_raises_oserror()
print("python bindings ok")
+2 -7
View File
@@ -30,6 +30,7 @@ class WeaselJsonStatus(enum.Enum):
AGAIN = 1
REJECT = 2
OVERFLOW = 3
NULL = 4
class WeaselJsonCallbacksBase:
@@ -84,13 +85,7 @@ class WeaselJsonParser:
pass
if self._lib is None:
import sys
print(
"Could not find libweaseljson implementation",
file=sys.stderr,
)
sys.exit(1)
raise OSError(f"Could not load libweaseljson from {build_dir}")
self._lib.WeaselJsonParser_create.argtypes = (
ctypes.c_int,