2 Commits
Author SHA1 Message Date
weaselbot 3036063861 ci: run schemagen regression tests in gitea workflow
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 52s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 53s
CI / pre-commit (pull_request) Successful in 51s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m35s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m30s
The new run_tests.sh exercises the example schema and the 40-property
regression schema, so run it after the main build/tests step.
2026-06-18 10:31:27 -04:00
weaselbot 03cf40a690 schemagen: support objects with more than 32 properties
CI / pre-commit (pull_request) Has been cancelled
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Has been cancelled
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Has been cancelled
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Has been cancelled
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Has been cancelled
Replace the 32-bit `uint32_t seen` bitmask with a `std::vector<uint64_t>`
bitset sized to the actual field count.  `requiredMask` is now a vector of
the same word count, and required-field checks use per-word masking so that
optional fields do not cause false rejections.

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

Fixes #3
2026-06-18 10:30:58 -04:00
46 changed files with 122 additions and 2882 deletions
+5
View File
@@ -59,6 +59,11 @@ jobs:
cd build
ctest --output-on-failure -j "$(nproc)" --timeout 90
- name: Run schemagen tests
run: |
cd contrib/schemagen
./run_tests.sh
- name: Package
if: matrix.upload
run: |
-9
View File
@@ -163,15 +163,6 @@ target_link_libraries(mytest PRIVATE ${PROJECT_NAME} doctest nanobench simdjson)
target_compile_options(mytest PRIVATE ${TEST_FLAGS})
doctest_discover_tests(mytest WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
find_package(Python3 COMPONENTS Interpreter)
if(Python3_Interpreter_FOUND)
add_test(NAME python_bindings
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/test_python_bindings.py)
endif()
add_subdirectory(contrib/schemagen)
include(CMakePushCheckState)
include(CheckCXXCompilerFlag)
cmake_push_check_state()
-105
View File
@@ -1,105 +0,0 @@
# Tests for contrib/schemagen
if(NOT Python3_Interpreter_FOUND)
return()
endif()
add_test(
NAME schemagen_python_tests
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_schemagen.py
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
set(SCHEMAGEN_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/weaseljson_schemagen.py)
set(EXAMPLE_SCHEMA ${CMAKE_CURRENT_SOURCE_DIR}/example.schema.json)
set(GEN_H ${CMAKE_CURRENT_BINARY_DIR}/gen.h)
set(BIG_SCHEMA ${CMAKE_CURRENT_SOURCE_DIR}/big.schema.json)
set(BIG_H ${CMAKE_CURRENT_BINARY_DIR}/big.h)
add_custom_command(
OUTPUT ${GEN_H}
COMMAND ${Python3_EXECUTABLE} ${SCHEMAGEN_SCRIPT} ${EXAMPLE_SCHEMA} -o
${GEN_H} --namespace weasel_schema
DEPENDS ${SCHEMAGEN_SCRIPT} ${EXAMPLE_SCHEMA}
COMMENT "Generating gen.h from example.schema.json")
add_custom_command(
OUTPUT ${BIG_H}
COMMAND ${Python3_EXECUTABLE} ${SCHEMAGEN_SCRIPT} ${BIG_SCHEMA} -o ${BIG_H}
--namespace big_schema
DEPENDS ${SCHEMAGEN_SCRIPT} ${BIG_SCHEMA}
COMMENT "Generating big.h from big.schema.json")
add_custom_target(schemagen_gen_h DEPENDS ${GEN_H})
add_custom_target(schemagen_big_h DEPENDS ${BIG_H})
add_executable(schemagen_example ${CMAKE_CURRENT_SOURCE_DIR}/test_gen.cpp)
target_include_directories(schemagen_example
PRIVATE include ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(schemagen_example PRIVATE ${PROJECT_NAME})
target_compile_options(schemagen_example PRIVATE -Wno-switch-enum)
add_dependencies(schemagen_example schemagen_gen_h)
add_executable(schemagen_big ${CMAKE_CURRENT_SOURCE_DIR}/test_big.cpp)
target_include_directories(schemagen_big PRIVATE include
${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(schemagen_big PRIVATE ${PROJECT_NAME})
target_compile_options(schemagen_big PRIVATE -Wno-switch-enum)
add_dependencies(schemagen_big schemagen_big_h)
set(NULLABLE_OBJECT_SCHEMA
${CMAKE_CURRENT_SOURCE_DIR}/nullable_object.schema.json)
set(NULLABLE_STRING_SCHEMA
${CMAKE_CURRENT_SOURCE_DIR}/nullable_string.schema.json)
set(NULLABLE_ARRAY_SCHEMA
${CMAKE_CURRENT_SOURCE_DIR}/nullable_array.schema.json)
set(NULLABLE_OBJECT_H ${CMAKE_CURRENT_BINARY_DIR}/nullable_object.h)
set(NULLABLE_STRING_H ${CMAKE_CURRENT_BINARY_DIR}/nullable_string.h)
set(NULLABLE_ARRAY_H ${CMAKE_CURRENT_BINARY_DIR}/nullable_array.h)
add_custom_command(
OUTPUT ${NULLABLE_OBJECT_H}
COMMAND ${Python3_EXECUTABLE} ${SCHEMAGEN_SCRIPT} ${NULLABLE_OBJECT_SCHEMA} -o
${NULLABLE_OBJECT_H} --namespace nullable_object
DEPENDS ${SCHEMAGEN_SCRIPT} ${NULLABLE_OBJECT_SCHEMA}
COMMENT "Generating nullable_object.h")
add_custom_command(
OUTPUT ${NULLABLE_STRING_H}
COMMAND ${Python3_EXECUTABLE} ${SCHEMAGEN_SCRIPT} ${NULLABLE_STRING_SCHEMA} -o
${NULLABLE_STRING_H} --namespace nullable_string
DEPENDS ${SCHEMAGEN_SCRIPT} ${NULLABLE_STRING_SCHEMA}
COMMENT "Generating nullable_string.h")
add_custom_command(
OUTPUT ${NULLABLE_ARRAY_H}
COMMAND ${Python3_EXECUTABLE} ${SCHEMAGEN_SCRIPT} ${NULLABLE_ARRAY_SCHEMA} -o
${NULLABLE_ARRAY_H} --namespace nullable_array
DEPENDS ${SCHEMAGEN_SCRIPT} ${NULLABLE_ARRAY_SCHEMA}
COMMENT "Generating nullable_array.h")
add_custom_target(
schemagen_nullable_h DEPENDS ${NULLABLE_OBJECT_H} ${NULLABLE_STRING_H}
${NULLABLE_ARRAY_H})
add_executable(schemagen_nullable_root
${CMAKE_CURRENT_SOURCE_DIR}/test_nullable_root.cpp)
target_include_directories(schemagen_nullable_root
PRIVATE include ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(schemagen_nullable_root PRIVATE ${PROJECT_NAME})
target_compile_options(schemagen_nullable_root PRIVATE -Wno-switch-enum)
add_dependencies(schemagen_nullable_root schemagen_nullable_h)
add_test(
NAME schemagen_example
COMMAND schemagen_example
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
add_test(
NAME schemagen_big
COMMAND schemagen_big
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
add_test(
NAME schemagen_nullable_root
COMMAND schemagen_nullable_root
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+12 -23
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` | not supported (rejected at generation) |
| `additionalProperties` absent / `true` | unknown keys' values skipped |
## Schema violations (rejected at parse time)
@@ -55,16 +55,15 @@ 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 in any object
- unknown key under `additionalProperties: false`
## Not supported (rejected at generation time, no fallback)
`oneOf` / `anyOf` / `allOf` / `not` / `if`-`then`-`else`,
`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).
`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).
## Notes
@@ -77,25 +76,15 @@ into the result, so it is non-movable.
## Testing
The schemagen tests are registered with CTest and run as part of the default
`ctest` invocation from the build directory:
```sh
cmake -S . -B build
make -C build -j "$(nproc)"
cd build
ctest --output-on-failure
```
For local development you can also use the convenience script:
After building weaseljson (e.g. `cmake -S . -B build && make -C build`), run:
```sh
cd contrib/schemagen
./run_tests.sh
```
Both regenerate the example parser (`gen.h`) and a regression parser with 40
required properties (`big.h`), compile `test_gen.cpp` and `test_big.cpp`, and run
them. `test_big.cpp` specifically covers issue #3: it checks that a 40-property
object accepts all fields, rejects a missing field at index 32, and rejects
duplicate keys around the 32-bit boundary.
This regenerates the example parser (`gen.h`) and a regression parser with 40
required properties (`big.h`), compiles `test_gen.cpp` and `test_big.cpp`, and
runs both. `test_big.cpp` specifically covers issue #3: it checks that a
40-property object accepts all fields, rejects a missing field at index 32, and
rejects duplicate keys around the 32-bit boundary.
-19
View File
@@ -56,7 +56,6 @@
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"name"
],
@@ -72,24 +71,6 @@
},
"tree": {
"$ref": "#/$defs/Node"
},
"nullable_hobbies": {
"type": "array",
"items": {
"type": [
"string",
"null"
]
}
},
"nullable_scores": {
"type": "array",
"items": {
"type": [
"integer",
"null"
]
}
}
},
"$defs": {
@@ -1,9 +0,0 @@
{
"type": [
"array",
"null"
],
"items": {
"type": "integer"
}
}
@@ -1,15 +0,0 @@
{
"type": [
"object",
"null"
],
"additionalProperties": false,
"required": [
"x"
],
"properties": {
"x": {
"type": "string"
}
}
}
@@ -1,6 +0,0 @@
{
"type": [
"string",
"null"
]
}
+1 -26
View File
@@ -68,15 +68,6 @@ 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 É",
@@ -95,9 +86,7 @@ int main() {
"value": 1,
"children": [ { "value": 2 }, { "value": 3 } ],
"next": { "value": 99 }
},
"nullable_hobbies": [null, "math", null, "lace", null],
"nullable_scores": [null, 10, null, 20, null]
}
})";
RootBuilder b;
WeaselJsonStatus s = parseStrided(b, json);
@@ -127,16 +116,6 @@ int main() {
CHECK(r.tree && r.tree->children && (*r.tree->children)[0].value == 2);
CHECK(r.tree && r.tree->children && (*r.tree->children)[1].value == 3);
CHECK(r.tree && r.tree->next && r.tree->next->value == 99);
CHECK(r.nullable_hobbies.has_value() && r.nullable_hobbies->size() == 5);
CHECK(r.nullable_hobbies && !(*r.nullable_hobbies)[0] &&
(*r.nullable_hobbies)[1] && *(*r.nullable_hobbies)[1] == "math" &&
!(*r.nullable_hobbies)[2] && (*r.nullable_hobbies)[3] &&
*(*r.nullable_hobbies)[3] == "lace" && !(*r.nullable_hobbies)[4]);
CHECK(r.nullable_scores.has_value() && r.nullable_scores->size() == 5);
CHECK(r.nullable_scores && !(*r.nullable_scores)[0] &&
(*r.nullable_scores)[1] && *(*r.nullable_scores)[1] == 10 &&
!(*r.nullable_scores)[2] && (*r.nullable_scores)[3] &&
*(*r.nullable_scores)[3] == 20 && !(*r.nullable_scores)[4]);
printf("ok happy path (byte-strided)\n");
}
}
@@ -193,10 +172,6 @@ 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
@@ -1,198 +0,0 @@
// 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;
}
-864
View File
@@ -1,864 +0,0 @@
#!/usr/bin/env python3
"""Tests for weaseljson_schemagen.py."""
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
import textwrap
import unittest
SCRIPT = os.path.join(os.path.dirname(__file__), "weaseljson_schemagen.py")
class SchemagenEnumTest(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_colliding_enum_values_deduplicate(self):
schema = {
"type": "object",
"additionalProperties": False,
"properties": {"role": {"enum": ["foo-bar", "foo_bar"]}},
}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertEqual(rc, 0, msg=stderr)
self.assertIn("enum class Role : int { foo_bar, foo_bar_1 };", stdout)
self.assertIn(
'static constexpr const char *Role_names[] = { "foo-bar", "foo_bar" };',
stdout,
)
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)
self.assertEqual(rc, 0, msg=stderr)
self.assertIn("enum class Role : int { admin, user, guest };", stdout)
class SchemagenKeywordTest(unittest.TestCase):
def run_schemagen(self, schema, args=None):
"""Run schemagen on a schema dict. Returns (returncode, stdout, stderr)."""
with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fp:
json.dump(schema, fp)
schema_path = fp.name
try:
cmd = [sys.executable, SCRIPT, schema_path]
if args:
cmd.extend(args)
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
return result.returncode, result.stdout, result.stderr
finally:
os.unlink(schema_path)
def test_cpp20_keywords_are_sanitized(self):
"""C++20 keywords used as JSON property names must be suffixed."""
keywords = [
"concept",
"consteval",
"constinit",
"co_await",
"co_return",
"co_yield",
"requires",
"module",
"import",
]
schema = {"type": "object", "additionalProperties": False, "properties": {}}
for kw in keywords:
schema["properties"][kw] = {"type": "string"}
rc, stdout, stderr = self.run_schemagen(schema)
self.assertEqual(rc, 0, msg=stderr)
for kw in keywords:
self.assertIn(f"std::optional<std::string> {kw}_;", stdout)
self.assertNotIn(f"std::optional<std::string> {kw};", stdout)
class SchemagenCollisionTest(unittest.TestCase):
"""Regression tests for issue #21: generated Root alias / Kind enum collisions."""
def setUp(self):
self.repo_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
self.include_dir = os.path.join(self.repo_root, "include")
self.compiler = shutil.which("c++")
def generate_and_compile(self, schema):
"""Run schemagen on schema and syntax-check the resulting header."""
with tempfile.TemporaryDirectory() as tmpdir:
schema_path = os.path.join(tmpdir, "schema.json")
with open(schema_path, "w") as fp:
json.dump(schema, fp)
header_path = os.path.join(tmpdir, "gen.h")
cmd = [
sys.executable,
SCRIPT,
schema_path,
"-o",
header_path,
"--namespace",
"test_schema",
]
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
self.assertEqual(result.returncode, 0, msg=result.stderr)
if self.compiler:
cpp_path = os.path.join(tmpdir, "test.cpp")
with open(cpp_path, "w") as fp:
fp.write(
'#include "gen.h"\n'
"int main() {\n"
" test_schema::RootBuilder b;\n"
" test_schema::Root r = b.take();\n"
" (void)r;\n"
"}\n"
)
comp = subprocess.run(
[
self.compiler,
"-std=c++20",
"-fsyntax-only",
"-I",
self.include_dir,
"-I",
tmpdir,
cpp_path,
],
capture_output=True,
text=True,
check=False,
)
self.assertEqual(comp.returncode, 0, msg=comp.stderr)
with open(header_path) as fp:
return fp.read()
def test_root_alias_does_not_collide_with_user_type(self):
schema = {
"type": "array",
"items": {"$ref": "#/$defs/Root"},
"$defs": {"Root": {"enum": ["a", "b"]}},
}
out = self.generate_and_compile(schema)
self.assertIn("enum class Root1 : int { a, b };", out)
self.assertIn("using Root = std::vector<Root1>;", out)
self.assertNotIn("using Root = std::vector<Root>;", out)
def test_kind_enum_does_not_duplicate_arr0(self):
schema = {
"type": "object",
"additionalProperties": False,
"properties": {
"arr": {"type": "array", "items": {"type": "string"}},
"obj": {"$ref": "#/$defs/Arr0"},
},
"$defs": {
"Arr0": {
"type": "object",
"additionalProperties": False,
"properties": {},
}
},
}
out = self.generate_and_compile(schema)
self.assertIn("struct Arr0", out)
m = re.search(r"enum class Kind : uint8_t \{([^}]+)\}", out)
self.assertIsNotNone(m)
enumerators = [e.strip() for e in m.group(1).split(",")]
self.assertIn("Arr0", enumerators)
self.assertIn("Arr1", enumerators)
self.assertEqual(len(enumerators), len(set(enumerators)))
def test_skip_user_type_is_allowed(self):
schema = {
"type": "array",
"items": {"$ref": "#/$defs/Skip"},
"$defs": {
"Skip": {
"type": "object",
"additionalProperties": False,
"properties": {},
}
},
}
out = self.generate_and_compile(schema)
self.assertIn("struct Skip", out)
self.assertNotIn("struct Skip1", out)
m = re.search(r"enum class Kind : uint8_t \{([^}]+)\}", out)
self.assertIsNotNone(m)
enumerators = [e.strip() for e in m.group(1).split(",")]
self.assertIn("Skip", enumerators)
self.assertIn("Arr0", enumerators)
self.assertEqual(len(enumerators), len(set(enumerators)))
def test_non_object_defs_reused_across_refs(self):
"""Regression test for issue #17: enum and array $defs must be reused."""
schema = {
"type": "object",
"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()
+81 -329
View File
@@ -18,43 +18,6 @@ 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
@@ -97,6 +60,7 @@ class ObjectType:
def __init__(self, name):
self.name = name
self.fields = [] # list[Field]
self.strict = False # additionalProperties: false
class EnumType:
@@ -122,34 +86,6 @@ def sanitize(name, fallback="x"):
return s
def unique_enum_identifiers(values):
"""Return a list of sanitized C++ identifiers, one per input value.
Distinct JSON enum values may sanitize to the same C++ token (e.g.
"foo-bar" and "foo_bar" both become "foo_bar"). This helper appends a
numeric suffix to later collisions so the generated `enum class` stays
valid while preserving the original order and therefore the index-to-value
mapping used at parse time.
"""
used = set()
out = []
for v in values:
base = sanitize(v)
if base not in used:
used.add(base)
out.append(base)
continue
n = 1
while True:
candidate = f"{base}_{n}"
if candidate not in used:
used.add(candidate)
out.append(candidate)
break
n += 1
return out
def camel(name):
parts = [p for p in name.replace("-", " ").replace("_", " ").split(" ") if p]
if not parts:
@@ -169,15 +105,9 @@ _CPP_KEYWORDS = {
"catch",
"char",
"class",
"concept",
"const",
"consteval",
"constinit",
"constexpr",
"continue",
"co_await",
"co_return",
"co_yield",
"decltype",
"default",
"delete",
@@ -194,11 +124,9 @@ _CPP_KEYWORDS = {
"friend",
"goto",
"if",
"import",
"inline",
"int",
"long",
"module",
"namespace",
"new",
"not",
@@ -210,7 +138,6 @@ _CPP_KEYWORDS = {
"public",
"register",
"return",
"requires",
"short",
"signed",
"sizeof",
@@ -252,44 +179,12 @@ class Builder:
base = camel(hint)
name = base
i = 1
while self._name_taken(name):
candidate = f"{base}{i}"
# A numeric suffix can itself land on a reserved generated name
# (e.g. hint "Arr0" -> "Arr01"). Use an underscore separator so
# we never loop through the reserved block.
if self._is_reserved(candidate):
candidate = f"{base}_{i}"
name = candidate
while name in self._used_names:
i += 1
name = f"{base}{i}"
self._used_names.add(name)
return name
def _name_taken(self, name):
return name in self._used_names or self._is_reserved(name)
@staticmethod
def _is_reserved(name):
"""Names generated internally that must not collide with user types."""
return name in ("Root", "RootScalar")
@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("#/"):
raise GenError(f"only local $ref supported, got: {ref}")
@@ -305,6 +200,7 @@ 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):
@@ -333,10 +229,6 @@ 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")
@@ -355,35 +247,18 @@ 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))
result = (TEnum(name), nullable)
if defname is not None:
self._building[defname] = result
return result
return (TEnum(name), nullable)
if typ == "object" or (typ is None and "properties" in node):
return (self._build_object(node, hint, defname, nullable), nullable)
return (self._build_object(node, hint, defname), 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")
)
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
return (TArr(elem, elem_nullable), nullable)
scalar = {
"string": "str",
@@ -392,10 +267,7 @@ class Builder:
"boolean": "bool",
}.get(typ)
if scalar:
result = (TScalar(scalar), nullable)
if defname is not None:
self._building[defname] = result
return result
return (TScalar(scalar), nullable)
if typ == "null":
raise GenError("'null'-only types are not supported")
@@ -409,27 +281,19 @@ class Builder:
return result
return (result, False)
def _build_object(self, node, hint, defname, nullable=False):
def _build_object(self, node, hint, defname):
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, 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")
self._building[defname] = TObj(name)
ap = node.get("additionalProperties", True)
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()
@@ -443,7 +307,7 @@ class Builder:
cpp = f"{base}{i}"
seen_cpp.add(cpp)
obj.fields.append(Field(key, cpp, ty, key in required, nullable))
return tobj
return TObj(name)
# ---------------------------------------------------------------------------
@@ -534,7 +398,6 @@ class Emitter:
self.kind_order = [] # all Kind enumerators in declaration order
self.root_ty = None
self.root_nullable = False
self._arr_counter = 0
# -- type strings -------------------------------------------------------
def base_cpp(self, ty):
@@ -565,19 +428,11 @@ class Emitter:
def arr_kind(self, tarr):
sig = self.base_cpp(tarr)
if sig not in self.arr_kinds:
name = self._fresh_arr_kind_name()
name = f"Arr{len(self.arr_kinds)}"
self.arr_kinds[sig] = name
self.arr_types.append((name, tarr))
self.b._used_names.add(name)
return self.arr_kinds[sig]
def _fresh_arr_kind_name(self):
while True:
name = f"Arr{self._arr_counter}"
self._arr_counter += 1
if not self.b._name_taken(name):
return name
def cat(self, ty):
if isinstance(ty, TScalar):
return {"str": "Str", "int": "Int", "dbl": "Dbl", "bool": "Bool"}[ty.kind]
@@ -601,18 +456,6 @@ 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)
@@ -665,7 +508,7 @@ namespace {ns} {{"""
return ""
out = []
for e in self.b.enums.values():
vals = ", ".join(unique_enum_identifiers(e.values))
vals = ", ".join(sanitize(v) for v in e.values)
out.append(f"enum class {e.name} : int {{ {vals} }};")
return "\n".join(out) + "\n"
@@ -685,8 +528,7 @@ namespace {ns} {{"""
and f.ty.kind in ("int", "dbl", "bool")
):
init = " = 0" if f.ty.kind != "bool" else " = false"
esc_key = _escape_cpp_string(f.key)
out.append(f' {store} {f.cpp}{init}; // "{esc_key}"')
out.append(f' {store} {f.cpp}{init}; // "{f.key}"')
out.append("};")
out.append("")
return "\n".join(out)
@@ -703,17 +545,20 @@ 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))
root_is_container = (
isinstance(self.root_ty, (TObj, TArr)) and not self.root_nullable
)
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):
if isinstance(self.root_ty, TObj) and not self.root_nullable:
return ("Obj", f"Kind::{self.root_ty.name}")
if isinstance(self.root_ty, TArr):
if isinstance(self.root_ty, TArr) and not self.root_nullable:
return ("Arr", f"Kind::{self.arr_kind(self.root_ty)}")
return (self.cat(self.root_ty), None)
@@ -783,12 +628,7 @@ 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(" case Kind::RootScalar: return &result_;")
lines.append(" default: return nullptr;")
lines.append(" }")
lines.append(" }")
@@ -814,7 +654,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 = _escape_cpp_string(fld.key)
esc = fld.key.replace("\\", "\\\\").replace('"', '\\"')
lines.append(f' if (key == "{esc}") return {i};')
lines.append(" return -1;")
lines.append(" default: return -1;")
@@ -866,38 +706,24 @@ namespace {ns} {{"""
" }"
)
def _is_array_kind(self):
arrs = [n for n, _ in self.arr_types]
if not arrs:
return " bool isArrayKind(Kind) const { return false; }"
cases = " ".join(f"case Kind::{n}:" for n in arrs)
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 isArrayKind(Kind k) const {\n"
" bool isStrict(Kind k) const {\n"
f" switch (k) {{ {cases} return true; default: return false; }}\n"
" }"
)
def _append_null(self):
lines = [
" void appendNull(Frame &f) {",
" switch (f.kind) {",
]
for name, tarr in self.arr_types:
vectype = self.base_cpp(tarr)
lines.append(f" case Kind::{name}: {{")
lines.append(f" auto *v = ({vectype} *)f.dest;")
lines.append(" v->emplace_back();")
lines.append(" return;")
lines.append(" }")
lines.append(" default: return;")
lines.append(" }")
lines.append(" }")
return "\n".join(lines)
def _enum_name_arrays(self):
out = []
for e in self.b.enums.values():
lits = ", ".join('"' + _escape_cpp_string(v) + '"' for v in e.values)
lits = ", ".join(
'"' + v.replace("\\", "\\\\").replace('"', '\\"') + '"'
for v in e.values
)
out.append(
f" static constexpr const char *{e.name}_names[] = {{ {lits} }};"
)
@@ -916,12 +742,7 @@ namespace {ns} {{"""
lines = [" if (error_) return;"]
if root_kind is not None and root_cat == event_cat:
lines.append(" if (stack_.empty()) {")
if self.root_nullable:
lines.append(" result_.emplace();")
dest = "&*result_"
else:
dest = "&result_"
lines.append(f" stack_.push_back(Frame{{{root_kind}, {dest}}});")
lines.append(f" stack_.push_back(Frame{{{root_kind}, &result_}});")
if event_cat == "Obj":
lines.append(" {")
lines.append(f" int n = fieldCount({root_kind});")
@@ -932,7 +753,13 @@ 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});")
@@ -947,9 +774,6 @@ 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 {{
@@ -957,10 +781,6 @@ 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_); }}
@@ -987,7 +807,7 @@ public:
private:
{kind_enum}
enum class Cat {{ Reject, Str, Int, Dbl, Bool, Enum, Obj, Arr }};
enum class Cat {{ Reject, Str, Int, Dbl, Bool, Enum, Obj, Arr, Skip }};
struct SlotInfo {{
Cat cat;
Kind child{{}};
@@ -998,10 +818,11 @@ private:
struct Frame {{
Kind kind;
void *dest;
int field = -1; // object: selected field (-1 means waiting for key)
int field = -1; // object: selected field (-1 want key, -2 skip)
std::vector<uint64_t> seen; // populated field bitset (object frames)
}};
static constexpr int kWantKey = -1;
static constexpr int kSkip = -2;
{self._enum_name_arrays()}
@@ -1016,95 +837,10 @@ private:
void reject() {{ error_ = true; }}
// 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.
// Wrap the generated slotInfo() with the generic key/skip states.
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);
@@ -1129,6 +865,11 @@ 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;
@@ -1146,6 +887,11 @@ 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();
}}
@@ -1157,10 +903,12 @@ private:
scratch_.append(buf, len);
if (!done) return;
int idx = matchKey(f.kind, scratch_);
if (idx < 0) {{
reject(); return; // unknown key
}}
scratch_.clear();
if (idx < 0) {{
if (isStrict(f.kind)) {{ reject(); return; }}
f.field = kSkip;
return;
}}
if (f.seen[idx >> 6] & (1ull << (idx & 63))) {{
reject(); return; // duplicate key
}}
@@ -1169,9 +917,10 @@ 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);
@@ -1196,9 +945,10 @@ 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;
@@ -1213,11 +963,16 @@ 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
// 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;
// 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;
}}
}} else {{
double v = 0;
@@ -1230,27 +985,22 @@ 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();
}}
{self._is_array_kind()}
{self._append_null()}
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
}}
@@ -1286,6 +1036,8 @@ private:
{self._reqmask()}
{self._is_object_kind()}
{self._is_strict()}
}};
"""
@@ -1 +0,0 @@
"d\ud82d\udd9c8\ud8ed
@@ -1,43 +0,0 @@
[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,
@@ -1 +0,0 @@
[[[{ "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
@@ -1 +0,0 @@
["\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
@@ -1,27 +0,0 @@
[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,[ "\
@@ -1,56 +0,0 @@
[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[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
@@ -1,54 +0,0 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[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]]]
@@ -1,54 +0,0 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[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,
@@ -1 +0,0 @@
"\uDBFF\zFEFF
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1,55 +0,0 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[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,
@@ -1 +0,0 @@
{"title":"\u041e\ud41d41d\ud82d\udd29\4d9cu0430 \u;a 4 \u03
@@ -1,71 +0,0 @@
[[[{},{}, [[[[[[{},{}, [[[{},{},[[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
@@ -1,160 +0,0 @@
[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[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
@@ -1,54 +0,0 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[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",:]
@@ -1,62 +0,0 @@
{";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
@@ -1 +0,0 @@
"\uDA8D\tD]{DDD
@@ -1,42 +0,0 @@
[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
@@ -1,71 +0,0 @@
[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[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,
@@ -1 +0,0 @@
{"":"_________..............................._____________@@@@ffa\udafa\udffa2fa[-401.
@@ -1,101 +0,0 @@
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[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
@@ -1 +0,0 @@
"de39\ud83dLudœ8
@@ -1,62 +0,0 @@
[[[{},{}, [[[{},{},[[[[{},{}, [[[{},{},[[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{},[-[
+1 -4
View File
@@ -38,8 +38,6 @@ 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;
@@ -66,8 +64,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. Returns WeaselJson_NULL if parser is null */
* `len` is 0 */
WeaselJsonStatus WeaselJsonParser_parse(WeaselJsonParser *parser, char *buf,
int len);
-1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
-9
View File
@@ -29,26 +29,17 @@ 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);
}
}
+4 -34
View File
@@ -138,13 +138,6 @@ struct Parser3 {
void reset() {
stackPtr = stack();
std::ignore = push({N_VALUE, N_WHITESPACE, T_EOF});
inKey = false;
terminalStatus = WeaselJson_OK;
utf8Codepoint = 0;
utf16Surrogate = 0;
minCodepoint = 0;
numDfa.reset();
strDfa.reset();
}
// Used for flushing pending data with on_*_data callbacks
@@ -162,7 +155,6 @@ struct Parser3 {
NumDfa numDfa;
Utf8Dfa strDfa;
bool inKey = false;
WeaselJsonStatus terminalStatus = WeaselJson_OK;
#ifndef HAS_MUSTTAIL
char *stashBufForTrampoline;
@@ -625,7 +617,7 @@ inline PRESERVE_NONE ContinuationStatus n_string2(Parser3 *self, char *buf,
return WeaselJson_REJECT;
}
buf += 4;
if (0xd800 <= codepoint && codepoint <= 0xdbff) {
if (0xd800 <= codepoint && codepoint <= 0xdfff) {
// utf-16 surrogate
int32_t codepoint2 = read4_hex(buf + 2);
if (!(buf[0] == '\\' && buf[1] == 'u' && 0xdc00 <= codepoint2 &&
@@ -650,8 +642,6 @@ 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) {
@@ -795,7 +785,7 @@ inline PRESERVE_NONE ContinuationStatus t_hex2(Parser3 *self, char *buf,
}
} else {
assert(self->utf8Codepoint < 0x10000);
if (0xd800 <= self->utf8Codepoint && self->utf8Codepoint <= 0xdbff) {
if (0xd800 <= self->utf8Codepoint && self->utf8Codepoint <= 0xdfff) {
// utf-16 surrogate
self->utf16Surrogate = self->utf8Codepoint;
self->utf8Codepoint = 0;
@@ -810,10 +800,6 @@ 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;
@@ -1070,34 +1056,18 @@ constexpr inline struct ContinuationTable {
inline WeaselJsonStatus Parser3::parse(char *buf, int len) {
this->dataBegin = this->writeBuf = buf;
if (this->terminalStatus != WeaselJson_OK) [[unlikely]] {
return this->terminalStatus;
}
if (len < 0) [[unlikely]] {
this->terminalStatus = WeaselJson_REJECT;
return WeaselJson_REJECT;
}
#ifdef HAS_MUSTTAIL
// The continuation returns a value in 0..3 here (kBounce is only used by the
// no-musttail trampoline below), so the conversion back to the enum is in
// range.
ContinuationStatus status =
symbolTables.continuations[top()](this, buf, buf + len);
if (status > WeaselJson_AGAIN) {
this->terminalStatus = WeaselJsonStatus(status);
}
return WeaselJsonStatus(status);
return WeaselJsonStatus(
symbolTables.continuations[top()](this, buf, buf + len));
#else
this->stashBufForTrampoline = buf;
ContinuationStatus result;
while ((result = symbolTables.continuations[top()](
this, stashBufForTrampoline, buf + len)) == kBounce)
;
if (result > WeaselJson_AGAIN) {
this->terminalStatus = WeaselJsonStatus(result);
}
return WeaselJsonStatus(result);
#endif
}
-154
View File
@@ -202,87 +202,6 @@ 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);
REQUIRE(parser != nullptr);
// Feed a valid prefix, then an invalid byte. The parser must reject.
std::string chunk1 = "1";
REQUIRE(WeaselJsonParser_parse(parser, chunk1.data(), chunk1.size()) ==
WeaselJson_AGAIN);
std::string chunk2 = "x";
REQUIRE(WeaselJsonParser_parse(parser, chunk2.data(), chunk2.size()) ==
WeaselJson_REJECT);
// After a reject, every later call must also reject, including the EOF
// finish call that would otherwise return OK.
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) == WeaselJson_REJECT);
// Further data chunks must also stay rejected.
std::string chunk3 = " ";
REQUIRE(WeaselJsonParser_parse(parser, chunk3.data(), chunk3.size()) ==
WeaselJson_REJECT);
WeaselJsonParser_destroy(parser);
}
TEST_CASE("create rejects too-small stack") {
auto c = noopCallbacks();
// The parser needs room for the bootstrap symbols pushed by reset(); a stack
@@ -302,81 +221,8 @@ 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);
REQUIRE(parser != nullptr);
// A negative length must not cause pointer arithmetic UB. It should be
// rejected, and the rejected state should remain sticky.
char buf[10] = "hello";
REQUIRE(WeaselJsonParser_parse(parser, buf, -1) == WeaselJson_REJECT);
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) == WeaselJson_REJECT);
WeaselJsonParser_destroy(parser);
}
TEST_CASE("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") {
struct State {
std::string stringData;
std::string keyData;
} state;
auto c = noopCallbacks();
c.on_string_data = +[](void *p, const char *buf, int len, int /*done*/) {
((State *)p)->stringData.append(buf, len);
};
c.on_key_data = +[](void *p, const char *buf, int len, int /*done*/) {
((State *)p)->keyData.append(buf, len);
};
auto *parser = WeaselJsonParser_create(1024, &c, &state, 0);
REQUIRE(parser != nullptr);
{
std::string chunk = "{\"ab";
REQUIRE(WeaselJsonParser_parse(parser, chunk.data(), chunk.size()) ==
WeaselJson_AGAIN);
}
// Reset mid-key: the next top-level string must be delivered as a string,
// not appended to the aborted key.
WeaselJsonParser_reset(parser);
state.stringData.clear();
state.keyData.clear();
{
std::string chunk = "\"hello\"";
REQUIRE(WeaselJsonParser_parse(parser, chunk.data(), chunk.size()) ==
WeaselJson_AGAIN);
}
REQUIRE(WeaselJsonParser_parse(parser, nullptr, 0) == WeaselJson_OK);
CHECK(state.stringData == "hello");
CHECK(state.keyData.empty());
WeaselJsonParser_destroy(parser);
}
void doTestUnescapingUtf8(std::string const &escaped,
std::string const &expected, int stride, int flags) {
CAPTURE(escaped);
-3
View File
@@ -33,9 +33,6 @@ 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;
-114
View File
@@ -1,114 +0,0 @@
import json
import weaseljson
class Recorder(weaseljson.WeaselJsonCallbacksBase):
def __init__(self):
self.keys = []
self.strings = []
self.numbers = []
self.events = []
self._current = bytearray()
def _flush(self, target, data, done):
self._current.extend(data)
if done:
target.append(bytes(self._current))
self._current = bytearray()
def on_begin_object(self):
self.events.append("begin_object")
def on_end_object(self):
self.events.append("end_object")
def on_begin_array(self):
self.events.append("begin_array")
def on_end_array(self):
self.events.append("end_array")
def on_key_data(self, data, done):
self._flush(self.keys, data, done)
def on_string_data(self, data, done):
self._flush(self.strings, data, done)
def on_number_data(self, data, done):
self._flush(self.numbers, data, done)
def on_true_literal(self):
self.events.append("true")
def on_false_literal(self):
self.events.append("false")
def on_null_literal(self):
self.events.append("null")
def parse_all(parser, data):
for i in range(len(data)):
status = parser.parse(data[i : i + 1])
if status != weaseljson.WeaselJsonStatus.AGAIN:
return status
return parser.parse(b"")
def test_object_keys_routed_correctly():
recorder = Recorder()
with weaseljson.WeaselJsonParser(recorder) as parser:
status = parse_all(
parser, json.dumps({"hello": "world", "foo": "bar"}).encode()
)
assert status == weaseljson.WeaselJsonStatus.OK, status
assert recorder.keys == [b"hello", b"foo"], recorder.keys
assert recorder.strings == [b"world", b"bar"], recorder.strings
def test_mixed_values():
recorder = Recorder()
with weaseljson.WeaselJsonParser(recorder) as parser:
status = parse_all(
parser,
json.dumps({"answer": 42, "yes": True, "no": False, "nil": None}).encode(),
)
assert status == weaseljson.WeaselJsonStatus.OK, status
assert recorder.keys == [b"answer", b"yes", b"no", b"nil"], recorder.keys
assert recorder.numbers == [b"42"], recorder.numbers
assert recorder.events.count("true") == 1
assert recorder.events.count("false") == 1
assert recorder.events.count("null") == 1
def test_create_rejects_too_small_stack():
for stack_size in (-1, 0, 1, 2):
try:
parser = weaseljson.WeaselJsonParser(Recorder(), stackSize=stack_size)
except ValueError:
continue
# If creation unexpectedly succeeds, close it cleanly and fail the test.
parser.close()
raise AssertionError(f"expected ValueError for stackSize={stack_size}")
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")
+18 -37
View File
@@ -15,7 +15,6 @@ class WeaselJsonCallbacks(ctypes.Structure):
("on_begin_object", event_callback),
("on_end_object", event_callback),
("on_string_data", data_callback),
("on_key_data", data_callback),
("on_begin_array", event_callback),
("on_end_array", event_callback),
("on_number_data", data_callback),
@@ -30,7 +29,6 @@ class WeaselJsonStatus(enum.Enum):
AGAIN = 1
REJECT = 2
OVERFLOW = 3
NULL = 4
class WeaselJsonCallbacksBase:
@@ -43,9 +41,6 @@ class WeaselJsonCallbacksBase:
def on_string_data(self, data, done):
pass
def on_key_data(self, data, done):
pass
def on_begin_array(self):
pass
@@ -85,7 +80,13 @@ class WeaselJsonParser:
pass
if self._lib is None:
raise OSError(f"Could not load libweaseljson from {build_dir}")
import sys
print(
"Could not find libweaseljson implementation",
file=sys.stderr,
)
sys.exit(1)
self._lib.WeaselJsonParser_create.argtypes = (
ctypes.c_int,
@@ -112,24 +113,12 @@ class WeaselJsonParser:
self.voidp_callbacks,
0,
)
if self.p is None:
raise ValueError(
"WeaselJsonParser_create returned NULL; "
"check stackSize (must be positive and large enough) "
"and available memory"
)
def _check_open(self):
if self.p is None:
raise RuntimeError("parser has been closed or creation failed")
def parse(self, data: bytes) -> WeaselJsonStatus:
self._check_open()
buf = (ctypes.c_ubyte * len(data)).from_buffer(bytearray(data))
return self._lib.WeaselJsonParser_parse(self.p, buf, len(data))
def reset(self):
self._check_open()
self._lib.WeaselJsonParser_reset(self.p)
def __enter__(self):
@@ -162,12 +151,6 @@ def on_string_data(p, buf, len, done):
self.on_string_data(bytes(ctypes.string_at(buf, len)), bool(done))
@ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, ctypes.c_int)
def on_key_data(p, buf, len, done):
self = ctypes.cast(p, ctypes.POINTER(ctypes.py_object)).contents.value
self.on_key_data(bytes(ctypes.string_at(buf, len)), bool(done))
@ctypes.CFUNCTYPE(None, ctypes.c_void_p)
def on_begin_array(p):
self = ctypes.cast(p, ctypes.POINTER(ctypes.py_object)).contents.value
@@ -208,7 +191,6 @@ c_callbacks = WeaselJsonCallbacks(
on_begin_object,
on_end_object,
on_string_data,
on_key_data,
on_begin_array,
on_end_array,
on_number_data,
@@ -224,15 +206,14 @@ class MyCallbacks(WeaselJsonCallbacksBase):
print(data)
if __name__ == "__main__":
with WeaselJsonParser(MyCallbacks()) as parser:
raw = json.dumps({"hello": "world", "foo": 42}).encode()
i = 0
stride = 1
while True:
slice = raw[i : i + stride]
s = parser.parse(slice)
if s != WeaselJsonStatus.AGAIN:
break
i += stride
print(s)
with WeaselJsonParser(MyCallbacks()) as parser:
raw = json.dumps({"hello": "world", "foo": 42}).encode()
i = 0
stride = 1
while True:
slice = raw[i : i + stride]
s = parser.parse(slice)
if s != WeaselJsonStatus.AGAIN:
break
i += stride
print(s)