mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Compare commits
13 Commits
lemire/features
...
filc
| Author | SHA1 | Date | |
|---|---|---|---|
| b2d39e9cda | |||
| 7662d7e57f | |||
| fad60f696a | |||
| 0507b42cb1 | |||
| b727c02950 | |||
| 47f28b88d6 | |||
| 486b2a3828 | |||
| 22773f3c70 | |||
| 249eb2c28a | |||
| 94c429aa70 | |||
| 72e51a9a81 | |||
| 1a57afec1f | |||
| 4de7426b9f |
@@ -15,6 +15,7 @@ if (TARGET benchmark::benchmark)
|
|||||||
link_libraries(benchmark::benchmark)
|
link_libraries(benchmark::benchmark)
|
||||||
add_executable(bench_parse_call bench_parse_call.cpp)
|
add_executable(bench_parse_call bench_parse_call.cpp)
|
||||||
add_executable(bench_dom_api bench_dom_api.cpp)
|
add_executable(bench_dom_api bench_dom_api.cpp)
|
||||||
|
add_executable(bench_stream_formats bench_stream_formats.cpp)
|
||||||
if(SIMDJSON_EXCEPTIONS)
|
if(SIMDJSON_EXCEPTIONS)
|
||||||
add_executable(bench_ondemand bench_ondemand.cpp)
|
add_executable(bench_ondemand bench_ondemand.cpp)
|
||||||
if(TARGET yyjson)
|
if(TARGET yyjson)
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ SIMDJSON_POP_DISABLE_WARNINGS
|
|||||||
#include "kostya/boostjson.h"
|
#include "kostya/boostjson.h"
|
||||||
|
|
||||||
#include "large_random/simdjson_ondemand.h"
|
#include "large_random/simdjson_ondemand.h"
|
||||||
|
#include "large_random/simdjson_ondemand_ranges.h"
|
||||||
#if SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
|
#if SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
|
||||||
#include "large_random/simdjson_ondemand_unordered.h"
|
#include "large_random/simdjson_ondemand_unordered.h"
|
||||||
#endif // SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
|
#endif // SIMDJSON_COMPETITION_ONDEMAND_UNORDERED
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
#include <benchmark/benchmark.h>
|
||||||
|
#include <string>
|
||||||
|
#include "simdjson.h"
|
||||||
|
|
||||||
|
using namespace simdjson;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum class stream_case {
|
||||||
|
ndjson_small,
|
||||||
|
ndjson_large,
|
||||||
|
rfc7464_small,
|
||||||
|
rfc7464_large,
|
||||||
|
comma_delimited_small,
|
||||||
|
comma_delimited_large
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr size_t TARGET_BYTES = 128 * 1000 * 1000;
|
||||||
|
constexpr size_t SMALL_PAYLOAD = 16;
|
||||||
|
constexpr size_t LARGE_PAYLOAD = 4096;
|
||||||
|
constexpr size_t BATCH_SIZE = 1 << 20;
|
||||||
|
|
||||||
|
struct stream_dataset {
|
||||||
|
padded_string json;
|
||||||
|
size_t count{};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string make_document(size_t id, size_t payload_size) {
|
||||||
|
return std::string{"{\"id\":"} + std::to_string(id) +
|
||||||
|
",\"name\":\"aaaaaaaa\",\"payload\":\"" +
|
||||||
|
std::string(payload_size, 'x') + "\",\"flag\":true}";
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_dataset build_dataset(stream_case which) {
|
||||||
|
const bool small = which == stream_case::ndjson_small ||
|
||||||
|
which == stream_case::rfc7464_small ||
|
||||||
|
which == stream_case::comma_delimited_small;
|
||||||
|
const bool rfc = which == stream_case::rfc7464_small ||
|
||||||
|
which == stream_case::rfc7464_large;
|
||||||
|
const bool comma = which == stream_case::comma_delimited_small ||
|
||||||
|
which == stream_case::comma_delimited_large;
|
||||||
|
const size_t payload_size = small ? SMALL_PAYLOAD : LARGE_PAYLOAD;
|
||||||
|
const size_t count = TARGET_BYTES / (payload_size + 48);
|
||||||
|
std::string out;
|
||||||
|
out.reserve(count * (payload_size + 64));
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
if (rfc) {
|
||||||
|
out += char(0x1E);
|
||||||
|
}
|
||||||
|
if (comma && i > 0) {
|
||||||
|
out += ',';
|
||||||
|
}
|
||||||
|
out += make_document(i, payload_size);
|
||||||
|
if (!comma) {
|
||||||
|
out += '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {padded_string(out), count};
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream_dataset &get_dataset(stream_case which) {
|
||||||
|
static const stream_dataset ndjson_small =
|
||||||
|
build_dataset(stream_case::ndjson_small);
|
||||||
|
static const stream_dataset ndjson_large =
|
||||||
|
build_dataset(stream_case::ndjson_large);
|
||||||
|
static const stream_dataset rfc_small =
|
||||||
|
build_dataset(stream_case::rfc7464_small);
|
||||||
|
static const stream_dataset rfc_large =
|
||||||
|
build_dataset(stream_case::rfc7464_large);
|
||||||
|
static const stream_dataset comma_small =
|
||||||
|
build_dataset(stream_case::comma_delimited_small);
|
||||||
|
static const stream_dataset comma_large =
|
||||||
|
build_dataset(stream_case::comma_delimited_large);
|
||||||
|
switch (which) {
|
||||||
|
case stream_case::ndjson_small:
|
||||||
|
return ndjson_small;
|
||||||
|
case stream_case::ndjson_large:
|
||||||
|
return ndjson_large;
|
||||||
|
case stream_case::rfc7464_small:
|
||||||
|
return rfc_small;
|
||||||
|
case stream_case::rfc7464_large:
|
||||||
|
return rfc_large;
|
||||||
|
case stream_case::comma_delimited_small:
|
||||||
|
return comma_small;
|
||||||
|
case stream_case::comma_delimited_large:
|
||||||
|
return comma_large;
|
||||||
|
}
|
||||||
|
return ndjson_small;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_counters(benchmark::State &state, const stream_dataset &dataset) {
|
||||||
|
state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(dataset.json.size()));
|
||||||
|
state.SetItemsProcessed(int64_t(state.iterations()) * int64_t(dataset.count));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <stream_case which, bool threaded = true>
|
||||||
|
static void bench_ondemand(benchmark::State &state) {
|
||||||
|
const auto &dataset = get_dataset(which);
|
||||||
|
ondemand::parser parser;
|
||||||
|
parser.threaded = threaded;
|
||||||
|
stream_format format = stream_format::whitespace_delimited;
|
||||||
|
if constexpr (which == stream_case::rfc7464_small ||
|
||||||
|
which == stream_case::rfc7464_large) {
|
||||||
|
format = stream_format::json_sequence;
|
||||||
|
} else if constexpr (which == stream_case::comma_delimited_small ||
|
||||||
|
which == stream_case::comma_delimited_large) {
|
||||||
|
format = stream_format::comma_delimited;
|
||||||
|
}
|
||||||
|
for (const auto _ : state) {
|
||||||
|
ondemand::document_stream docs;
|
||||||
|
auto error = parser.iterate_many(dataset.json, BATCH_SIZE, format).get(docs);
|
||||||
|
if (error) {
|
||||||
|
state.SkipWithError(error_message(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t sum = 0;
|
||||||
|
for (auto doc : docs) {
|
||||||
|
ondemand::object obj;
|
||||||
|
if ((error = doc.get_object().get(obj))) {
|
||||||
|
state.SkipWithError(error_message(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t id;
|
||||||
|
if ((error = obj["id"].get_uint64().get(id))) {
|
||||||
|
state.SkipWithError(error_message(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sum += id;
|
||||||
|
}
|
||||||
|
benchmark::DoNotOptimize(sum);
|
||||||
|
}
|
||||||
|
set_counters(state, dataset);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <stream_case which>
|
||||||
|
static void bench_dom(benchmark::State &state) {
|
||||||
|
const auto &dataset = get_dataset(which);
|
||||||
|
dom::parser parser;
|
||||||
|
parser.threaded = true;
|
||||||
|
stream_format format = stream_format::whitespace_delimited;
|
||||||
|
if constexpr (which == stream_case::rfc7464_small ||
|
||||||
|
which == stream_case::rfc7464_large) {
|
||||||
|
format = stream_format::json_sequence;
|
||||||
|
} else if constexpr (which == stream_case::comma_delimited_small ||
|
||||||
|
which == stream_case::comma_delimited_large) {
|
||||||
|
format = stream_format::comma_delimited;
|
||||||
|
}
|
||||||
|
for (const auto _ : state) {
|
||||||
|
dom::document_stream docs;
|
||||||
|
auto error = parser.parse_many(dataset.json, BATCH_SIZE, format).get(docs);
|
||||||
|
if (error) {
|
||||||
|
state.SkipWithError(error_message(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t sum = 0;
|
||||||
|
for (auto doc : docs) {
|
||||||
|
uint64_t id;
|
||||||
|
if ((error = doc["id"].get(id))) {
|
||||||
|
state.SkipWithError(error_message(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sum += id;
|
||||||
|
}
|
||||||
|
benchmark::DoNotOptimize(sum);
|
||||||
|
}
|
||||||
|
set_counters(state, dataset);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
BENCHMARK(bench_ondemand<stream_case::ndjson_small>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_ondemand<stream_case::ndjson_large>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_ondemand<stream_case::rfc7464_small>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_ondemand<stream_case::rfc7464_large>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_ondemand<stream_case::comma_delimited_small>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_ondemand<stream_case::comma_delimited_large>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
// Non-threaded comma_delimited for comparison
|
||||||
|
BENCHMARK(bench_ondemand<stream_case::comma_delimited_small, false>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_ondemand<stream_case::comma_delimited_large, false>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
|
||||||
|
BENCHMARK(bench_dom<stream_case::ndjson_small>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_dom<stream_case::ndjson_large>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_dom<stream_case::rfc7464_small>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_dom<stream_case::rfc7464_large>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_dom<stream_case::comma_delimited_small>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
BENCHMARK(bench_dom<stream_case::comma_delimited_large>)
|
||||||
|
->UseRealTime()
|
||||||
|
->DisplayAggregatesOnly(true);
|
||||||
|
|
||||||
|
BENCHMARK_MAIN();
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_RANGES
|
||||||
|
|
||||||
|
#include "large_random.h"
|
||||||
|
|
||||||
|
namespace large_random {
|
||||||
|
|
||||||
|
using namespace simdjson;
|
||||||
|
|
||||||
|
// Identical to simdjson_ondemand but uses get_range() for iteration.
|
||||||
|
// Demonstrates that the ranges wrapper has zero per-element overhead.
|
||||||
|
struct simdjson_ondemand_ranges {
|
||||||
|
static constexpr diff_flags DiffFlags = diff_flags::NONE;
|
||||||
|
|
||||||
|
ondemand::parser parser{};
|
||||||
|
|
||||||
|
bool run(simdjson::padded_string &json, std::vector<point> &result) {
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
for (auto coord_result : ondemand::get_range(doc.get_array())) {
|
||||||
|
ondemand::object coord = coord_result;
|
||||||
|
result.emplace_back(json_benchmark::point{coord.find_field("x"), coord.find_field("y"), coord.find_field("z")});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BENCHMARK_TEMPLATE(large_random, simdjson_ondemand_ranges)->UseManualTime();
|
||||||
|
|
||||||
|
} // namespace large_random
|
||||||
|
|
||||||
|
#endif // SIMDJSON_EXCEPTIONS && SIMDJSON_SUPPORTS_RANGES
|
||||||
@@ -1415,6 +1415,8 @@ With this code, deserializing an `std::list<Car>` instance would capture only th
|
|||||||
that are not made by Toyota.
|
that are not made by Toyota.
|
||||||
|
|
||||||
|
|
||||||
|
**Performance tip**: You will get better performance if you order the attributes (make, model)
|
||||||
|
in the order they appear in the JSON document.
|
||||||
|
|
||||||
|
|
||||||
### 3. Using static reflection (C++26)
|
### 3. Using static reflection (C++26)
|
||||||
@@ -1491,6 +1493,10 @@ void f() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
**Performance tip**: You will get better performance if you order the attributes (make, model)
|
||||||
|
in the order they appear in the JSON document.
|
||||||
|
|
||||||
#### Special cases
|
#### Special cases
|
||||||
|
|
||||||
However, there are instances where the construction cannot
|
However, there are instances where the construction cannot
|
||||||
@@ -1870,6 +1876,66 @@ if (!error) {
|
|||||||
|
|
||||||
This function is particularly useful for extracting data from complex JSON structures with nested arrays and objects. By leveraging wildcards, you can simplify your queries and reduce the need for multiple iterations.
|
This function is particularly useful for extracting data from complex JSON structures with nested arrays and objects. By leveraging wildcards, you can simplify your queries and reduce the need for multiple iterations.
|
||||||
|
|
||||||
|
## C++20 Ranges Support (On-Demand)
|
||||||
|
|
||||||
|
When compiling with C++20 (or later), you can use `std::ranges` with the On-Demand API
|
||||||
|
via the `get_range()` helper. This enables use of range adaptors such as `std::views::transform`.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "simdjson.h"
|
||||||
|
#include <ranges>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
auto json = R"([
|
||||||
|
{ "name": "Alice", "age": 30 },
|
||||||
|
{ "name": "Bob", "age": 25 },
|
||||||
|
{ "name": "Carol", "age": 35 }
|
||||||
|
])"_padded;
|
||||||
|
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto arr = doc.get_array();
|
||||||
|
|
||||||
|
// Use std::views::transform to extract names
|
||||||
|
auto names = ondemand::get_range(arr)
|
||||||
|
| std::views::transform([](auto elem) -> std::string {
|
||||||
|
return std::string(std::string_view(elem["name"]));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto name : names) {
|
||||||
|
std::cout << name << std::endl; // Alice, Bob, Carol
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `get_range()` and `get_key_value_range()` functions wrap an `ondemand::array`
|
||||||
|
or `ondemand::object` in a `std::ranges::view` that satisfies `std::ranges::input_range`.
|
||||||
|
They work with both exception and non-exception code:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// With exceptions:
|
||||||
|
auto range = ondemand::get_range(doc.get_array());
|
||||||
|
|
||||||
|
// Without exceptions:
|
||||||
|
ondemand::array arr;
|
||||||
|
if (doc.get_array().get(arr) == SUCCESS) {
|
||||||
|
auto range = ondemand::get_range(arr);
|
||||||
|
for (auto elem : range) { /* ... */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Object iteration uses `get_key_value_range()` and yields `simdjson_result<ondemand::field>` elements:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto obj = doc.get_object();
|
||||||
|
for (auto field_result : ondemand::get_key_value_range(obj)) {
|
||||||
|
std::cout << field_result.key() << std::endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The range wrappers are zero-cost: they forward directly to the underlying
|
||||||
|
On-Demand iterators with no value buffering or extra per-element overhead.
|
||||||
|
|
||||||
## Compile-Time JSONPath and JSON Pointer (C++26 Reflection)
|
## Compile-Time JSONPath and JSON Pointer (C++26 Reflection)
|
||||||
|
|
||||||
The simdjson library provides **compile-time validated** JSONPath and JSON Pointer accessors when using C++26 Static Reflection. These accessors validate paths against struct definitions at compile time and generate optimized code with zero runtime overhead. In some cases, we find that it is much faster. Furthermore, it is safer in the sense that the expression
|
The simdjson library provides **compile-time validated** JSONPath and JSON Pointer accessors when using C++26 Static Reflection. These accessors validate paths against struct definitions at compile time and generate optimized code with zero runtime overhead. In some cases, we find that it is much faster. Furthermore, it is safer in the sense that the expression
|
||||||
|
|||||||
+280
-29
@@ -26,6 +26,7 @@ Contents
|
|||||||
- [Tracking your position](#tracking-your-position)
|
- [Tracking your position](#tracking-your-position)
|
||||||
- [Incomplete streams](#incomplete-streams)
|
- [Incomplete streams](#incomplete-streams)
|
||||||
- [C++20 features](#c20-features)
|
- [C++20 features](#c20-features)
|
||||||
|
- [C++26 features (static reflection)](#c26-features-static-reflection)
|
||||||
|
|
||||||
Motivation
|
Motivation
|
||||||
-----------
|
-----------
|
||||||
@@ -132,7 +133,7 @@ E.g., `[1,2]{"32":1}` is recognized as two documents.
|
|||||||
Some official formats **(non-exhaustive list)**:
|
Some official formats **(non-exhaustive list)**:
|
||||||
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec/)
|
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec/)
|
||||||
- [JSON lines (JSONL)](http://jsonlines.org/)
|
- [JSON lines (JSONL)](http://jsonlines.org/)
|
||||||
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by simdjson!
|
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464)
|
||||||
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
|
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
|
||||||
|
|
||||||
API
|
API
|
||||||
@@ -278,39 +279,131 @@ Importantly, you should only call `truncated_bytes()` after iterating through al
|
|||||||
Comma-separated documents
|
Comma-separated documents
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
We also support comma-separated documents, but with some performance limitations. The `iterate_many` function takes in an option to allow parsing of comma separated documents (which defaults on false). In this mode, the entire buffer is processed in one batch. Therefore, the total size of the document should not exceed the maximal capacity of the parser (4 GB). This mode also effectively disallow multithreading. It is therefore mostly suitable for not "very large" inputs. In this mode, the batch_size parameter
|
To parse comma-separated documents like `{"a":1},{"b":2},{"c":3}`, use the `stream_format::comma_delimited` parameter:
|
||||||
is effectively ignored, as it is set to at least the document size.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
|
auto json = R"({"a":1},{"b":2},{"c":3})"_padded;
|
||||||
ondemand::parser parser;
|
ondemand::parser parser;
|
||||||
ondemand::document_stream doc_stream;
|
ondemand::document_stream stream;
|
||||||
// We pass '32' as the batch size, but it is a bogus parameter because, since
|
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||||
// we pass 'true' to the allow_comma parameter, the batch size will be set to at least
|
simdjson::stream_format::comma_delimited).get(stream);
|
||||||
// the document size.
|
if (error) { std::cerr << error << std::endl; return; }
|
||||||
auto error = parser.iterate_many(json, 32, true).get(doc_stream);
|
for (auto doc : stream) {
|
||||||
if (error) { std::cerr << error << std::endl; return; }
|
std::cout << doc << std::endl;
|
||||||
for (auto doc : doc_stream) {
|
}
|
||||||
std::cout << doc.type() << std::endl;
|
// Prints: {"a":1}
|
||||||
}
|
// {"b":2}
|
||||||
```
|
// {"c":3}
|
||||||
|
|
||||||
This will print:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
number
|
|
||||||
number
|
Whitespace around the commas is allowed:
|
||||||
number
|
```cpp
|
||||||
number
|
auto json = R"({"a":1} , {"b":2} , {"c":3})"_padded; // Also works
|
||||||
string
|
|
||||||
string
|
|
||||||
string
|
|
||||||
object
|
|
||||||
array
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Nested commas inside objects and arrays are preserved:
|
||||||
|
```cpp
|
||||||
|
auto json = R"({"arr":[1,2,3]},{"obj":{"x":1,"y":2}})"_padded;
|
||||||
|
// Correctly parses as 2 documents, not 6
|
||||||
|
```
|
||||||
|
|
||||||
|
Mixed document types are supported:
|
||||||
|
```cpp
|
||||||
|
auto json = R"(1, 2, 3, 4, "a", "b", "c", {"hello": "world"}, [1, 2, 3])"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
ondemand::document_stream doc_stream;
|
||||||
|
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::comma_delimited).get(doc_stream);
|
||||||
|
if (error) { std::cerr << error << std::endl; return; }
|
||||||
|
for (auto doc : doc_stream) {
|
||||||
|
std::cout << doc.type() << std::endl;
|
||||||
|
}
|
||||||
|
// Prints: number number number number string string string object array
|
||||||
|
```
|
||||||
|
|
||||||
|
Extra top-level separators are tolerated for compatibility with the legacy
|
||||||
|
`allow_comma_separated` behavior. For example, leading commas, trailing commas,
|
||||||
|
and repeated commas are treated as empty separators rather than documents.
|
||||||
|
|
||||||
|
### Legacy `allow_comma_separated` parameter (deprecated)
|
||||||
|
|
||||||
|
The `allow_comma_separated` boolean parameter is deprecated. When set to `true`, it now internally maps to `stream_format::comma_delimited`.
|
||||||
|
|
||||||
|
The old single-batch limitation no longer applies - comma-delimited parsing now supports multi-batch processing and threading for optimal performance on large files.
|
||||||
|
|
||||||
|
JSON Text Sequences (RFC 7464)
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
[RFC 7464](https://tools.ietf.org/html/rfc7464) defines a format for streaming JSON values using ASCII Record Separator (RS, 0x1E) as a delimiter. Each JSON text is preceded by RS and optionally followed by ASCII Line Feed (LF, 0x0A).
|
||||||
|
|
||||||
|
Example input:
|
||||||
|
```
|
||||||
|
<RS>{"name":"doc1"}<LF>
|
||||||
|
<RS>{"name":"doc2"}<LF>
|
||||||
|
<RS>{"name":"doc3"}<LF>
|
||||||
|
```
|
||||||
|
|
||||||
|
To parse JSON text sequences, use the `stream_format::json_sequence` parameter:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Build input with RS (0x1E) and LF (0x0A) delimiters
|
||||||
|
std::string input_str;
|
||||||
|
input_str += '\x1e'; input_str += "{\"a\":1}"; input_str += '\x0a';
|
||||||
|
input_str += '\x1e'; input_str += "{\"b\":2}"; input_str += '\x0a';
|
||||||
|
input_str += '\x1e'; input_str += "{\"c\":3}"; input_str += '\x0a';
|
||||||
|
simdjson::padded_string input(input_str);
|
||||||
|
|
||||||
|
ondemand::parser parser;
|
||||||
|
ondemand::document_stream stream;
|
||||||
|
auto error = parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::json_sequence).get(stream);
|
||||||
|
if (error) { std::cerr << error << std::endl; return; }
|
||||||
|
for (auto doc : stream) {
|
||||||
|
std::cout << doc << std::endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `stream_format` enum has the following values:
|
||||||
|
- `stream_format::whitespace_delimited` (default): Standard NDJSON/JSON Lines format
|
||||||
|
- `stream_format::json_sequence`: RFC 7464 format with RS delimiters
|
||||||
|
- `stream_format::comma_delimited`: Comma-separated JSON documents
|
||||||
|
- `stream_format::comma_delimited_array`: A single JSON array whose elements are iterated as comma-delimited documents (see below)
|
||||||
|
|
||||||
|
The trailing LF after each JSON text is optional but recommended by the RFC for robustness.
|
||||||
|
|
||||||
|
JSON Array As A Document Stream
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Sometimes an input is a single, well-formed JSON array — `[{"a":1},{"b":2},{"c":3}]` — but you want to iterate its elements one at a time without materializing the whole array. Use `stream_format::comma_delimited_array`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto json = R"([{"a":1},{"b":2},{"c":3}])"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
ondemand::document_stream stream;
|
||||||
|
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::comma_delimited_array).get(stream);
|
||||||
|
if (error) { std::cerr << error << std::endl; return; }
|
||||||
|
for (auto doc : stream) {
|
||||||
|
std::cout << doc << std::endl;
|
||||||
|
}
|
||||||
|
// Prints: {"a":1}
|
||||||
|
// {"b":2}
|
||||||
|
// {"c":3}
|
||||||
|
```
|
||||||
|
|
||||||
|
The parser strips the outer `[` and `]` plus any surrounding JSON whitespace (space, tab, LF, CR) and then behaves exactly like `stream_format::comma_delimited` over the remaining bytes. All comma-delimited features are inherited: multi-batch processing, threading, mixed scalar types, and nested commas preserved inside inner objects and arrays.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// All of these work:
|
||||||
|
auto a = R"([1, "x", true, null, {"k":"v"}, [1,2]])"_padded; // mixed scalars
|
||||||
|
auto b = R"( [ 1, 2, 3 ] )"_padded; // whitespace
|
||||||
|
auto c = R"([])"_padded; // empty array → 0 docs
|
||||||
|
```
|
||||||
|
|
||||||
|
If the input is not a well-formed outer array (missing `[`, missing `]`, or empty / all-whitespace), `iterate_many` returns `TAPE_ERROR`. Content **inside** the array is not validated up front — individual document parse errors surface when you iterate, just like `comma_delimited`.
|
||||||
|
|
||||||
|
Positions reported via `current_index()` are relative to the **stripped** buffer (the bytes between `[` and `]`), not the original input, for consistency with the existing BOM-stripping behavior.
|
||||||
|
|
||||||
|
|
||||||
C++20 features
|
C++20 features
|
||||||
--------------------
|
--------------------
|
||||||
@@ -418,3 +511,161 @@ Otherwise you may use this longer version for explicit handling of errors:
|
|||||||
cars.push_back(c);
|
cars.push_back(c);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Performance tip**: You will get better performance if you order the attributes (make, model)
|
||||||
|
in the order they appear in the JSON document.
|
||||||
|
|
||||||
|
C++26 features (static reflection)
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
If you have a C++26 compatible compiler with [P2996](https://wg21.link/P2996)
|
||||||
|
static reflection support, you can compile the simdjson library with the
|
||||||
|
`SIMDJSON_STATIC_REFLECTION` macro set to `1`. When this is the case, simdjson
|
||||||
|
can deserialize a stream of JSON documents directly into your own structures
|
||||||
|
**without** writing any `tag_invoke` function. The library inspects the
|
||||||
|
non-static public members of your type at compile time and produces the
|
||||||
|
parsing code automatically.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define SIMDJSON_STATIC_REFLECTION 1
|
||||||
|
#include "simdjson.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
Consider the same `Car` structure used in the C++20 example, but **without**
|
||||||
|
any `tag_invoke` glue:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Car {
|
||||||
|
std::string make;
|
||||||
|
std::string model;
|
||||||
|
int year;
|
||||||
|
std::vector<double> tire_pressure;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
With C++26 static reflection enabled, you can iterate a stream of cars and
|
||||||
|
push them into a `std::vector<Car>` directly:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
|
||||||
|
"tire_pressure": [ 40.1, 39.9 ] }
|
||||||
|
{ "make": "Kia", "model": "Soul", "year": 2012,
|
||||||
|
"tire_pressure": [ 30.1, 31.0 ] }
|
||||||
|
{ "make": "Toyota", "model": "Tercel", "year": 1999,
|
||||||
|
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
ondemand::document_stream stream;
|
||||||
|
auto error = parser.iterate_many(json).get(stream);
|
||||||
|
if (error) { /* handle error */ }
|
||||||
|
std::vector<Car> cars;
|
||||||
|
for (auto doc : stream) {
|
||||||
|
Car c;
|
||||||
|
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
|
||||||
|
cars.push_back(c);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This works for every `stream_format` value supported by `iterate_many`. The
|
||||||
|
following examples each parse the same three cars, but laid out using a
|
||||||
|
different streaming convention.
|
||||||
|
|
||||||
|
### Whitespace-delimited (default, NDJSON / JSON Lines)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
|
||||||
|
"tire_pressure": [ 40.1, 39.9 ] }
|
||||||
|
{ "make": "Kia", "model": "Soul", "year": 2012,
|
||||||
|
"tire_pressure": [ 30.1, 31.0 ] }
|
||||||
|
{ "make": "Toyota", "model": "Tercel", "year": 1999,
|
||||||
|
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
ondemand::document_stream stream;
|
||||||
|
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::whitespace_delimited).get(stream);
|
||||||
|
if (error) { /* handle error */ }
|
||||||
|
std::vector<Car> cars;
|
||||||
|
for (auto doc : stream) {
|
||||||
|
cars.push_back((Car)doc); // throws on error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Comma-delimited documents
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto json = R"( { "make": "Toyota", "model": "Camry", "year": 2018,
|
||||||
|
"tire_pressure": [ 40.1, 39.9 ] },
|
||||||
|
{ "make": "Kia", "model": "Soul", "year": 2012,
|
||||||
|
"tire_pressure": [ 30.1, 31.0 ] },
|
||||||
|
{ "make": "Toyota", "model": "Tercel", "year": 1999,
|
||||||
|
"tire_pressure": [ 29.8, 30.0 ] } )"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
ondemand::document_stream stream;
|
||||||
|
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::comma_delimited).get(stream);
|
||||||
|
if (error) { /* handle error */ }
|
||||||
|
std::vector<Car> cars;
|
||||||
|
for (auto doc : stream) {
|
||||||
|
Car c;
|
||||||
|
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
|
||||||
|
cars.push_back(c);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### A single JSON array as a stream of documents
|
||||||
|
|
||||||
|
When the input is a single JSON array, you can stream its elements one at a
|
||||||
|
time without materializing the entire array as a `std::vector` upfront:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto json = R"( [ { "make": "Toyota", "model": "Camry", "year": 2018,
|
||||||
|
"tire_pressure": [ 40.1, 39.9 ] },
|
||||||
|
{ "make": "Kia", "model": "Soul", "year": 2012,
|
||||||
|
"tire_pressure": [ 30.1, 31.0 ] },
|
||||||
|
{ "make": "Toyota", "model": "Tercel", "year": 1999,
|
||||||
|
"tire_pressure": [ 29.8, 30.0 ] } ] )"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
ondemand::document_stream stream;
|
||||||
|
auto error = parser.iterate_many(json, ondemand::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::comma_delimited_array).get(stream);
|
||||||
|
if (error) { /* handle error */ }
|
||||||
|
std::vector<Car> cars;
|
||||||
|
for (auto doc : stream) {
|
||||||
|
Car c;
|
||||||
|
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
|
||||||
|
cars.push_back(c);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON Text Sequences (RFC 7464)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Build input with RS (0x1E) and LF (0x0A) delimiters
|
||||||
|
std::string input_str;
|
||||||
|
auto append = [&](std::string_view doc) {
|
||||||
|
input_str += '\x1e'; input_str += doc; input_str += '\x0a';
|
||||||
|
};
|
||||||
|
append(R"({ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9 ] })");
|
||||||
|
append(R"({ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0 ] })");
|
||||||
|
append(R"({ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0 ] })");
|
||||||
|
simdjson::padded_string input(input_str);
|
||||||
|
|
||||||
|
ondemand::parser parser;
|
||||||
|
ondemand::document_stream stream;
|
||||||
|
auto error = parser.iterate_many(input, ondemand::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::json_sequence).get(stream);
|
||||||
|
if (error) { /* handle error */ }
|
||||||
|
std::vector<Car> cars;
|
||||||
|
for (auto doc : stream) {
|
||||||
|
Car c;
|
||||||
|
if ((error = doc.get<Car>().get(c))) { /* handle error */ }
|
||||||
|
cars.push_back(c);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In every case, the user-defined type (`Car` here) does not need a hand-written
|
||||||
|
`tag_invoke` overload: the library generates the deserialization code from the
|
||||||
|
type's public data members at compile time.
|
||||||
|
|
||||||
|
|
||||||
|
**Performance tip**: You will get better performance if you order the attributes (make, model)
|
||||||
|
in the order they appear in the JSON document.
|
||||||
+113
-1
@@ -132,7 +132,7 @@ Whitespace Characters:
|
|||||||
Some official formats **(non-exhaustive list)**:
|
Some official formats **(non-exhaustive list)**:
|
||||||
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec)
|
- [Newline-Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec)
|
||||||
- [JSON lines (JSONL)](http://jsonlines.org/)
|
- [JSON lines (JSONL)](http://jsonlines.org/)
|
||||||
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464) <- Not supported by simdjson!
|
- [Record separator-delimited JSON (RFC 7464)](https://tools.ietf.org/html/rfc7464)
|
||||||
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
|
- [More on Wikipedia...](https://en.wikipedia.org/wiki/JSON_streaming)
|
||||||
|
|
||||||
API
|
API
|
||||||
@@ -253,3 +253,115 @@ Consider the following example where a truncated document (`{"key":"intentionall
|
|||||||
|
|
||||||
|
|
||||||
Importantly, you should only call `truncated_bytes()` after iterating through all of the documents since the stream cannot tell whether there are truncated documents at the very end when it may not have accessed that part of the data yet.
|
Importantly, you should only call `truncated_bytes()` after iterating through all of the documents since the stream cannot tell whether there are truncated documents at the very end when it may not have accessed that part of the data yet.
|
||||||
|
|
||||||
|
JSON Text Sequences (RFC 7464)
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
[RFC 7464](https://tools.ietf.org/html/rfc7464) defines a format for streaming JSON values using ASCII Record Separator (RS, 0x1E) as a delimiter. Each JSON text is preceded by RS and optionally followed by ASCII Line Feed (LF, 0x0A).
|
||||||
|
|
||||||
|
Example input:
|
||||||
|
```
|
||||||
|
<RS>{"name":"doc1"}<LF>
|
||||||
|
<RS>{"name":"doc2"}<LF>
|
||||||
|
<RS>{"name":"doc3"}<LF>
|
||||||
|
```
|
||||||
|
|
||||||
|
To parse JSON text sequences, use the `stream_format::json_sequence` parameter:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Build input with RS (0x1E) and LF (0x0A) delimiters
|
||||||
|
std::string input_str;
|
||||||
|
input_str += '\x1e'; input_str += "{\"a\":1}"; input_str += '\x0a';
|
||||||
|
input_str += '\x1e'; input_str += "{\"b\":2}"; input_str += '\x0a';
|
||||||
|
input_str += '\x1e'; input_str += "{\"c\":3}"; input_str += '\x0a';
|
||||||
|
simdjson::padded_string input(input_str);
|
||||||
|
|
||||||
|
simdjson::dom::parser parser;
|
||||||
|
simdjson::dom::document_stream stream;
|
||||||
|
auto error = parser.parse_many(input, simdjson::dom::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::json_sequence).get(stream);
|
||||||
|
if (error) { std::cerr << error << std::endl; return; }
|
||||||
|
for (auto doc : stream) {
|
||||||
|
std::cout << doc << std::endl;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `stream_format` enum has the following values:
|
||||||
|
- `stream_format::whitespace_delimited` (default): Standard NDJSON/JSON Lines format
|
||||||
|
- `stream_format::json_sequence`: RFC 7464 format with RS delimiters
|
||||||
|
- `stream_format::comma_delimited`: Comma-separated JSON documents
|
||||||
|
- `stream_format::comma_delimited_array`: A single JSON array whose elements are iterated as comma-delimited documents (see below)
|
||||||
|
|
||||||
|
The trailing LF after each JSON text is optional but recommended by the RFC for robustness.
|
||||||
|
|
||||||
|
Comma-Separated Documents
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Some systems produce JSON documents separated by commas, like `{"a":1},{"b":2},{"c":3}`. This is common when extracting elements from a JSON array or when APIs return comma-separated results.
|
||||||
|
|
||||||
|
To parse comma-separated documents, use the `stream_format::comma_delimited` parameter:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto json = R"({"a":1},{"b":2},{"c":3})"_padded;
|
||||||
|
simdjson::dom::parser parser;
|
||||||
|
simdjson::dom::document_stream stream;
|
||||||
|
auto error = parser.parse_many(json, simdjson::dom::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::comma_delimited).get(stream);
|
||||||
|
if (error) { std::cerr << error << std::endl; return; }
|
||||||
|
for (auto doc : stream) {
|
||||||
|
std::cout << doc << std::endl;
|
||||||
|
}
|
||||||
|
// Prints: {"a":1}
|
||||||
|
// {"b":2}
|
||||||
|
// {"c":3}
|
||||||
|
```
|
||||||
|
|
||||||
|
Whitespace around the commas is allowed:
|
||||||
|
```cpp
|
||||||
|
auto json = R"({"a":1} , {"b":2} , {"c":3})"_padded; // Also works
|
||||||
|
```
|
||||||
|
|
||||||
|
Nested commas inside objects and arrays are preserved:
|
||||||
|
```cpp
|
||||||
|
auto json = R"({"arr":[1,2,3]},{"obj":{"x":1,"y":2}})"_padded;
|
||||||
|
// Correctly parses as 2 documents, not 6
|
||||||
|
```
|
||||||
|
|
||||||
|
Extra top-level separators are tolerated for compatibility with the legacy
|
||||||
|
On-Demand comma-separated mode. Leading commas, trailing commas, and repeated
|
||||||
|
commas are treated as empty separators rather than documents.
|
||||||
|
|
||||||
|
Unlike the legacy `allow_comma_separated` parameter, `stream_format::comma_delimited` supports multi-batch processing and threading for optimal performance on large files.
|
||||||
|
|
||||||
|
JSON Array As A Document Stream
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Sometimes an input is a single, well-formed JSON array — `[{"a":1},{"b":2},{"c":3}]` — but you want to iterate its elements one at a time without materializing the whole array. Use `stream_format::comma_delimited_array`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto json = R"([{"a":1},{"b":2},{"c":3}])"_padded;
|
||||||
|
simdjson::dom::parser parser;
|
||||||
|
simdjson::dom::document_stream stream;
|
||||||
|
auto error = parser.parse_many(json, simdjson::dom::DEFAULT_BATCH_SIZE,
|
||||||
|
simdjson::stream_format::comma_delimited_array).get(stream);
|
||||||
|
if (error) { std::cerr << error << std::endl; return; }
|
||||||
|
for (auto doc : stream) {
|
||||||
|
std::cout << doc << std::endl;
|
||||||
|
}
|
||||||
|
// Prints: {"a":1}
|
||||||
|
// {"b":2}
|
||||||
|
// {"c":3}
|
||||||
|
```
|
||||||
|
|
||||||
|
The parser strips the outer `[` and `]` plus any surrounding JSON whitespace (space, tab, LF, CR) and then behaves exactly like `stream_format::comma_delimited` over the remaining bytes. All comma-delimited features are inherited: multi-batch processing, threading, mixed scalar types, and nested commas preserved inside inner objects and arrays.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// All of these work:
|
||||||
|
auto a = R"([1, "x", true, null, {"k":"v"}, [1,2]])"_padded; // mixed scalars
|
||||||
|
auto b = R"( [ 1, 2, 3 ] )"_padded; // whitespace
|
||||||
|
auto c = R"([])"_padded; // empty array → 0 docs
|
||||||
|
```
|
||||||
|
|
||||||
|
If the input is not a well-formed outer array (missing `[`, missing `]`, or empty / all-whitespace), `parse_many` returns `TAPE_ERROR`. Content **inside** the array is not validated up front — individual document parse errors surface when you iterate, just like `comma_delimited`.
|
||||||
|
|
||||||
|
Positions reported via `current_index()` are relative to the **stripped** buffer (the bytes between `[` and `]`), not the original input, for consistency with the existing BOM-stripping behavior.
|
||||||
|
|||||||
+7
-1
@@ -208,7 +208,7 @@ You can still make sure of this capability in your code if you are an expert
|
|||||||
programmer and you are willing to silence sanitizer warnings.
|
programmer and you are willing to silence sanitizer warnings.
|
||||||
|
|
||||||
If you are building simdjson with C++17 or better, you can use `simdjson::padded_input`.
|
If you are building simdjson with C++17 or better, you can use `simdjson::padded_input`.
|
||||||
The `padded_input` struct automatically manages padding for you. It can be constructed from a `std::string_view` or a C-style string with length. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
|
The `padded_input` struct automatically manages padding for you. It can be constructed from a `std::string_view`, a C-style string with length, or a `std::string`. For `std::string`, it takes into account the reserved capacity when determining if sufficient padding exists. If the input already has sufficient padding (up to the end of the memory page), it creates a view without copying. Otherwise, it copies the data into a `padded_string` with proper padding.
|
||||||
|
|
||||||
Example usage:
|
Example usage:
|
||||||
|
|
||||||
@@ -216,6 +216,12 @@ Example usage:
|
|||||||
std::string_view json = get_json_data();
|
std::string_view json = get_json_data();
|
||||||
simdjson::padded_input input(json); // Automatically pads if needed
|
simdjson::padded_input input(json); // Automatically pads if needed
|
||||||
auto result = parser.parse(input);
|
auto result = parser.parse(input);
|
||||||
|
|
||||||
|
// Also works with std::string, considering capacity
|
||||||
|
std::string json_str = get_json_string();
|
||||||
|
json_str.reserve(json_str.size() + 100); // Reserve extra space
|
||||||
|
simdjson::padded_input input2(json_str); // May avoid copying if capacity is sufficient
|
||||||
|
auto result2 = parser.parse(input2);
|
||||||
```
|
```
|
||||||
|
|
||||||
This simplifies padding management compared to manually checking and allocating.
|
This simplifies padding management compared to manually checking and allocating.
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ SIMDJSON_PUSH_DISABLE_UNUSED_WARNINGS
|
|||||||
|
|
||||||
/** The maximum document size supported by simdjson. */
|
/** The maximum document size supported by simdjson. */
|
||||||
constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF;
|
constexpr size_t SIMDJSON_MAXSIZE_BYTES = 0xFFFFFFFF;
|
||||||
|
/** The maximum depth of nested objects and arrays supported by simdjson.
|
||||||
|
A depth of SIMDJSON_MAXSIZE_BYTES/2 is not reasonable and would be
|
||||||
|
adversarial, but it serves as an upper bound for validation purposes. */
|
||||||
|
constexpr size_t SIMDJSON_MAX_DEPTH = SIMDJSON_MAXSIZE_BYTES/2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The amount of padding needed in a buffer to parse JSON.
|
* The amount of padding needed in a buffer to parse JSON.
|
||||||
@@ -46,6 +50,21 @@ struct padded_string;
|
|||||||
class padded_string_view;
|
class padded_string_view;
|
||||||
enum class stage1_mode;
|
enum class stage1_mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream format for parse_many/iterate_many.
|
||||||
|
*/
|
||||||
|
enum class stream_format {
|
||||||
|
whitespace_delimited, ///< Whitespace-delimited JSON documents (default, includes NDJSON/JSONL)
|
||||||
|
json_sequence, ///< RFC 7464 JSON text sequences (RS-delimited)
|
||||||
|
comma_delimited, ///< Comma-separated JSON documents (e.g., `{...},{...},{...}`)
|
||||||
|
comma_delimited_array ///< A single JSON array whose elements are iterated as
|
||||||
|
///< comma-separated documents (e.g., `[{...},{...},{...}]`).
|
||||||
|
///< The parser strips the outer `[` / `]` plus any
|
||||||
|
///< surrounding JSON whitespace (space, tab, LF, CR)
|
||||||
|
///< and then behaves like `comma_delimited` over the
|
||||||
|
///< remaining bytes.
|
||||||
|
};
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ inline error_code document::allocate(size_t capacity) noexcept {
|
|||||||
allocated_capacity = 0;
|
allocated_capacity = 0;
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
}
|
}
|
||||||
|
if (capacity > SIMDJSON_MAXSIZE_BYTES) {
|
||||||
|
return CAPACITY;
|
||||||
|
}
|
||||||
|
|
||||||
// a pathological input like "[[[[..." would generate capacity tape elements, so
|
// a pathological input like "[[[[..." would generate capacity tape elements, so
|
||||||
// need a capacity of at least capacity + 1, but it is also possible to do
|
// need a capacity of at least capacity + 1, but it is also possible to do
|
||||||
|
|||||||
@@ -89,12 +89,14 @@ simdjson_inline document_stream::document_stream(
|
|||||||
dom::parser &_parser,
|
dom::parser &_parser,
|
||||||
const uint8_t *_buf,
|
const uint8_t *_buf,
|
||||||
size_t _len,
|
size_t _len,
|
||||||
size_t _batch_size
|
size_t _batch_size,
|
||||||
|
stream_format _format
|
||||||
) noexcept
|
) noexcept
|
||||||
: parser{&_parser},
|
: parser{&_parser},
|
||||||
buf{_buf},
|
buf{_buf},
|
||||||
len{_len},
|
len{_len},
|
||||||
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
|
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
|
||||||
|
format{_format},
|
||||||
error{SUCCESS}
|
error{SUCCESS}
|
||||||
#ifdef SIMDJSON_THREADS_ENABLED
|
#ifdef SIMDJSON_THREADS_ENABLED
|
||||||
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
|
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
|
||||||
@@ -112,6 +114,7 @@ simdjson_inline document_stream::document_stream() noexcept
|
|||||||
buf{nullptr},
|
buf{nullptr},
|
||||||
len{0},
|
len{0},
|
||||||
batch_size{0},
|
batch_size{0},
|
||||||
|
format{stream_format::whitespace_delimited},
|
||||||
error{UNINITIALIZED}
|
error{UNINITIALIZED}
|
||||||
#ifdef SIMDJSON_THREADS_ENABLED
|
#ifdef SIMDJSON_THREADS_ENABLED
|
||||||
, use_thread(false)
|
, use_thread(false)
|
||||||
@@ -224,7 +227,14 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
|
|||||||
} else {
|
} else {
|
||||||
size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index];
|
size_t next_doc_index = stream->batch_start + stream->parser->implementation->structural_indexes[stream->parser->implementation->next_structural_index];
|
||||||
size_t svlen = next_doc_index - current_index();
|
size_t svlen = next_doc_index - current_index();
|
||||||
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) {
|
// Trim trailing whitespace, NUL, and RS (0x1E). In RFC 7464 json_sequence
|
||||||
|
// mode the scanner classifies RS as a scalar character, so an RS-prefixed
|
||||||
|
// scalar document (number/true/false/null/string) has no closing structural
|
||||||
|
// index and the slice runs all the way up to the next document's RS. RS
|
||||||
|
// cannot legally appear in a JSON value at the source level (control
|
||||||
|
// characters in strings must be escaped as \u001E), so stripping it is
|
||||||
|
// safe in every stream_format.
|
||||||
|
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E)) {
|
||||||
svlen--;
|
svlen--;
|
||||||
}
|
}
|
||||||
return std::string_view(start, svlen);
|
return std::string_view(start, svlen);
|
||||||
@@ -274,10 +284,35 @@ inline size_t document_stream::next_batch_start() const noexcept {
|
|||||||
|
|
||||||
inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept {
|
inline error_code document_stream::run_stage1(dom::parser &p, size_t _batch_start) noexcept {
|
||||||
size_t remaining = len - _batch_start;
|
size_t remaining = len - _batch_start;
|
||||||
|
stage1_mode mode;
|
||||||
if (remaining <= batch_size) {
|
if (remaining <= batch_size) {
|
||||||
return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final);
|
// Final batch
|
||||||
|
switch (format) {
|
||||||
|
case stream_format::json_sequence:
|
||||||
|
mode = stage1_mode::json_sequence_final;
|
||||||
|
break;
|
||||||
|
case stream_format::comma_delimited:
|
||||||
|
mode = stage1_mode::comma_delimited_final;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode = stage1_mode::streaming_final;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return p.implementation->stage1(&buf[_batch_start], remaining, mode);
|
||||||
} else {
|
} else {
|
||||||
return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial);
|
// Partial batch
|
||||||
|
switch (format) {
|
||||||
|
case stream_format::json_sequence:
|
||||||
|
mode = stage1_mode::json_sequence_partial;
|
||||||
|
break;
|
||||||
|
case stream_format::comma_delimited:
|
||||||
|
mode = stage1_mode::comma_delimited_partial;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode = stage1_mode::streaming_partial;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return p.implementation->stage1(&buf[_batch_start], batch_size, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -206,12 +206,14 @@ private:
|
|||||||
* @param buf is the raw byte buffer we need to process
|
* @param buf is the raw byte buffer we need to process
|
||||||
* @param len is the length of the raw byte buffer in bytes
|
* @param len is the length of the raw byte buffer in bytes
|
||||||
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
|
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
|
||||||
|
* @param format is the stream format
|
||||||
*/
|
*/
|
||||||
simdjson_inline document_stream(
|
simdjson_inline document_stream(
|
||||||
dom::parser &parser,
|
dom::parser &parser,
|
||||||
const uint8_t *buf,
|
const uint8_t *buf,
|
||||||
size_t len,
|
size_t len,
|
||||||
size_t batch_size
|
size_t batch_size,
|
||||||
|
stream_format format = stream_format::whitespace_delimited
|
||||||
) noexcept;
|
) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -261,6 +263,8 @@ private:
|
|||||||
const uint8_t *buf;
|
const uint8_t *buf;
|
||||||
size_t len;
|
size_t len;
|
||||||
size_t batch_size;
|
size_t batch_size;
|
||||||
|
/** The stream format. */
|
||||||
|
stream_format format;
|
||||||
/** The error (or lack thereof) from the current document. */
|
/** The error (or lack thereof) from the current document. */
|
||||||
error_code error;
|
error_code error;
|
||||||
size_t batch_start{0};
|
size_t batch_start{0};
|
||||||
|
|||||||
@@ -170,12 +170,7 @@ simdjson_inline simdjson_result<element> parser::parse(const padded_string_view
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept {
|
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept {
|
||||||
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
return parse_many(buf, len, batch_size, stream_format::whitespace_delimited);
|
||||||
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
|
|
||||||
buf += 3;
|
|
||||||
len -= 3;
|
|
||||||
}
|
|
||||||
return document_stream(*this, buf, len, batch_size);
|
|
||||||
}
|
}
|
||||||
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size) noexcept {
|
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size) noexcept {
|
||||||
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size);
|
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size);
|
||||||
@@ -187,6 +182,42 @@ inline simdjson_result<document_stream> parser::parse_many(const padded_string &
|
|||||||
return parse_many(s.data(), s.length(), batch_size);
|
return parse_many(s.data(), s.length(), batch_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline simdjson_result<document_stream> parser::parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept {
|
||||||
|
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
||||||
|
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
|
||||||
|
buf += 3;
|
||||||
|
len -= 3;
|
||||||
|
}
|
||||||
|
if (format == stream_format::comma_delimited_array) {
|
||||||
|
// Strip leading JSON whitespace.
|
||||||
|
while (len > 0 && (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n' || buf[0] == '\r')) {
|
||||||
|
buf++; len--;
|
||||||
|
}
|
||||||
|
// Expect the opening '['.
|
||||||
|
if (len == 0 || buf[0] != '[') { return TAPE_ERROR; }
|
||||||
|
buf++; len--;
|
||||||
|
// Strip trailing JSON whitespace.
|
||||||
|
while (len > 0 && (buf[len-1] == ' ' || buf[len-1] == '\t' || buf[len-1] == '\n' || buf[len-1] == '\r')) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
// Expect the closing ']'.
|
||||||
|
if (len == 0 || buf[len-1] != ']') { return TAPE_ERROR; }
|
||||||
|
len--;
|
||||||
|
// Fall through to comma_delimited over the array contents.
|
||||||
|
format = stream_format::comma_delimited;
|
||||||
|
}
|
||||||
|
return document_stream(*this, buf, len, batch_size, format);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept {
|
||||||
|
return parse_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size, format);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept {
|
||||||
|
return parse_many(s.data(), s.length(), batch_size, format);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
|
||||||
|
return parse_many(s.data(), s.length(), batch_size, format);
|
||||||
|
}
|
||||||
|
|
||||||
simdjson_inline size_t parser::capacity() const noexcept {
|
simdjson_inline size_t parser::capacity() const noexcept {
|
||||||
return implementation ? implementation->capacity() : 0;
|
return implementation ? implementation->capacity() : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -494,6 +494,23 @@ public:
|
|||||||
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
||||||
simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete;
|
simdjson_result<document_stream> parse_many(const char *buf, size_t batch_size = dom::DEFAULT_BATCH_SIZE) noexcept = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a stream of JSON documents with explicit format specification.
|
||||||
|
*
|
||||||
|
* @param buf The concatenated JSON documents.
|
||||||
|
* @param len The length of the buffer.
|
||||||
|
* @param batch_size The batch size to use.
|
||||||
|
* @param format The stream format.
|
||||||
|
* @return A stream of documents, or an error.
|
||||||
|
*/
|
||||||
|
inline simdjson_result<document_stream> parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept;
|
||||||
|
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||||
|
inline simdjson_result<document_stream> parse_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept;
|
||||||
|
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||||
|
inline simdjson_result<document_stream> parse_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
|
||||||
|
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||||
|
inline simdjson_result<document_stream> parse_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
|
* Ensure this parser has enough memory to process JSON documents up to `capacity` bytes in length
|
||||||
* and `max_depth` depth.
|
* and `max_depth` depth.
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ inline simdjson_warn_unused error_code dom_parser_implementation::set_capacity(s
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept {
|
inline simdjson_warn_unused error_code dom_parser_implementation::set_max_depth(size_t max_depth) noexcept {
|
||||||
|
if(max_depth > SIMDJSON_MAX_DEPTH) { return CAPACITY; }
|
||||||
// Stage 2 stacks
|
// Stage 2 stacks
|
||||||
open_containers.reset(new (std::nothrow) open_container[max_depth]);
|
open_containers.reset(new (std::nothrow) open_container[max_depth]);
|
||||||
is_array.reset(new (std::nothrow) bool[max_depth]);
|
is_array.reset(new (std::nothrow) bool[max_depth]);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "simdjson/generic/ondemand/field.h"
|
#include "simdjson/generic/ondemand/field.h"
|
||||||
#include "simdjson/generic/ondemand/object.h"
|
#include "simdjson/generic/ondemand/object.h"
|
||||||
#include "simdjson/generic/ondemand/object_iterator.h"
|
#include "simdjson/generic/ondemand/object_iterator.h"
|
||||||
|
#include "simdjson/generic/ondemand/ranges.h"
|
||||||
#include "simdjson/generic/ondemand/serialization.h"
|
#include "simdjson/generic/ondemand/serialization.h"
|
||||||
|
|
||||||
// Deserialization for standard types
|
// Deserialization for standard types
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
#include "simdjson/generic/ondemand/logger-inl.h"
|
#include "simdjson/generic/ondemand/logger-inl.h"
|
||||||
#include "simdjson/generic/ondemand/object-inl.h"
|
#include "simdjson/generic/ondemand/object-inl.h"
|
||||||
#include "simdjson/generic/ondemand/object_iterator-inl.h"
|
#include "simdjson/generic/ondemand/object_iterator-inl.h"
|
||||||
|
#include "simdjson/generic/ondemand/ranges-inl.h"
|
||||||
#include "simdjson/generic/ondemand/parser-inl.h"
|
#include "simdjson/generic/ondemand/parser-inl.h"
|
||||||
#include "simdjson/generic/ondemand/raw_json_string-inl.h"
|
#include "simdjson/generic/ondemand/raw_json_string-inl.h"
|
||||||
#include "simdjson/generic/ondemand/token_iterator-inl.h"
|
#include "simdjson/generic/ondemand/token_iterator-inl.h"
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ class token_iterator;
|
|||||||
class value;
|
class value;
|
||||||
class value_iterator;
|
class value_iterator;
|
||||||
|
|
||||||
|
#if SIMDJSON_SUPPORTS_RANGES
|
||||||
|
class array_range;
|
||||||
|
class array_range_iterator;
|
||||||
|
class object_range;
|
||||||
|
class object_range_iterator;
|
||||||
|
#endif // SIMDJSON_SUPPORTS_RANGES
|
||||||
|
|
||||||
} // namespace ondemand
|
} // namespace ondemand
|
||||||
} // namespace SIMDJSON_IMPLEMENTATION
|
} // namespace SIMDJSON_IMPLEMENTATION
|
||||||
} // namespace simdjson
|
} // namespace simdjson
|
||||||
|
|||||||
@@ -95,13 +95,15 @@ simdjson_inline document_stream::document_stream(
|
|||||||
const uint8_t *_buf,
|
const uint8_t *_buf,
|
||||||
size_t _len,
|
size_t _len,
|
||||||
size_t _batch_size,
|
size_t _batch_size,
|
||||||
bool _allow_comma_separated
|
bool _allow_comma_separated,
|
||||||
|
stream_format _format
|
||||||
) noexcept
|
) noexcept
|
||||||
: parser{&_parser},
|
: parser{&_parser},
|
||||||
buf{_buf},
|
buf{_buf},
|
||||||
len{_len},
|
len{_len},
|
||||||
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
|
batch_size{_batch_size <= MINIMAL_BATCH_SIZE ? MINIMAL_BATCH_SIZE : _batch_size},
|
||||||
allow_comma_separated{_allow_comma_separated},
|
allow_comma_separated{_allow_comma_separated},
|
||||||
|
format{_format},
|
||||||
error{SUCCESS}
|
error{SUCCESS}
|
||||||
#ifdef SIMDJSON_THREADS_ENABLED
|
#ifdef SIMDJSON_THREADS_ENABLED
|
||||||
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
|
, use_thread(_parser.threaded) // we need to make a copy because _parser.threaded can change
|
||||||
@@ -120,6 +122,7 @@ simdjson_inline document_stream::document_stream() noexcept
|
|||||||
len{0},
|
len{0},
|
||||||
batch_size{0},
|
batch_size{0},
|
||||||
allow_comma_separated{false},
|
allow_comma_separated{false},
|
||||||
|
format{stream_format::whitespace_delimited},
|
||||||
error{UNINITIALIZED}
|
error{UNINITIALIZED}
|
||||||
#ifdef SIMDJSON_THREADS_ENABLED
|
#ifdef SIMDJSON_THREADS_ENABLED
|
||||||
, use_thread(false)
|
, use_thread(false)
|
||||||
@@ -219,7 +222,10 @@ inline void document_stream::start() noexcept {
|
|||||||
error = run_stage1(*parser, batch_start);
|
error = run_stage1(*parser, batch_start);
|
||||||
}
|
}
|
||||||
if (error) { return; }
|
if (error) { return; }
|
||||||
doc_index = batch_start;
|
// For json_sequence mode, structural_indexes[0] points to the actual JSON value
|
||||||
|
// after the RS delimiter and any following whitespace. For regular mode, it is
|
||||||
|
// the offset from batch_start to the first document in the batch.
|
||||||
|
doc_index = batch_start + parser->implementation->structural_indexes[0];
|
||||||
doc = document(json_iterator(&buf[batch_start], parser));
|
doc = document(json_iterator(&buf[batch_start], parser));
|
||||||
doc.iter._streaming = true;
|
doc.iter._streaming = true;
|
||||||
|
|
||||||
@@ -300,7 +306,7 @@ inline void document_stream::next() noexcept {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
if (error) { continue; } // If the error was EMPTY, we may want to load another batch.
|
if (error) { continue; } // If the error was EMPTY, we may want to load another batch.
|
||||||
doc_index = batch_start;
|
doc_index = batch_start + parser->implementation->structural_indexes[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,10 +335,35 @@ inline error_code document_stream::run_stage1(ondemand::parser &p, size_t _batch
|
|||||||
// This code only updates the structural index in the parser, it does not update any json_iterator
|
// This code only updates the structural index in the parser, it does not update any json_iterator
|
||||||
// instance.
|
// instance.
|
||||||
size_t remaining = len - _batch_start;
|
size_t remaining = len - _batch_start;
|
||||||
|
stage1_mode mode;
|
||||||
if (remaining <= batch_size) {
|
if (remaining <= batch_size) {
|
||||||
return p.implementation->stage1(&buf[_batch_start], remaining, stage1_mode::streaming_final);
|
// Final batch
|
||||||
|
switch (format) {
|
||||||
|
case stream_format::json_sequence:
|
||||||
|
mode = stage1_mode::json_sequence_final;
|
||||||
|
break;
|
||||||
|
case stream_format::comma_delimited:
|
||||||
|
mode = stage1_mode::comma_delimited_final;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode = stage1_mode::streaming_final;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return p.implementation->stage1(&buf[_batch_start], remaining, mode);
|
||||||
} else {
|
} else {
|
||||||
return p.implementation->stage1(&buf[_batch_start], batch_size, stage1_mode::streaming_partial);
|
// Partial batch
|
||||||
|
switch (format) {
|
||||||
|
case stream_format::json_sequence:
|
||||||
|
mode = stage1_mode::json_sequence_partial;
|
||||||
|
break;
|
||||||
|
case stream_format::comma_delimited:
|
||||||
|
mode = stage1_mode::comma_delimited_partial;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mode = stage1_mode::streaming_partial;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return p.implementation->stage1(&buf[_batch_start], batch_size, mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,14 +384,21 @@ simdjson_inline std::string_view document_stream::iterator::source() const noexc
|
|||||||
depth--;
|
depth--;
|
||||||
break;
|
break;
|
||||||
default: // Scalar value document
|
default: // Scalar value document
|
||||||
// TODO: We could remove trailing whitespaces
|
|
||||||
// This returns a string spanning from start of value to the beginning of the next document (excluded)
|
// This returns a string spanning from start of value to the beginning of the next document (excluded)
|
||||||
{
|
{
|
||||||
auto next_index = stream->parser->implementation->structural_indexes[++cur_struct_index];
|
auto next_index = stream->parser->implementation->structural_indexes[++cur_struct_index];
|
||||||
// normally the length would be next_index - current_index() - 1, except for the last document
|
// normally the length would be next_index - current_index() - 1, except for the last document
|
||||||
size_t svlen = next_index - current_index();
|
size_t svlen = next_index - current_index();
|
||||||
const char *start = reinterpret_cast<const char*>(stream->buf) + current_index();
|
const char *start = reinterpret_cast<const char*>(stream->buf) + current_index();
|
||||||
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0')) {
|
// Trim trailing whitespace, NUL, and RS (0x1E). In RFC 7464
|
||||||
|
// json_sequence mode the scanner classifies RS as a scalar
|
||||||
|
// character, so an RS-prefixed scalar document (number / true /
|
||||||
|
// false / null / string) has no closing structural index and the
|
||||||
|
// slice runs all the way up to the next document's RS. RS cannot
|
||||||
|
// legally appear in a JSON value at the source level (control
|
||||||
|
// characters in strings must be escaped as \u001E), so stripping
|
||||||
|
// it is safe in every stream_format.
|
||||||
|
while(svlen > 1 && (std::isspace(start[svlen-1]) || start[svlen-1] == '\0' || static_cast<uint8_t>(start[svlen-1]) == 0x1E)) {
|
||||||
svlen--;
|
svlen--;
|
||||||
}
|
}
|
||||||
return std::string_view(start, svlen);
|
return std::string_view(start, svlen);
|
||||||
|
|||||||
@@ -229,13 +229,16 @@ private:
|
|||||||
* @param buf is the raw byte buffer we need to process
|
* @param buf is the raw byte buffer we need to process
|
||||||
* @param len is the length of the raw byte buffer in bytes
|
* @param len is the length of the raw byte buffer in bytes
|
||||||
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
|
* @param batch_size is the size of the windows (must be strictly greater or equal to the largest JSON document)
|
||||||
|
* @param allow_comma_separated whether to allow comma-separated documents
|
||||||
|
* @param format the stream format
|
||||||
*/
|
*/
|
||||||
simdjson_inline document_stream(
|
simdjson_inline document_stream(
|
||||||
ondemand::parser &parser,
|
ondemand::parser &parser,
|
||||||
const uint8_t *buf,
|
const uint8_t *buf,
|
||||||
size_t len,
|
size_t len,
|
||||||
size_t batch_size,
|
size_t batch_size,
|
||||||
bool allow_comma_separated
|
bool allow_comma_separated,
|
||||||
|
stream_format format = stream_format::whitespace_delimited
|
||||||
) noexcept;
|
) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -284,6 +287,7 @@ private:
|
|||||||
size_t len;
|
size_t len;
|
||||||
size_t batch_size;
|
size_t batch_size;
|
||||||
bool allow_comma_separated;
|
bool allow_comma_separated;
|
||||||
|
stream_format format;
|
||||||
/**
|
/**
|
||||||
* We are going to use just one document instance. The document owns
|
* We are going to use just one document instance. The document owns
|
||||||
* the json_iterator. It implies that we only ever pass a reference
|
* the json_iterator. It implies that we only ever pass a reference
|
||||||
|
|||||||
@@ -133,6 +133,34 @@ simdjson_warn_unused simdjson_inline simdjson_result<json_iterator> parser::iter
|
|||||||
return json_iterator(reinterpret_cast<const uint8_t *>(json.data()), this);
|
return json_iterator(reinterpret_cast<const uint8_t *>(json.data()), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size) noexcept {
|
||||||
|
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
||||||
|
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
|
||||||
|
buf += 3;
|
||||||
|
len -= 3;
|
||||||
|
}
|
||||||
|
return document_stream(*this, buf, len, batch_size, false, stream_format::whitespace_delimited);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size) noexcept {
|
||||||
|
return iterate_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(padded_string_view s, size_t batch_size) noexcept {
|
||||||
|
if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||||
|
return iterate_many(s.data(), s.length(), batch_size);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(const padded_string &s, size_t batch_size) noexcept {
|
||||||
|
return iterate_many(padded_string_view(s), batch_size);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(const std::string &s, size_t batch_size) noexcept {
|
||||||
|
return iterate_many(padded_string_view(s), batch_size);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(std::string &s, size_t batch_size) noexcept {
|
||||||
|
return iterate_many(pad(s), batch_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef SIMDJSON_DISABLE_DEPRECATED_API
|
||||||
|
SIMDJSON_PUSH_DISABLE_WARNINGS
|
||||||
|
SIMDJSON_DISABLE_DEPRECATED_WARNING
|
||||||
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
|
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
|
||||||
// Warning: no check is done on the buffer padding. We trust the user.
|
// Warning: no check is done on the buffer padding. We trust the user.
|
||||||
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
||||||
@@ -140,8 +168,11 @@ inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf,
|
|||||||
buf += 3;
|
buf += 3;
|
||||||
len -= 3;
|
len -= 3;
|
||||||
}
|
}
|
||||||
if(allow_comma_separated && batch_size < len) { batch_size = len; }
|
// Map allow_comma_separated to stream_format::comma_delimited
|
||||||
return document_stream(*this, buf, len, batch_size, allow_comma_separated);
|
if (allow_comma_separated) {
|
||||||
|
return document_stream(*this, buf, len, batch_size, false, stream_format::comma_delimited);
|
||||||
|
}
|
||||||
|
return document_stream(*this, buf, len, batch_size, false, stream_format::whitespace_delimited);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
|
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept {
|
||||||
@@ -161,6 +192,48 @@ inline simdjson_result<document_stream> parser::iterate_many(const std::string &
|
|||||||
inline simdjson_result<document_stream> parser::iterate_many(std::string &s, size_t batch_size, bool allow_comma_separated) noexcept {
|
inline simdjson_result<document_stream> parser::iterate_many(std::string &s, size_t batch_size, bool allow_comma_separated) noexcept {
|
||||||
return iterate_many(pad(s), batch_size, allow_comma_separated);
|
return iterate_many(pad(s), batch_size, allow_comma_separated);
|
||||||
}
|
}
|
||||||
|
SIMDJSON_POP_DISABLE_WARNINGS
|
||||||
|
#endif // SIMDJSON_DISABLE_DEPRECATED_API
|
||||||
|
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept {
|
||||||
|
if(batch_size < MINIMAL_BATCH_SIZE) { batch_size = MINIMAL_BATCH_SIZE; }
|
||||||
|
if((len >= 3) && (std::memcmp(buf, "\xEF\xBB\xBF", 3) == 0)) {
|
||||||
|
buf += 3;
|
||||||
|
len -= 3;
|
||||||
|
}
|
||||||
|
if (format == stream_format::comma_delimited_array) {
|
||||||
|
// Strip leading JSON whitespace.
|
||||||
|
while (len > 0 && (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n' || buf[0] == '\r')) {
|
||||||
|
buf++; len--;
|
||||||
|
}
|
||||||
|
// Expect the opening '['.
|
||||||
|
if (len == 0 || buf[0] != '[') { return TAPE_ERROR; }
|
||||||
|
buf++; len--;
|
||||||
|
// Strip trailing JSON whitespace.
|
||||||
|
while (len > 0 && (buf[len-1] == ' ' || buf[len-1] == '\t' || buf[len-1] == '\n' || buf[len-1] == '\r')) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
// Expect the closing ']'.
|
||||||
|
if (len == 0 || buf[len-1] != ']') { return TAPE_ERROR; }
|
||||||
|
len--;
|
||||||
|
// Fall through to comma_delimited over the array contents.
|
||||||
|
format = stream_format::comma_delimited;
|
||||||
|
}
|
||||||
|
return document_stream(*this, buf, len, batch_size, false, format);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept {
|
||||||
|
return iterate_many(reinterpret_cast<const uint8_t *>(buf), len, batch_size, format);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept {
|
||||||
|
if (!s.has_sufficient_padding()) { return INSUFFICIENT_PADDING; }
|
||||||
|
return iterate_many(s.data(), s.length(), batch_size, format);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept {
|
||||||
|
return iterate_many(padded_string_view(s), batch_size, format);
|
||||||
|
}
|
||||||
|
inline simdjson_result<document_stream> parser::iterate_many(const padded_string &s, size_t batch_size, stream_format format) noexcept {
|
||||||
|
return iterate_many(padded_string_view(s), batch_size, format);
|
||||||
|
}
|
||||||
simdjson_pure simdjson_inline size_t parser::capacity() const noexcept {
|
simdjson_pure simdjson_inline size_t parser::capacity() const noexcept {
|
||||||
return _capacity;
|
return _capacity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,32 +244,66 @@ public:
|
|||||||
* spot is cache-related: small enough to fit in cache, yet big enough to
|
* spot is cache-related: small enough to fit in cache, yet big enough to
|
||||||
* parse as many documents as possible in one tight loop.
|
* parse as many documents as possible in one tight loop.
|
||||||
* Defaults to 10MB, which has been a reasonable sweet spot in our tests.
|
* Defaults to 10MB, which has been a reasonable sweet spot in our tests.
|
||||||
* @param allow_comma_separated (defaults on false) This allows a mode where the documents are
|
* @param allow_comma_separated @deprecated Use stream_format::comma_delimited instead.
|
||||||
* separated by commas instead of whitespace. It comes with a performance
|
* When true, maps internally to stream_format::comma_delimited.
|
||||||
* penalty because the entire document is indexed at once (and the document must be
|
* Defaults to false.
|
||||||
* less than 4 GB), and there is no multithreading. In this mode, the batch_size parameter
|
|
||||||
* is effectively ignored, as it is set to at least the document size.
|
|
||||||
* @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors:
|
* @return The stream, or an error. An empty input will yield 0 documents rather than an EMPTY error. Errors:
|
||||||
* - MEMALLOC if the parser does not have enough capacity and memory allocation fails
|
* - MEMALLOC if the parser does not have enough capacity and memory allocation fails
|
||||||
* - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity.
|
* - CAPACITY if the parser does not have enough capacity and batch_size > max_capacity.
|
||||||
* - other json errors if parsing fails. You should not rely on these errors to always the same for the
|
* - other json errors if parsing fails. You should not rely on these errors to always the same for the
|
||||||
* same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware).
|
* same document: they may vary under runtime dispatch (so they may vary depending on your system and hardware).
|
||||||
*/
|
*/
|
||||||
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||||
inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||||
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||||
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size)
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size)
|
||||||
the string might be automatically padded with up to SIMDJSON_PADDING whitespace characters */
|
the string might be automatically padded with up to SIMDJSON_PADDING whitespace characters */
|
||||||
inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||||
/** @overload parse_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size) */
|
||||||
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE, bool allow_comma_separated = false) noexcept;
|
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept;
|
||||||
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
/** @private We do not want to allow implicit conversion from C string to std::string. */
|
||||||
simdjson_result<document_stream> iterate_many(const char *buf, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept = delete;
|
simdjson_result<document_stream> iterate_many(const char *buf, size_t batch_size = DEFAULT_BATCH_SIZE) noexcept = delete;
|
||||||
|
|
||||||
|
#ifndef SIMDJSON_DISABLE_DEPRECATED_API
|
||||||
|
/**
|
||||||
|
* @deprecated Use iterate_many with stream_format::comma_delimited instead.
|
||||||
|
*/
|
||||||
|
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||||
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||||
|
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(padded_string_view json, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||||
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||||
|
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||||
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||||
|
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||||
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||||
|
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(std::string &s, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||||
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, bool allow_comma_separated) */
|
||||||
|
simdjson_deprecated inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size, bool allow_comma_separated) noexcept;
|
||||||
|
#endif // SIMDJSON_DISABLE_DEPRECATED_API
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a stream of JSON documents with explicit format specification.
|
||||||
|
*
|
||||||
|
* @param buf The concatenated JSON documents.
|
||||||
|
* @param len The length of the buffer.
|
||||||
|
* @param batch_size The batch size to use.
|
||||||
|
* @param format The stream format (whitespace_delimited, json_sequence, or comma_delimited).
|
||||||
|
* @return A stream of documents, or an error.
|
||||||
|
*/
|
||||||
|
inline simdjson_result<document_stream> iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) noexcept;
|
||||||
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||||
|
inline simdjson_result<document_stream> iterate_many(const char *buf, size_t len, size_t batch_size, stream_format format) noexcept;
|
||||||
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||||
|
inline simdjson_result<document_stream> iterate_many(padded_string_view s, size_t batch_size, stream_format format) noexcept;
|
||||||
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||||
|
inline simdjson_result<document_stream> iterate_many(const std::string &s, size_t batch_size, stream_format format) noexcept;
|
||||||
|
/** @overload iterate_many(const uint8_t *buf, size_t len, size_t batch_size, stream_format format) */
|
||||||
|
inline simdjson_result<document_stream> iterate_many(const padded_string &s, size_t batch_size, stream_format format) noexcept;
|
||||||
|
|
||||||
/** The capacity of this parser (the largest document it can process). */
|
/** The capacity of this parser (the largest document it can process). */
|
||||||
simdjson_pure simdjson_inline size_t capacity() const noexcept;
|
simdjson_pure simdjson_inline size_t capacity() const noexcept;
|
||||||
/** The maximum capacity of this parser (the largest document it is allowed to process). */
|
/** The maximum capacity of this parser (the largest document it is allowed to process). */
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#ifndef SIMDJSON_GENERIC_ONDEMAND_RANGES_INL_H
|
||||||
|
|
||||||
|
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
||||||
|
#define SIMDJSON_GENERIC_ONDEMAND_RANGES_INL_H
|
||||||
|
#include "simdjson/generic/ondemand/base.h"
|
||||||
|
#include "simdjson/generic/ondemand/ranges.h"
|
||||||
|
#include "simdjson/generic/ondemand/array-inl.h"
|
||||||
|
#include "simdjson/generic/ondemand/array_iterator-inl.h"
|
||||||
|
#include "simdjson/generic/ondemand/object-inl.h"
|
||||||
|
#include "simdjson/generic/ondemand/object_iterator-inl.h"
|
||||||
|
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||||
|
|
||||||
|
#if SIMDJSON_SUPPORTS_RANGES
|
||||||
|
|
||||||
|
namespace simdjson {
|
||||||
|
namespace SIMDJSON_IMPLEMENTATION {
|
||||||
|
namespace ondemand {
|
||||||
|
|
||||||
|
//
|
||||||
|
// array_range_iterator
|
||||||
|
//
|
||||||
|
|
||||||
|
simdjson_inline array_range_iterator::array_range_iterator(array_iterator iter) noexcept
|
||||||
|
: iter_{iter} {}
|
||||||
|
|
||||||
|
simdjson_inline simdjson_result<value> array_range_iterator::operator*() const noexcept {
|
||||||
|
return *iter_;
|
||||||
|
}
|
||||||
|
|
||||||
|
simdjson_inline array_range_iterator& array_range_iterator::operator++() noexcept {
|
||||||
|
++iter_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||||
|
simdjson_inline void array_range_iterator::operator++(int) noexcept {
|
||||||
|
++*this;
|
||||||
|
}
|
||||||
|
SIMDJSON_POP_DISABLE_WARNINGS
|
||||||
|
|
||||||
|
//
|
||||||
|
// array_range
|
||||||
|
//
|
||||||
|
|
||||||
|
simdjson_inline array_range::array_range(array& arr) noexcept {
|
||||||
|
auto b = arr.begin();
|
||||||
|
if (b.error()) { error_ = b.error(); return; }
|
||||||
|
begin_ = b.value_unsafe();
|
||||||
|
end_ = arr.end().value_unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
simdjson_inline array_range_iterator array_range::begin() noexcept {
|
||||||
|
return array_range_iterator(begin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
simdjson_inline array_range_iterator array_range::end() noexcept {
|
||||||
|
return array_range_iterator(end_);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// object_range_iterator
|
||||||
|
//
|
||||||
|
|
||||||
|
simdjson_inline object_range_iterator::object_range_iterator(object_iterator iter) noexcept
|
||||||
|
: iter_{iter} {}
|
||||||
|
|
||||||
|
simdjson_inline simdjson_result<field> object_range_iterator::operator*() const noexcept {
|
||||||
|
return *iter_;
|
||||||
|
}
|
||||||
|
|
||||||
|
simdjson_inline object_range_iterator& object_range_iterator::operator++() noexcept {
|
||||||
|
++iter_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||||
|
simdjson_inline void object_range_iterator::operator++(int) noexcept {
|
||||||
|
++*this;
|
||||||
|
}
|
||||||
|
SIMDJSON_POP_DISABLE_WARNINGS
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// object_range
|
||||||
|
//
|
||||||
|
|
||||||
|
simdjson_inline object_range::object_range(object& obj) noexcept {
|
||||||
|
auto b = obj.begin();
|
||||||
|
if (b.error()) { error_ = b.error(); return; }
|
||||||
|
begin_ = b.value_unsafe();
|
||||||
|
end_ = obj.end().value_unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
simdjson_inline object_range_iterator object_range::begin() noexcept {
|
||||||
|
return object_range_iterator(begin_);
|
||||||
|
}
|
||||||
|
|
||||||
|
simdjson_inline object_range_iterator object_range::end() noexcept {
|
||||||
|
return object_range_iterator(end_);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Free functions
|
||||||
|
//
|
||||||
|
|
||||||
|
simdjson_inline array_range get_range(array& arr) noexcept {
|
||||||
|
return array_range(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
simdjson_inline object_range get_key_value_range(object& obj) noexcept {
|
||||||
|
return object_range(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SIMDJSON_EXCEPTIONS
|
||||||
|
simdjson_inline array_range get_range(simdjson_result<array> result) {
|
||||||
|
return array_range(result.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
simdjson_inline object_range get_key_value_range(simdjson_result<object> result) {
|
||||||
|
return object_range(result.value());
|
||||||
|
}
|
||||||
|
#endif // SIMDJSON_EXCEPTIONS
|
||||||
|
|
||||||
|
} // namespace ondemand
|
||||||
|
} // namespace SIMDJSON_IMPLEMENTATION
|
||||||
|
} // namespace simdjson
|
||||||
|
|
||||||
|
// Verify the range wrapper types satisfy the expected C++20 concepts.
|
||||||
|
static_assert(std::input_iterator<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range_iterator>);
|
||||||
|
static_assert(std::input_iterator<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range_iterator>);
|
||||||
|
static_assert(std::ranges::input_range<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range>);
|
||||||
|
static_assert(std::ranges::input_range<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range>);
|
||||||
|
static_assert(std::ranges::view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range>);
|
||||||
|
static_assert(std::ranges::view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range>);
|
||||||
|
|
||||||
|
#endif // SIMDJSON_SUPPORTS_RANGES
|
||||||
|
|
||||||
|
#endif // SIMDJSON_GENERIC_ONDEMAND_RANGES_INL_H
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
#ifndef SIMDJSON_GENERIC_ONDEMAND_RANGES_H
|
||||||
|
|
||||||
|
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
|
||||||
|
#define SIMDJSON_GENERIC_ONDEMAND_RANGES_H
|
||||||
|
#include "simdjson/generic/ondemand/base.h"
|
||||||
|
#include "simdjson/generic/ondemand/array.h"
|
||||||
|
#include "simdjson/generic/ondemand/array_iterator.h"
|
||||||
|
#include "simdjson/generic/ondemand/object.h"
|
||||||
|
#include "simdjson/generic/ondemand/object_iterator.h"
|
||||||
|
#include "simdjson/generic/ondemand/field.h"
|
||||||
|
#include "simdjson/generic/ondemand/value.h"
|
||||||
|
#endif // SIMDJSON_CONDITIONAL_INCLUDE
|
||||||
|
|
||||||
|
#if SIMDJSON_SUPPORTS_RANGES
|
||||||
|
|
||||||
|
namespace simdjson {
|
||||||
|
namespace SIMDJSON_IMPLEMENTATION {
|
||||||
|
namespace ondemand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ranges-compatible iterator adapter for JSON arrays.
|
||||||
|
*
|
||||||
|
* Wraps array_iterator to satisfy std::input_iterator by providing:
|
||||||
|
* - const operator* (via mutable internal state)
|
||||||
|
* - post-increment operator
|
||||||
|
* - iterator_concept tag
|
||||||
|
*
|
||||||
|
* The mutable approach is standard for single-pass input iterators that
|
||||||
|
* read from external sources (similar to std::istream_iterator).
|
||||||
|
*/
|
||||||
|
class array_range_iterator {
|
||||||
|
public:
|
||||||
|
using iterator_concept = std::input_iterator_tag;
|
||||||
|
using value_type = simdjson_result<value>;
|
||||||
|
using reference = simdjson_result<value>;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
|
||||||
|
simdjson_inline array_range_iterator() noexcept = default;
|
||||||
|
simdjson_inline explicit array_range_iterator(array_iterator iter) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current element. Const-qualified for std::indirectly_readable;
|
||||||
|
* internally delegates to the mutable wrapped iterator.
|
||||||
|
*/
|
||||||
|
simdjson_inline simdjson_result<value> operator*() const noexcept;
|
||||||
|
simdjson_inline array_range_iterator& operator++() noexcept;
|
||||||
|
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||||
|
simdjson_inline void operator++(int) noexcept;
|
||||||
|
SIMDJSON_POP_DISABLE_WARNINGS
|
||||||
|
/**
|
||||||
|
* Comparison delegates to array_iterator::operator==, which checks
|
||||||
|
* whether the underlying parser has finished the array (depth-based).
|
||||||
|
*/
|
||||||
|
simdjson_inline friend bool operator==(const array_range_iterator& a,
|
||||||
|
const array_range_iterator& b) noexcept {
|
||||||
|
return a.iter_ == b.iter_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable array_iterator iter_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A std::ranges::view over a JSON array.
|
||||||
|
*
|
||||||
|
* Wraps an ondemand::array and exposes begin()/end() that return
|
||||||
|
* array_range_iterator (satisfying std::input_iterator), enabling
|
||||||
|
* use with std::views::transform and other range adaptors.
|
||||||
|
*
|
||||||
|
* If the array's begin() returns an error (only possible under
|
||||||
|
* SIMDJSON_DEVELOPMENT_CHECKS), the range will be empty and error()
|
||||||
|
* will return the error code.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ondemand::parser parser;
|
||||||
|
* auto doc = parser.iterate(json);
|
||||||
|
* auto arr = doc.get_array().value();
|
||||||
|
* for (auto elem : ondemand::get_range(arr)) { ... }
|
||||||
|
*/
|
||||||
|
class array_range {
|
||||||
|
public:
|
||||||
|
simdjson_inline array_range() noexcept = default;
|
||||||
|
simdjson_inline explicit array_range(array& arr) noexcept;
|
||||||
|
|
||||||
|
simdjson_inline array_range_iterator begin() noexcept;
|
||||||
|
simdjson_inline array_range_iterator end() noexcept;
|
||||||
|
|
||||||
|
/** Returns SUCCESS if the range was created successfully, or the error code otherwise. */
|
||||||
|
simdjson_inline error_code error() const noexcept { return error_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
array_iterator begin_{};
|
||||||
|
array_iterator end_{};
|
||||||
|
error_code error_{SUCCESS};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ranges-compatible iterator adapter for JSON objects.
|
||||||
|
*
|
||||||
|
* Wraps object_iterator to satisfy std::input_iterator, yielding
|
||||||
|
* simdjson_result<field> elements (key-value pairs).
|
||||||
|
*/
|
||||||
|
class object_range_iterator {
|
||||||
|
public:
|
||||||
|
using iterator_concept = std::input_iterator_tag;
|
||||||
|
using value_type = simdjson_result<field>;
|
||||||
|
using reference = simdjson_result<field>;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
|
||||||
|
simdjson_inline object_range_iterator() noexcept = default;
|
||||||
|
simdjson_inline explicit object_range_iterator(object_iterator iter) noexcept;
|
||||||
|
|
||||||
|
simdjson_inline simdjson_result<field> operator*() const noexcept;
|
||||||
|
simdjson_inline object_range_iterator& operator++() noexcept;
|
||||||
|
SIMDJSON_PUSH_DISABLE_ALL_WARNINGS
|
||||||
|
simdjson_inline void operator++(int) noexcept;
|
||||||
|
SIMDJSON_POP_DISABLE_WARNINGS
|
||||||
|
simdjson_inline friend bool operator==(const object_range_iterator& a,
|
||||||
|
const object_range_iterator& b) noexcept {
|
||||||
|
return a.iter_ == b.iter_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable object_iterator iter_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A std::ranges::view over a JSON object.
|
||||||
|
*
|
||||||
|
* Wraps an ondemand::object and exposes begin()/end() that return
|
||||||
|
* object_range_iterator, enabling use with range adaptors.
|
||||||
|
*
|
||||||
|
* If the object's begin() returns an error, the range will be empty
|
||||||
|
* and error() will return the error code.
|
||||||
|
*/
|
||||||
|
class object_range {
|
||||||
|
public:
|
||||||
|
simdjson_inline object_range() noexcept = default;
|
||||||
|
simdjson_inline explicit object_range(object& obj) noexcept;
|
||||||
|
|
||||||
|
simdjson_inline object_range_iterator begin() noexcept;
|
||||||
|
simdjson_inline object_range_iterator end() noexcept;
|
||||||
|
|
||||||
|
/** Returns SUCCESS if the range was created successfully, or the error code otherwise. */
|
||||||
|
simdjson_inline error_code error() const noexcept { return error_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
object_iterator begin_{};
|
||||||
|
object_iterator end_{};
|
||||||
|
error_code error_{SUCCESS};
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Get a std::ranges compatible view over a JSON array. */
|
||||||
|
simdjson_inline array_range get_range(array& arr) noexcept;
|
||||||
|
|
||||||
|
/** Get a std::ranges compatible view over a JSON object (key-value pairs). */
|
||||||
|
simdjson_inline object_range get_key_value_range(object& obj) noexcept;
|
||||||
|
|
||||||
|
#if SIMDJSON_EXCEPTIONS
|
||||||
|
/** Get a std::ranges compatible view, unwrapping the simdjson_result (throws on error). */
|
||||||
|
simdjson_inline array_range get_range(simdjson_result<array> result);
|
||||||
|
|
||||||
|
/** Get a std::ranges compatible view, unwrapping the simdjson_result (throws on error). */
|
||||||
|
simdjson_inline object_range get_key_value_range(simdjson_result<object> result);
|
||||||
|
#endif // SIMDJSON_EXCEPTIONS
|
||||||
|
|
||||||
|
} // namespace ondemand
|
||||||
|
} // namespace SIMDJSON_IMPLEMENTATION
|
||||||
|
} // namespace simdjson
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
namespace ranges {
|
||||||
|
template<>
|
||||||
|
inline constexpr bool enable_view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::array_range> = true;
|
||||||
|
template<>
|
||||||
|
inline constexpr bool enable_view<simdjson::SIMDJSON_IMPLEMENTATION::ondemand::object_range> = true;
|
||||||
|
} // namespace ranges
|
||||||
|
} // namespace std
|
||||||
|
|
||||||
|
#endif // SIMDJSON_SUPPORTS_RANGES
|
||||||
|
|
||||||
|
#endif // SIMDJSON_GENERIC_ONDEMAND_RANGES_H
|
||||||
@@ -19,7 +19,12 @@ class document;
|
|||||||
* 3) The stream_final mode allows us to truncate final
|
* 3) The stream_final mode allows us to truncate final
|
||||||
* unterminated strings. It is useful in conjunction with streaming_partial.
|
* unterminated strings. It is useful in conjunction with streaming_partial.
|
||||||
*/
|
*/
|
||||||
enum class stage1_mode { regular, streaming_partial, streaming_final};
|
enum class stage1_mode {
|
||||||
|
regular,
|
||||||
|
streaming_partial, streaming_final,
|
||||||
|
json_sequence_partial, json_sequence_final,
|
||||||
|
comma_delimited_partial, comma_delimited_final
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if mode == streaming_partial or mode == streaming_final
|
* Returns true if mode == streaming_partial or mode == streaming_final
|
||||||
@@ -31,7 +36,6 @@ inline bool is_streaming(stage1_mode mode) {
|
|||||||
// return (mode == stage1_mode::streaming_partial || mode == stage1_mode::streaming_final);
|
// return (mode == stage1_mode::streaming_partial || mode == stage1_mode::streaming_final);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -268,7 +268,21 @@ class SimdjsonFile:
|
|||||||
print(f" Adding include: {self} includes {include}")
|
print(f" Adding include: {self} includes {include}")
|
||||||
if self.is_conditional_include:
|
if self.is_conditional_include:
|
||||||
# If I have a dependency file, I can only include something that has a dependency file.
|
# If I have a dependency file, I can only include something that has a dependency file.
|
||||||
assert include.is_conditional_include, f"Error: Amalgamated file '{self}' is trying to include '{include}', but '{include}' is not an amalgamated file. Amalgamated files can only include other amalgamated files to maintain conditional inclusion structure. Check the inclusion rules in the script's 'rules' variable. {rules}"
|
if not include.is_conditional_include:
|
||||||
|
dep = self.dependency_file
|
||||||
|
dep_hint = f" and add it to the dependency file '{dep}'" if dep else ""
|
||||||
|
raise AssertionError(
|
||||||
|
f"Error: Amalgamated file '{self}' is trying to include '{include}', "
|
||||||
|
f"but '{include}' is not an amalgamated file.\n\n"
|
||||||
|
f"FIX: Wrap the #include \"{include}\" in a conditional block:\n\n"
|
||||||
|
f" #ifndef SIMDJSON_CONDITIONAL_INCLUDE\n"
|
||||||
|
f" #include \"{include}\"\n"
|
||||||
|
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
|
||||||
|
f"This makes the include editor-only (skipped during amalgamation){dep_hint}.\n\n"
|
||||||
|
f"During amalgamation, '{include}' is already included earlier in the "
|
||||||
|
f"amalgamated output, so it does not need to be included again.\n\n"
|
||||||
|
f"{rules}"
|
||||||
|
)
|
||||||
# TODO make sure we only include amalgamated files that are guaranteed to be included with us (or before us)
|
# TODO make sure we only include amalgamated files that are guaranteed to be included with us (or before us)
|
||||||
# if include.amalgamator_file:
|
# if include.amalgamator_file:
|
||||||
# assert include.amalgamator_file == self, f"{self} cannot include {include}: it should be included from {include.amalgamator_file} instead."
|
# assert include.amalgamator_file == self, f"{self} cannot include {include}: it should be included from {include.amalgamator_file} instead."
|
||||||
@@ -425,8 +439,10 @@ class Amalgamator:
|
|||||||
self.implementation = "SIMDJSON_BUILTIN_IMPLEMENTATION"
|
self.implementation = "SIMDJSON_BUILTIN_IMPLEMENTATION"
|
||||||
|
|
||||||
assert not self.editor_only_region, f"Error: Already in an editor-only region when starting to write '{file}'. Ensure proper nesting of conditional blocks."
|
assert not self.editor_only_region, f"Error: Already in an editor-only region when starting to write '{file}'. Ensure proper nesting of conditional blocks."
|
||||||
|
editor_only_start_line = None
|
||||||
|
bare_endif_lines = []
|
||||||
with open(file.absolute_path, 'r') as fid2:
|
with open(file.absolute_path, 'r') as fid2:
|
||||||
for line in fid2:
|
for line_number, line in enumerate(fid2, 1):
|
||||||
line = line.rstrip('\n')
|
line = line.rstrip('\n')
|
||||||
|
|
||||||
# Ignore #pragma once, it causes warnings if it ends up in a .cpp file
|
# Ignore #pragma once, it causes warnings if it ends up in a .cpp file
|
||||||
@@ -439,6 +455,7 @@ class Amalgamator:
|
|||||||
assert self.in_conditional_include_block, f"Error: File '{file}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' without a prior '#define SIMDJSON_CONDITIONAL_INCLUDE'. Ensure the define comes first. Stack: {self.include_stack}. {rules}"
|
assert self.in_conditional_include_block, f"Error: File '{file}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' without a prior '#define SIMDJSON_CONDITIONAL_INCLUDE'. Ensure the define comes first. Stack: {self.include_stack}. {rules}"
|
||||||
assert not self.editor_only_region, f"Error: File '{file}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' twice in a row. Ensure conditional blocks are properly nested and closed. {rules}"
|
assert not self.editor_only_region, f"Error: File '{file}' uses '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' twice in a row. Ensure conditional blocks are properly nested and closed. {rules}"
|
||||||
self.editor_only_region = True
|
self.editor_only_region = True
|
||||||
|
editor_only_start_line = line_number
|
||||||
|
|
||||||
# Handle ignored lines (and ending ignore blocks)
|
# Handle ignored lines (and ending ignore blocks)
|
||||||
end_ignore = endif_conditional_re.search(line)
|
end_ignore = endif_conditional_re.search(line)
|
||||||
@@ -453,6 +470,12 @@ class Amalgamator:
|
|||||||
file.add_editor_only_include(included_file)
|
file.add_editor_only_include(included_file)
|
||||||
if end_ignore:
|
if end_ignore:
|
||||||
self.editor_only_region = False
|
self.editor_only_region = False
|
||||||
|
editor_only_start_line = None
|
||||||
|
else:
|
||||||
|
# Track bare #endif lines that might be the intended closer
|
||||||
|
stripped = line.strip()
|
||||||
|
if stripped == '#endif' or (stripped.startswith('#endif') and 'SIMDJSON_CONDITIONAL_INCLUDE' not in stripped):
|
||||||
|
bare_endif_lines.append((line_number, line.strip()))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
assert not end_ignore, f"Error: File '{file}' has '#endif // SIMDJSON_CONDITIONAL_INCLUDE' without a matching '#ifndef'. Ensure proper conditional block structure. {rules}"
|
assert not end_ignore, f"Error: File '{file}' has '#endif // SIMDJSON_CONDITIONAL_INCLUDE' without a matching '#ifndef'. Ensure proper conditional block structure. {rules}"
|
||||||
@@ -501,7 +524,35 @@ class Amalgamator:
|
|||||||
|
|
||||||
self.write(line)
|
self.write(line)
|
||||||
|
|
||||||
assert not self.editor_only_region, f"Error: File '{file}' ended without closing the '#endif // SIMDJSON_CONDITIONAL_INCLUDE'. Ensure all conditional blocks are properly closed. {rules}"
|
if self.editor_only_region:
|
||||||
|
msg = (
|
||||||
|
f"Error: File '{file}' ended without closing the "
|
||||||
|
f"'#endif // SIMDJSON_CONDITIONAL_INCLUDE' block "
|
||||||
|
f"(opened at line {editor_only_start_line}).\n\n"
|
||||||
|
)
|
||||||
|
if bare_endif_lines:
|
||||||
|
msg += (
|
||||||
|
f"HINT: Found #endif line(s) inside the block that are missing "
|
||||||
|
f"the required comment. The amalgamation script looks for exactly:\n\n"
|
||||||
|
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
|
||||||
|
f"but found:\n"
|
||||||
|
)
|
||||||
|
for ln, text in bare_endif_lines:
|
||||||
|
msg += f" line {ln}: {text}\n"
|
||||||
|
msg += (
|
||||||
|
f"\nFIX: Change the #endif to:\n\n"
|
||||||
|
f" #endif // SIMDJSON_CONDITIONAL_INCLUDE\n\n"
|
||||||
|
f"The '// SIMDJSON_CONDITIONAL_INCLUDE' comment is required "
|
||||||
|
f"for the amalgamation script to recognize it as the closing "
|
||||||
|
f"of the conditional block.\n"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
msg += (
|
||||||
|
f"FIX: Add '#endif // SIMDJSON_CONDITIONAL_INCLUDE' to close "
|
||||||
|
f"the '#ifndef SIMDJSON_CONDITIONAL_INCLUDE' block.\n"
|
||||||
|
)
|
||||||
|
msg += f"\n{rules}"
|
||||||
|
raise AssertionError(msg)
|
||||||
|
|
||||||
self.write(f"/* end file {self.file_to_str(file)} */")
|
self.write(f"/* end file {self.file_to_str(file)} */")
|
||||||
|
|
||||||
|
|||||||
+58
-2
@@ -226,9 +226,15 @@ simdjson_warn_unused simdjson_inline error_code scan() {
|
|||||||
add_structural();
|
add_structural();
|
||||||
// Primitive or invalid character (invalid characters will be checked in stage 2)
|
// Primitive or invalid character (invalid characters will be checked in stage 2)
|
||||||
} else {
|
} else {
|
||||||
// Anything else, add the structural and go until we find the next one
|
// Anything else, add the structural and go until we find the next one.
|
||||||
|
// We also stop on RS (0x1E) so that RFC 7464 json_sequence inputs
|
||||||
|
// like `\x1e"a"\x1e"b"` produce a separate structural for each RS
|
||||||
|
// rather than being absorbed into a single primitive run. RS is a
|
||||||
|
// control character that is invalid in normal JSON, so breaking
|
||||||
|
// the run here has no effect on well-formed non-json_sequence
|
||||||
|
// inputs.
|
||||||
add_structural();
|
add_structural();
|
||||||
while (idx+1<len && !char_is_space_or_operator(buf[idx+1])) {
|
while (idx+1<len && !char_is_space_or_operator(buf[idx+1]) && buf[idx+1] != 0x1e) {
|
||||||
idx++;
|
idx++;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -283,6 +289,56 @@ simdjson_warn_unused simdjson_inline error_code scan() {
|
|||||||
// doing.
|
// doing.
|
||||||
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
|
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
|
||||||
if (parser.n_structural_indexes == 0) { return EMPTY; }
|
if (parser.n_structural_indexes == 0) { return EMPTY; }
|
||||||
|
} else if (partial == stage1_mode::json_sequence_partial) {
|
||||||
|
// RFC 7464: use RS positions for batch boundaries
|
||||||
|
if(unclosed_string) {
|
||||||
|
parser.n_structural_indexes--;
|
||||||
|
if (simdjson_unlikely(parser.n_structural_indexes == 0)) { return CAPACITY; }
|
||||||
|
}
|
||||||
|
uint32_t next_batch_start = uint32_t(len);
|
||||||
|
auto new_structural_indexes = find_next_document_index_json_sequence(parser, len, false, next_batch_start);
|
||||||
|
if (new_structural_indexes == DOCUMENT_TOO_LARGE) {
|
||||||
|
return CAPACITY;
|
||||||
|
}
|
||||||
|
if (new_structural_indexes == 0) {
|
||||||
|
parser.n_structural_indexes = 0;
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
parser.n_structural_indexes = new_structural_indexes;
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes] = next_batch_start;
|
||||||
|
} else if (partial == stage1_mode::json_sequence_final) {
|
||||||
|
// RFC 7464: final batch, last document extends to EOF
|
||||||
|
if(unclosed_string) { parser.n_structural_indexes--; }
|
||||||
|
uint32_t next_batch_start = uint32_t(len);
|
||||||
|
parser.n_structural_indexes = find_next_document_index_json_sequence(parser, len, true, next_batch_start);
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes];
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
|
||||||
|
if (simdjson_unlikely(parser.n_structural_indexes == 0)) { return EMPTY; }
|
||||||
|
} else if (partial == stage1_mode::comma_delimited_partial) {
|
||||||
|
// Comma-delimited: filter root-level commas, use comma positions for batch boundaries
|
||||||
|
if(unclosed_string) {
|
||||||
|
parser.n_structural_indexes--;
|
||||||
|
if (simdjson_unlikely(parser.n_structural_indexes == 0)) { return CAPACITY; }
|
||||||
|
}
|
||||||
|
uint32_t next_batch_start = uint32_t(len);
|
||||||
|
auto new_structural_indexes = filter_comma_delimited(parser, len, false, next_batch_start);
|
||||||
|
if (new_structural_indexes == DOCUMENT_TOO_LARGE) {
|
||||||
|
return CAPACITY;
|
||||||
|
}
|
||||||
|
if (new_structural_indexes == 0) {
|
||||||
|
parser.n_structural_indexes = 0;
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
parser.n_structural_indexes = new_structural_indexes;
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes] = next_batch_start;
|
||||||
|
} else if (partial == stage1_mode::comma_delimited_final) {
|
||||||
|
// Comma-delimited: final batch, last document extends to EOF
|
||||||
|
if(unclosed_string) { parser.n_structural_indexes--; }
|
||||||
|
uint32_t next_batch_start = uint32_t(len);
|
||||||
|
parser.n_structural_indexes = filter_comma_delimited(parser, len, true, next_batch_start);
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes];
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
|
||||||
|
if (simdjson_unlikely(parser.n_structural_indexes == 0)) { return EMPTY; }
|
||||||
} else if(unclosed_string) { error = UNCLOSED_STRING; }
|
} else if(unclosed_string) { error = UNCLOSED_STRING; }
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,277 @@ simdjson_inline uint32_t find_next_document_index(dom_parser_implementation &par
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sentinel value returned to indicate a document started but didn't fit
|
||||||
|
* (CAPACITY error), as opposed to 0 which means no document content found
|
||||||
|
* (EMPTY).
|
||||||
|
*/
|
||||||
|
constexpr uint32_t DOCUMENT_TOO_LARGE = UINT32_MAX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For RFC 7464 JSON text sequences, filter RS from structural indexes and
|
||||||
|
* find batch boundaries.
|
||||||
|
*
|
||||||
|
* In JSON sequence mode, RS (0x1E) marks the start of each JSON text.
|
||||||
|
* RS bytes appear in structural_indexes as they are classified as scalars.
|
||||||
|
* This function:
|
||||||
|
* 1. Scans structural_indexes to find and count RS positions
|
||||||
|
* 2. Filters RS out of structural_indexes in-place
|
||||||
|
* 3. Determines batch boundaries based on RS positions
|
||||||
|
*
|
||||||
|
* @param parser The parser with structural_indexes and buf.
|
||||||
|
* @param len The length of the current batch buffer.
|
||||||
|
* @param is_final True if this is the final batch (no more data coming).
|
||||||
|
* @param next_batch_start Output: offset where the next batch should start.
|
||||||
|
* @return The number of structural indexes to keep (after RS filtering),
|
||||||
|
* 0 if no document content found (EMPTY),
|
||||||
|
* or DOCUMENT_TOO_LARGE if a document started but didn't fit (CAPACITY).
|
||||||
|
*/
|
||||||
|
simdjson_inline uint32_t find_next_document_index_json_sequence(
|
||||||
|
dom_parser_implementation &parser,
|
||||||
|
size_t len,
|
||||||
|
bool is_final,
|
||||||
|
uint32_t &next_batch_start) {
|
||||||
|
// Default: next batch starts at end of buffer
|
||||||
|
next_batch_start = uint32_t(len);
|
||||||
|
|
||||||
|
if (parser.n_structural_indexes == 0) { return 0; }
|
||||||
|
|
||||||
|
// Phase 1: Scan structural_indexes to find RS positions and handle them.
|
||||||
|
// RS marks the start of a JSON text. For objects/arrays, the '{' or '[' after RS
|
||||||
|
// is already in structural_indexes (it's an operator). For scalars like numbers,
|
||||||
|
// the digit following RS is NOT in structural_indexes because the scanner sees
|
||||||
|
// RS as a scalar, making the digit a scalar continuation, not a start.
|
||||||
|
// We must: (1) remove RS from structural_indexes, and (2) for scalars, add the
|
||||||
|
// actual value start position.
|
||||||
|
uint32_t write_idx = 0;
|
||||||
|
uint32_t last_rs_pos = 0;
|
||||||
|
uint32_t rs_count = 0;
|
||||||
|
|
||||||
|
for (uint32_t read_idx = 0; read_idx < parser.n_structural_indexes; read_idx++) {
|
||||||
|
const uint32_t pos = parser.structural_indexes[read_idx];
|
||||||
|
if (parser.buf[pos] == 0x1E) {
|
||||||
|
// This is an RS character - find the actual JSON value start.
|
||||||
|
last_rs_pos = pos;
|
||||||
|
rs_count++;
|
||||||
|
// Skip past this RS and any whitespace *and any additional RSes*
|
||||||
|
// to locate the real value. Consecutive RSes are degenerate
|
||||||
|
// "empty records" per RFC 7464; we collapse them here. They do
|
||||||
|
// not always appear as separate entries in structural_indexes
|
||||||
|
// because the scanner groups runs of adjacent non-whitespace
|
||||||
|
// scalar bytes (including RS) into a single scalar start.
|
||||||
|
uint32_t value_start = pos + 1;
|
||||||
|
while (value_start < len) {
|
||||||
|
const uint8_t c = parser.buf[value_start];
|
||||||
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
|
||||||
|
value_start++;
|
||||||
|
} else if (c == 0x1E) {
|
||||||
|
// Collapsed empty record. Still count it so rs_count reflects
|
||||||
|
// the true number of record markers and last_rs_pos tracks
|
||||||
|
// the final one.
|
||||||
|
last_rs_pos = value_start;
|
||||||
|
rs_count++;
|
||||||
|
value_start++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the scanner emitted additional structurals inside the
|
||||||
|
// whitespace+RS run we just walked over (i.e., isolated RSes
|
||||||
|
// separated by whitespace), skip past them so we do not
|
||||||
|
// double-count or double-emit.
|
||||||
|
while (read_idx + 1 < parser.n_structural_indexes &&
|
||||||
|
parser.structural_indexes[read_idx + 1] < value_start) {
|
||||||
|
read_idx++;
|
||||||
|
}
|
||||||
|
// Check if the value start is an operator (always present in
|
||||||
|
// scanner structural_indexes) or a scalar-like start (which may
|
||||||
|
// be missing from structural_indexes and must be added here).
|
||||||
|
// Note: '"' is NOT always in structural_indexes. The scanner
|
||||||
|
// classifies '"' as a scalar character and emits it as a
|
||||||
|
// structural only when it is a *scalar start* (preceded by
|
||||||
|
// whitespace or an operator). When '"' immediately follows an
|
||||||
|
// RS (which the scanner also classifies as scalar), it is
|
||||||
|
// treated as a scalar continuation and not emitted - so we
|
||||||
|
// must add it here just like any other scalar value.
|
||||||
|
if (value_start < len) {
|
||||||
|
const uint8_t c = parser.buf[value_start];
|
||||||
|
const bool is_operator =
|
||||||
|
(c == '{' || c == '}' || c == '[' || c == ']' ||
|
||||||
|
c == ':' || c == ',');
|
||||||
|
// If the next scanner structural is exactly at value_start,
|
||||||
|
// the scanner already emitted it (it followed whitespace) and
|
||||||
|
// we must not add a duplicate - a subsequent iteration will
|
||||||
|
// copy it into write_idx.
|
||||||
|
const bool already_emitted =
|
||||||
|
(read_idx + 1 < parser.n_structural_indexes &&
|
||||||
|
parser.structural_indexes[read_idx + 1] == value_start);
|
||||||
|
if (!is_operator && !already_emitted) {
|
||||||
|
// Scalar value (number/true/false/null/string) - add its
|
||||||
|
// position since scanner missed it.
|
||||||
|
parser.structural_indexes[write_idx++] = value_start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not RS, copy to output
|
||||||
|
parser.structural_indexes[write_idx++] = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update structural index count
|
||||||
|
parser.n_structural_indexes = write_idx;
|
||||||
|
|
||||||
|
if (parser.n_structural_indexes == 0) { return 0; }
|
||||||
|
if (rs_count == 0) {
|
||||||
|
// No RS found; for final batch, try generic boundary detection
|
||||||
|
return is_final ? find_next_document_index(parser) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2: Determine batch boundaries based on RS positions
|
||||||
|
|
||||||
|
if (is_final) {
|
||||||
|
// Final batch: all documents are complete (last one ends at EOF).
|
||||||
|
// In json_sequence mode, RS markers define document boundaries, so all
|
||||||
|
// remaining structurals form complete documents. Return them all directly.
|
||||||
|
// (Calling find_next_document_index() would fail for scalar documents.)
|
||||||
|
return parser.n_structural_indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial batch: need to find complete documents only.
|
||||||
|
// A document starting at an RS is complete if there is another RS after it.
|
||||||
|
next_batch_start = last_rs_pos;
|
||||||
|
|
||||||
|
if (rs_count < 2) {
|
||||||
|
// Only one RS, so we have at most one document that may be incomplete.
|
||||||
|
// We cannot confirm it is complete without another RS.
|
||||||
|
// Return DOCUMENT_TOO_LARGE if content was found (write_idx > 0), 0 if only separators.
|
||||||
|
return (parser.n_structural_indexes > 0) ? DOCUMENT_TOO_LARGE : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have at least 2 RS markers. The last complete document ends before last_rs_pos.
|
||||||
|
|
||||||
|
// Find the structural index cutoff: keep only structurals < last_rs_pos.
|
||||||
|
// Since we already filtered RS, all remaining structurals are valid.
|
||||||
|
// We iterate backward to find the last structural before last_rs_pos.
|
||||||
|
uint32_t keep_count = 0;
|
||||||
|
for (uint32_t i = parser.n_structural_indexes; i > 0; i--) {
|
||||||
|
if (parser.structural_indexes[i - 1] < last_rs_pos) {
|
||||||
|
keep_count = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No structurals before the last RS - no complete documents
|
||||||
|
if (keep_count == 0) { return 0; }
|
||||||
|
|
||||||
|
// All documents before the last RS are complete by definition (the next RS
|
||||||
|
// confirms their end). No need to call find_next_document_index() which
|
||||||
|
// would fail for scalar documents like `1` or `"hello"`.
|
||||||
|
return keep_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter comma-delimited documents by removing root-level commas from
|
||||||
|
* structural indexes.
|
||||||
|
*
|
||||||
|
* For comma-delimited format like `{...},{...},{...}`, we need to remove
|
||||||
|
* the commas that separate documents (depth 0) while preserving commas
|
||||||
|
* inside arrays and objects (depth > 0).
|
||||||
|
*
|
||||||
|
* After filtering, the structural indexes look like whitespace-delimited
|
||||||
|
* documents, so find_next_document_index() works unchanged.
|
||||||
|
*
|
||||||
|
* @param parser The parser with structural_indexes and buf.
|
||||||
|
* @param len The length of the current batch buffer.
|
||||||
|
* @param is_final True if this is the final batch (no more data coming).
|
||||||
|
* @param next_batch_start Output: offset where the next batch should start.
|
||||||
|
* @return The number of structural indexes to keep,
|
||||||
|
* 0 if no document content found (EMPTY),
|
||||||
|
* or DOCUMENT_TOO_LARGE if a document started but didn't fit (CAPACITY).
|
||||||
|
*/
|
||||||
|
simdjson_inline uint32_t filter_comma_delimited(
|
||||||
|
dom_parser_implementation &parser,
|
||||||
|
size_t len,
|
||||||
|
bool is_final,
|
||||||
|
uint32_t &next_batch_start) {
|
||||||
|
// Default: next batch starts at end of buffer
|
||||||
|
next_batch_start = uint32_t(len);
|
||||||
|
|
||||||
|
if (parser.n_structural_indexes == 0) { return 0; }
|
||||||
|
|
||||||
|
// Track depth to identify root-level commas (depth 0)
|
||||||
|
int depth = 0;
|
||||||
|
uint32_t write_idx = 0;
|
||||||
|
uint32_t last_root_comma_pos = 0;
|
||||||
|
uint32_t root_comma_count = 0;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < parser.n_structural_indexes; i++) {
|
||||||
|
uint32_t idx = parser.structural_indexes[i];
|
||||||
|
uint8_t c = parser.buf[idx];
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case '{': case '[':
|
||||||
|
depth++;
|
||||||
|
parser.structural_indexes[write_idx++] = idx;
|
||||||
|
break;
|
||||||
|
case '}': case ']':
|
||||||
|
depth--;
|
||||||
|
parser.structural_indexes[write_idx++] = idx;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
if (depth == 0) {
|
||||||
|
// Root-level comma = document boundary, skip it
|
||||||
|
last_root_comma_pos = idx;
|
||||||
|
root_comma_count++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parser.structural_indexes[write_idx++] = idx;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Colons, scalars, etc.
|
||||||
|
parser.structural_indexes[write_idx++] = idx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update structural index count
|
||||||
|
parser.n_structural_indexes = write_idx;
|
||||||
|
|
||||||
|
if (parser.n_structural_indexes == 0) { return 0; }
|
||||||
|
|
||||||
|
if (is_final) {
|
||||||
|
// Final batch: use standard boundary detection on filtered indexes
|
||||||
|
return find_next_document_index(parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial batch: need to find complete documents only.
|
||||||
|
// A document ending with a root comma is complete.
|
||||||
|
if (root_comma_count == 0) {
|
||||||
|
// No root commas found; we cannot confirm any document is complete.
|
||||||
|
// The whole batch might be one incomplete document.
|
||||||
|
// Return DOCUMENT_TOO_LARGE if content was found (write_idx > 0), 0 if only commas.
|
||||||
|
return (parser.n_structural_indexes > 0) ? DOCUMENT_TOO_LARGE : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have at least one root comma. Documents before the last comma are complete.
|
||||||
|
next_batch_start = last_root_comma_pos + 1;
|
||||||
|
|
||||||
|
// Find the structural index cutoff: keep only structurals < last_root_comma_pos
|
||||||
|
uint32_t keep_count = 0;
|
||||||
|
for (uint32_t i = parser.n_structural_indexes; i > 0; i--) {
|
||||||
|
if (parser.structural_indexes[i - 1] < last_root_comma_pos) {
|
||||||
|
keep_count = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keep_count == 0) { return 0; }
|
||||||
|
|
||||||
|
// Use standard boundary detection on the complete portion
|
||||||
|
parser.n_structural_indexes = keep_count;
|
||||||
|
return find_next_document_index(parser);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace stage1
|
} // namespace stage1
|
||||||
} // unnamed namespace
|
} // unnamed namespace
|
||||||
} // namespace SIMDJSON_IMPLEMENTATION
|
} // namespace SIMDJSON_IMPLEMENTATION
|
||||||
|
|||||||
@@ -314,7 +314,6 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa
|
|||||||
return EMPTY;
|
return EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parser.n_structural_indexes = new_structural_indexes;
|
parser.n_structural_indexes = new_structural_indexes;
|
||||||
} else if (partial == stage1_mode::streaming_final) {
|
} else if (partial == stage1_mode::streaming_final) {
|
||||||
if(have_unclosed_string) { parser.n_structural_indexes--; }
|
if(have_unclosed_string) { parser.n_structural_indexes--; }
|
||||||
@@ -342,6 +341,56 @@ simdjson_inline error_code json_structural_indexer::finish(dom_parser_implementa
|
|||||||
// the trailing garbage.
|
// the trailing garbage.
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
} else if (partial == stage1_mode::json_sequence_partial) {
|
||||||
|
// RFC 7464: use RS positions for batch boundaries
|
||||||
|
if(have_unclosed_string) {
|
||||||
|
parser.n_structural_indexes--;
|
||||||
|
if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; }
|
||||||
|
}
|
||||||
|
uint32_t next_batch_start = uint32_t(len);
|
||||||
|
auto new_structural_indexes = find_next_document_index_json_sequence(parser, len, false, next_batch_start);
|
||||||
|
if (new_structural_indexes == DOCUMENT_TOO_LARGE) {
|
||||||
|
return CAPACITY;
|
||||||
|
}
|
||||||
|
if (new_structural_indexes == 0) {
|
||||||
|
parser.n_structural_indexes = 0;
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
parser.n_structural_indexes = new_structural_indexes;
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes] = next_batch_start;
|
||||||
|
} else if (partial == stage1_mode::json_sequence_final) {
|
||||||
|
// RFC 7464: final batch, last document extends to EOF
|
||||||
|
if(have_unclosed_string) { parser.n_structural_indexes--; }
|
||||||
|
uint32_t next_batch_start = uint32_t(len);
|
||||||
|
parser.n_structural_indexes = find_next_document_index_json_sequence(parser, len, true, next_batch_start);
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes];
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
|
||||||
|
if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return EMPTY; }
|
||||||
|
} else if (partial == stage1_mode::comma_delimited_partial) {
|
||||||
|
// Comma-delimited: filter root-level commas, use comma positions for batch boundaries
|
||||||
|
if(have_unclosed_string) {
|
||||||
|
parser.n_structural_indexes--;
|
||||||
|
if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return CAPACITY; }
|
||||||
|
}
|
||||||
|
uint32_t next_batch_start = uint32_t(len);
|
||||||
|
auto new_structural_indexes = filter_comma_delimited(parser, len, false, next_batch_start);
|
||||||
|
if (new_structural_indexes == DOCUMENT_TOO_LARGE) {
|
||||||
|
return CAPACITY;
|
||||||
|
}
|
||||||
|
if (new_structural_indexes == 0) {
|
||||||
|
parser.n_structural_indexes = 0;
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
parser.n_structural_indexes = new_structural_indexes;
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes] = next_batch_start;
|
||||||
|
} else if (partial == stage1_mode::comma_delimited_final) {
|
||||||
|
// Comma-delimited: final batch, last document extends to EOF
|
||||||
|
if(have_unclosed_string) { parser.n_structural_indexes--; }
|
||||||
|
uint32_t next_batch_start = uint32_t(len);
|
||||||
|
parser.n_structural_indexes = filter_comma_delimited(parser, len, true, next_batch_start);
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes + 1] = parser.structural_indexes[parser.n_structural_indexes];
|
||||||
|
parser.structural_indexes[parser.n_structural_indexes] = uint32_t(len);
|
||||||
|
if (simdjson_unlikely(parser.n_structural_indexes == 0u)) { return EMPTY; }
|
||||||
}
|
}
|
||||||
checker.check_eof();
|
checker.check_eof();
|
||||||
return checker.errors();
|
return checker.errors();
|
||||||
|
|||||||
@@ -52,13 +52,17 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
#elif defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)
|
#elif (defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)) || defined(__FILC__)
|
||||||
#include <cpuid.h>
|
#include <cpuid.h>
|
||||||
#endif
|
#endif
|
||||||
#if defined(__loongarch__) && defined(__linux__)
|
#if defined(__loongarch__) && defined(__linux__)
|
||||||
#include <sys/auxv.h>
|
#include <sys/auxv.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __FILC__
|
||||||
|
#include <stdfil.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace simdjson {
|
namespace simdjson {
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
@@ -109,7 +113,7 @@ static inline void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx,
|
|||||||
*ebx = cpu_info[1];
|
*ebx = cpu_info[1];
|
||||||
*ecx = cpu_info[2];
|
*ecx = cpu_info[2];
|
||||||
*edx = cpu_info[3];
|
*edx = cpu_info[3];
|
||||||
#elif defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)
|
#elif (defined(HAVE_GCC_GET_CPUID) && defined(USE_GCC_GET_CPUID)) || defined(__FILC__)
|
||||||
uint32_t level = *eax;
|
uint32_t level = *eax;
|
||||||
__get_cpuid(level, eax, ebx, ecx, edx);
|
__get_cpuid(level, eax, ebx, ecx, edx);
|
||||||
#else
|
#else
|
||||||
@@ -126,6 +130,8 @@ static inline void cpuid(uint32_t *eax, uint32_t *ebx, uint32_t *ecx,
|
|||||||
static inline uint64_t xgetbv() {
|
static inline uint64_t xgetbv() {
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
return _xgetbv(0);
|
return _xgetbv(0);
|
||||||
|
#elif defined(__FILC__)
|
||||||
|
return zxgetbv();
|
||||||
#else
|
#else
|
||||||
uint32_t xcr0_lo, xcr0_hi;
|
uint32_t xcr0_lo, xcr0_hi;
|
||||||
asm volatile("xgetbv\n\t" : "=a" (xcr0_lo), "=d" (xcr0_hi) : "c" (0));
|
asm volatile("xgetbv\n\t" : "=a" (xcr0_lo), "=d" (xcr0_hi) : "c" (0));
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ include(${PROJECT_SOURCE_DIR}/cmake/add_cpp_test.cmake)
|
|||||||
add_subdirectory(dom)
|
add_subdirectory(dom)
|
||||||
add_subdirectory(ondemand)
|
add_subdirectory(ondemand)
|
||||||
|
|
||||||
|
# compilation_failure_tests is added before the global link_libraries(simdjson) so that
|
||||||
|
# multiple_include/myexe does not receive a duplicate simdjson entry alongside the one
|
||||||
|
# already propagated by mylib PUBLIC simdjson::simdjson.
|
||||||
|
add_subdirectory(compilation_failure_tests)
|
||||||
|
|
||||||
# All remaining tests link with simdjson proper
|
# All remaining tests link with simdjson proper
|
||||||
link_libraries(simdjson)
|
link_libraries(simdjson)
|
||||||
@@ -27,6 +31,5 @@ endif()
|
|||||||
# SIMDJSON_FORCE_IMPLEMENTATION, so we know we're testing what we think we're testing
|
# SIMDJSON_FORCE_IMPLEMENTATION, so we know we're testing what we think we're testing
|
||||||
add_cpp_test(checkimplementation LABELS other per_implementation)
|
add_cpp_test(checkimplementation LABELS other per_implementation)
|
||||||
|
|
||||||
add_subdirectory(compilation_failure_tests)
|
|
||||||
add_subdirectory(builder)
|
add_subdirectory(builder)
|
||||||
add_subdirectory(compile_time)
|
add_subdirectory(compile_time)
|
||||||
@@ -14,6 +14,17 @@ function(add_dual_compile_test TEST_NAME)
|
|||||||
endfunction(add_dual_compile_test)
|
endfunction(add_dual_compile_test)
|
||||||
|
|
||||||
|
|
||||||
|
if(NOT BUILD_SHARED_LIBS)
|
||||||
|
# Add multiple_include BEFORE link_libraries(simdjson) so that myexe only receives
|
||||||
|
# simdjson once (through mylib PUBLIC simdjson::simdjson) and not a second time from
|
||||||
|
# a directory-wide link entry.
|
||||||
|
add_subdirectory(multiple_include)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Dual-compile tests build executables that reference simdjson symbols, so they need
|
||||||
|
# the library linked in.
|
||||||
|
link_libraries(simdjson)
|
||||||
|
|
||||||
add_dual_compile_test(example_compiletest)
|
add_dual_compile_test(example_compiletest)
|
||||||
# These don't compile with exceptions off
|
# These don't compile with exceptions off
|
||||||
if (SIMDJSON_EXCEPTIONS)
|
if (SIMDJSON_EXCEPTIONS)
|
||||||
@@ -25,8 +36,3 @@ if (SIMDJSON_EXCEPTIONS)
|
|||||||
add_dual_compile_test(dangling_parser_parse_padstring)
|
add_dual_compile_test(dangling_parser_parse_padstring)
|
||||||
add_dual_compile_test(unsafe_parse_many)
|
add_dual_compile_test(unsafe_parse_many)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT BUILD_SHARED_LIBS)
|
|
||||||
# We only check that it builds
|
|
||||||
add_subdirectory(multiple_include)
|
|
||||||
endif()
|
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
#ifndef SIMDJSON_TESTS_DOCUMENT_STREAM_FUZZ_TEST_COMMON_H
|
||||||
|
#define SIMDJSON_TESTS_DOCUMENT_STREAM_FUZZ_TEST_COMMON_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "simdjson.h"
|
||||||
|
|
||||||
|
namespace document_stream_fuzz {
|
||||||
|
|
||||||
|
constexpr uint64_t fixed_seed = 0x5eed1234ULL;
|
||||||
|
constexpr size_t batch_size = 512;
|
||||||
|
constexpr size_t minimum_total_bytes = 4096;
|
||||||
|
constexpr size_t minimum_document_count = 128;
|
||||||
|
|
||||||
|
struct stream_case {
|
||||||
|
const char *name;
|
||||||
|
simdjson::stream_format format;
|
||||||
|
std::string input;
|
||||||
|
std::vector<std::string> expected_documents;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline char random_char(std::mt19937_64 &rng) {
|
||||||
|
static constexpr char alphabet[] =
|
||||||
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"0123456789 _-/.";
|
||||||
|
std::uniform_int_distribution<size_t> dist(0, sizeof(alphabet) - 2);
|
||||||
|
return alphabet[dist(rng)];
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string make_ascii_text(std::mt19937_64 &rng, size_t min_length, size_t max_length) {
|
||||||
|
std::uniform_int_distribution<size_t> length_dist(min_length, max_length);
|
||||||
|
const size_t length = length_dist(rng);
|
||||||
|
std::string text;
|
||||||
|
text.reserve(length);
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
text.push_back(random_char(rng));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string quote_json_string(const std::string &text) {
|
||||||
|
std::string quoted;
|
||||||
|
quoted.reserve(text.size() + 2);
|
||||||
|
quoted.push_back('"');
|
||||||
|
for (char ch : text) {
|
||||||
|
if (ch == '\\' || ch == '"') {
|
||||||
|
quoted.push_back('\\');
|
||||||
|
}
|
||||||
|
quoted.push_back(ch);
|
||||||
|
}
|
||||||
|
quoted.push_back('"');
|
||||||
|
return quoted;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string make_integer_literal(std::mt19937_64 &rng) {
|
||||||
|
std::uniform_int_distribution<int64_t> dist(-1000000, 1000000);
|
||||||
|
return std::to_string(dist(rng));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string make_float_literal(std::mt19937_64 &rng) {
|
||||||
|
std::uniform_int_distribution<int64_t> whole_dist(-250000, 250000);
|
||||||
|
std::uniform_int_distribution<int64_t> fraction_dist(1, 9999);
|
||||||
|
return std::to_string(whole_dist(rng)) + "." + std::to_string(fraction_dist(rng));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string make_scalar(std::mt19937_64 &rng) {
|
||||||
|
std::uniform_int_distribution<int> type_dist(0, 4);
|
||||||
|
switch (type_dist(rng)) {
|
||||||
|
case 0: return quote_json_string(make_ascii_text(rng, 0, 20));
|
||||||
|
case 1: return make_integer_literal(rng);
|
||||||
|
case 2: return make_float_literal(rng);
|
||||||
|
case 3: return (rng() & 1) ? "true" : "false";
|
||||||
|
default: return "null";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string make_value(std::mt19937_64 &rng, int depth);
|
||||||
|
|
||||||
|
inline std::string make_array(std::mt19937_64 &rng, int depth) {
|
||||||
|
std::uniform_int_distribution<int> count_dist(0, depth == 0 ? 5 : 3);
|
||||||
|
const int count = count_dist(rng);
|
||||||
|
std::string out = "[";
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
out.push_back(',');
|
||||||
|
}
|
||||||
|
out += make_value(rng, depth + 1);
|
||||||
|
}
|
||||||
|
out.push_back(']');
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string make_object(std::mt19937_64 &rng, int depth) {
|
||||||
|
std::uniform_int_distribution<int> count_dist(0, depth == 0 ? 5 : 3);
|
||||||
|
const int count = count_dist(rng);
|
||||||
|
std::string out = "{";
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
out.push_back(',');
|
||||||
|
}
|
||||||
|
std::string key = "k";
|
||||||
|
key += std::to_string(depth);
|
||||||
|
key += '_';
|
||||||
|
key += std::to_string(i);
|
||||||
|
key += '_';
|
||||||
|
key += make_ascii_text(rng, 1, 6);
|
||||||
|
out += quote_json_string(key);
|
||||||
|
out.push_back(':');
|
||||||
|
out += make_value(rng, depth + 1);
|
||||||
|
}
|
||||||
|
out.push_back('}');
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string make_value(std::mt19937_64 &rng, int depth) {
|
||||||
|
if (depth >= 3) {
|
||||||
|
return make_scalar(rng);
|
||||||
|
}
|
||||||
|
std::uniform_int_distribution<int> type_dist(0, 6);
|
||||||
|
switch (type_dist(rng)) {
|
||||||
|
case 0: return quote_json_string(make_ascii_text(rng, 0, 20));
|
||||||
|
case 1: return make_integer_literal(rng);
|
||||||
|
case 2: return make_float_literal(rng);
|
||||||
|
case 3: return (rng() & 1) ? "true" : "false";
|
||||||
|
case 4: return "null";
|
||||||
|
case 5: return make_array(rng, depth);
|
||||||
|
default: return make_object(rng, depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<std::string> make_documents() {
|
||||||
|
std::vector<std::string> docs = {
|
||||||
|
"0",
|
||||||
|
"-17",
|
||||||
|
"3.125",
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
"null",
|
||||||
|
"\"alpha beta\"",
|
||||||
|
"[]",
|
||||||
|
"[1,true,\"x\"]",
|
||||||
|
"{}",
|
||||||
|
"{\"a\":1,\"b\":[2,3],\"c\":{\"d\":false}}"
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t total_bytes = 0;
|
||||||
|
for (const auto &doc : docs) {
|
||||||
|
total_bytes += doc.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::mt19937_64 rng(fixed_seed);
|
||||||
|
while (docs.size() < minimum_document_count || total_bytes < minimum_total_bytes) {
|
||||||
|
docs.push_back(make_value(rng, 0));
|
||||||
|
total_bytes += docs.back().size();
|
||||||
|
}
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<std::string> make_wrapped_documents(const std::vector<std::string> &docs) {
|
||||||
|
std::vector<std::string> wrapped;
|
||||||
|
wrapped.reserve(docs.size());
|
||||||
|
for (size_t i = 0; i < docs.size(); i++) {
|
||||||
|
wrapped.push_back("{\"id\":" + std::to_string(i) + ",\"value\":" + docs[i] + "}");
|
||||||
|
}
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string build_whitespace_input(const std::vector<std::string> &docs) {
|
||||||
|
static const char *separators[] = {" ", "\n", "\r\n", "\t", " \n\t", "\r\t "};
|
||||||
|
std::string out = " \n\t";
|
||||||
|
for (size_t i = 0; i < docs.size(); i++) {
|
||||||
|
out += docs[i];
|
||||||
|
if (i + 1 < docs.size()) {
|
||||||
|
out += separators[i % (sizeof(separators) / sizeof(separators[0]))];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out += "\n\t ";
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string build_json_sequence_input(const std::vector<std::string> &docs) {
|
||||||
|
std::string out;
|
||||||
|
for (size_t i = 0; i < docs.size(); i++) {
|
||||||
|
out.push_back('\x1e');
|
||||||
|
out += docs[i];
|
||||||
|
out.push_back('\n');
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string build_comma_delimited_input(const std::vector<std::string> &docs) {
|
||||||
|
std::string out;
|
||||||
|
for (size_t i = 0; i < docs.size(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
out += (i % 3 == 0) ? ",\n" : ", ";
|
||||||
|
}
|
||||||
|
out += docs[i];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string build_comma_delimited_array_input(const std::vector<std::string> &docs) {
|
||||||
|
std::string out = " \t\n[";
|
||||||
|
for (size_t i = 0; i < docs.size(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
out += (i % 4 == 0) ? ",\n" : ", ";
|
||||||
|
}
|
||||||
|
out += docs[i];
|
||||||
|
}
|
||||||
|
out += "]\r\n";
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<stream_case> make_stream_cases(const std::vector<std::string> &docs) {
|
||||||
|
std::vector<std::string> whitespace_docs = make_wrapped_documents(docs);
|
||||||
|
return {
|
||||||
|
{"whitespace_delimited", simdjson::stream_format::whitespace_delimited, build_whitespace_input(whitespace_docs), whitespace_docs},
|
||||||
|
{"json_sequence", simdjson::stream_format::json_sequence, build_json_sequence_input(whitespace_docs), whitespace_docs},
|
||||||
|
{"comma_delimited", simdjson::stream_format::comma_delimited, build_comma_delimited_input(whitespace_docs), whitespace_docs},
|
||||||
|
{"comma_delimited_array", simdjson::stream_format::comma_delimited_array, build_comma_delimited_array_input(whitespace_docs), whitespace_docs}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace document_stream_fuzz
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -7,6 +7,7 @@ if(NOT SIMDJSON_LEGACY_VISUAL_STUDIO AND NOT SIMDJSON_WINDOWS_DLL)
|
|||||||
endif()
|
endif()
|
||||||
add_cpp_test(basictests LABELS dom acceptance per_implementation)
|
add_cpp_test(basictests LABELS dom acceptance per_implementation)
|
||||||
add_cpp_test(document_stream_tests LABELS dom acceptance per_implementation)
|
add_cpp_test(document_stream_tests LABELS dom acceptance per_implementation)
|
||||||
|
add_cpp_test(document_stream_fuzz_tests LABELS dom acceptance per_implementation)
|
||||||
add_cpp_test(document_tests LABELS dom acceptance per_implementation)
|
add_cpp_test(document_tests LABELS dom acceptance per_implementation)
|
||||||
add_cpp_test(errortests LABELS dom acceptance per_implementation)
|
add_cpp_test(errortests LABELS dom acceptance per_implementation)
|
||||||
add_cpp_test(extracting_values_example LABELS dom acceptance per_implementation)
|
add_cpp_test(extracting_values_example LABELS dom acceptance per_implementation)
|
||||||
@@ -126,14 +127,16 @@ endif()
|
|||||||
# 1. Visual Studio 2022 v17.6 or later
|
# 1. Visual Studio 2022 v17.6 or later
|
||||||
# 2. GCC v14.0.0 or later (GCC v13.0.0 cannot handle pipe operator of lambda)
|
# 2. GCC v14.0.0 or later (GCC v13.0.0 cannot handle pipe operator of lambda)
|
||||||
# 3. Clang v15.0.0 or later (certain version C++ headers occur error when compiling)
|
# 3. Clang v15.0.0 or later (certain version C++ headers occur error when compiling)
|
||||||
|
# 4. or if we are targeting C++20 or better
|
||||||
if(
|
if(
|
||||||
(MSVC AND MSVC_VERSION LESS 1930) OR
|
(MSVC AND MSVC_VERSION GREATER_EQUAL 1930) OR
|
||||||
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.0.0") OR
|
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.0.0") OR
|
||||||
(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "15.0.0")
|
(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "15.0.0") OR
|
||||||
|
(CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||||
)
|
)
|
||||||
message(STATUS "compiler id: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}")
|
message(STATUS "compiler id: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||||
add_cpp_test(ranges_test LABELS dom acceptance per_implementation)
|
add_cpp_test(ranges_test LABELS dom acceptance per_implementation)
|
||||||
if(NOT SIMDJSON_STATIC_REFLECTION)
|
if(NOT SIMDJSON_STATIC_REFLECTION AND NOT CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||||
set_target_properties(ranges_test PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
|
set_target_properties(ranges_test PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "simdjson.h"
|
||||||
|
#include "test_macros.h"
|
||||||
|
#include "test_main.h"
|
||||||
|
#include "document_stream_fuzz_test_common.h"
|
||||||
|
|
||||||
|
namespace document_stream_fuzz_tests {
|
||||||
|
|
||||||
|
std::string strip_stream_artifacts(simdjson::stream_format format, std::string_view text) {
|
||||||
|
size_t start = 0;
|
||||||
|
size_t end = text.size();
|
||||||
|
while (start < end && (text[start] == ' ' || text[start] == '\t' || text[start] == '\n' || text[start] == '\r' || text[start] == '\x1e')) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
while (end > start && (text[end - 1] == ' ' || text[end - 1] == '\t' || text[end - 1] == '\n' || text[end - 1] == '\r' || text[end - 1] == '\x1e')) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
std::string cleaned(text.substr(start, end - start));
|
||||||
|
if (format == simdjson::stream_format::comma_delimited || format == simdjson::stream_format::comma_delimited_array) {
|
||||||
|
while (!cleaned.empty() && cleaned.back() == ',') {
|
||||||
|
cleaned.pop_back();
|
||||||
|
while (!cleaned.empty() && (cleaned.back() == ' ' || cleaned.back() == '\t' || cleaned.back() == '\n' || cleaned.back() == '\r')) {
|
||||||
|
cleaned.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string canonicalize_document(simdjson::stream_format format, std::string_view text) {
|
||||||
|
std::string cleaned = strip_stream_artifacts(format, text);
|
||||||
|
simdjson::dom::parser parser;
|
||||||
|
simdjson::dom::element doc;
|
||||||
|
if (parser.parse(cleaned).get(doc)) {
|
||||||
|
return std::string("PARSE_ERROR:") + cleaned;
|
||||||
|
}
|
||||||
|
return simdjson::minify(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string> &expected_documents() {
|
||||||
|
static const std::vector<std::string> docs = document_stream_fuzz::make_documents();
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<document_stream_fuzz::stream_case> &stream_cases() {
|
||||||
|
static const std::vector<document_stream_fuzz::stream_case> cases =
|
||||||
|
document_stream_fuzz::make_stream_cases(expected_documents());
|
||||||
|
return cases;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verify_case(const document_stream_fuzz::stream_case &test_case) {
|
||||||
|
TEST_START();
|
||||||
|
const auto &expected = test_case.expected_documents;
|
||||||
|
ASSERT_TRUE(test_case.input.size() > document_stream_fuzz::batch_size * 4);
|
||||||
|
|
||||||
|
simdjson::padded_string input(test_case.input);
|
||||||
|
simdjson::dom::parser parser;
|
||||||
|
|
||||||
|
for (int pass = 0; pass < 2; pass++) {
|
||||||
|
simdjson::dom::document_stream stream;
|
||||||
|
ASSERT_SUCCESS(parser.parse_many(input, document_stream_fuzz::batch_size, test_case.format).get(stream));
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
for (auto doc : stream) {
|
||||||
|
ASSERT_SUCCESS(doc.error());
|
||||||
|
ASSERT_TRUE(index < expected.size());
|
||||||
|
simdjson::dom::element el;
|
||||||
|
ASSERT_SUCCESS(doc.get(el));
|
||||||
|
ASSERT_EQUAL(
|
||||||
|
canonicalize_document(test_case.format, simdjson::minify(el)),
|
||||||
|
canonicalize_document(test_case.format, expected[index])
|
||||||
|
);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(index, expected.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool run() {
|
||||||
|
for (const auto &test_case : stream_cases()) {
|
||||||
|
std::cout << "Running fuzz corpus against stream format: " << test_case.name << std::endl;
|
||||||
|
if (!verify_case(test_case)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace document_stream_fuzz_tests
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
return test_main(argc, argv, document_stream_fuzz_tests::run);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ add_cpp_test(ondemand_array_tests LABELS ondemand acceptance
|
|||||||
add_cpp_test(ondemand_array_error_tests LABELS ondemand acceptance per_implementation)
|
add_cpp_test(ondemand_array_error_tests LABELS ondemand acceptance per_implementation)
|
||||||
add_cpp_test(ondemand_compilation_tests LABELS ondemand acceptance per_implementation)
|
add_cpp_test(ondemand_compilation_tests LABELS ondemand acceptance per_implementation)
|
||||||
add_cpp_test(ondemand_document_stream_tests LABELS ondemand acceptance per_implementation)
|
add_cpp_test(ondemand_document_stream_tests LABELS ondemand acceptance per_implementation)
|
||||||
|
add_cpp_test(ondemand_document_stream_fuzz_tests LABELS ondemand acceptance per_implementation)
|
||||||
add_cpp_test(ondemand_error_tests LABELS ondemand acceptance per_implementation)
|
add_cpp_test(ondemand_error_tests LABELS ondemand acceptance per_implementation)
|
||||||
add_cpp_test(ondemand_error_location_tests LABELS ondemand acceptance per_implementation)
|
add_cpp_test(ondemand_error_location_tests LABELS ondemand acceptance per_implementation)
|
||||||
add_cpp_test(ondemand_json_pointer_tests LABELS ondemand acceptance per_implementation)
|
add_cpp_test(ondemand_json_pointer_tests LABELS ondemand acceptance per_implementation)
|
||||||
@@ -44,6 +45,25 @@ if(NOT SIMDJSON_SANITIZE)
|
|||||||
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
|
add_cpp_test(ondemand_cacheline LABELS ondemand acceptance per_implementation)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Add the tests if we're on:
|
||||||
|
# 1. Visual Studio 2022 v17.6 or later
|
||||||
|
# 2. GCC v14.0.0 or later (GCC v13.0.0 cannot handle pipe operator of lambda)
|
||||||
|
# 3. Clang v15.0.0 or later (certain version C++ headers occur error when compiling)
|
||||||
|
# 4. or if we are targeting C++20 or better
|
||||||
|
if(
|
||||||
|
(MSVC AND MSVC_VERSION GREATER_EQUAL 1930) OR
|
||||||
|
(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "14.0.0") OR
|
||||||
|
(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "15.0.0") OR
|
||||||
|
(CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||||
|
)
|
||||||
|
message(STATUS "compiler id: ${CMAKE_CXX_COMPILER_ID} version: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||||
|
add_cpp_test(ondemand_ranges_tests LABELS ondemand acceptance per_implementation)
|
||||||
|
if(NOT SIMDJSON_STATIC_REFLECTION AND NOT CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||||
|
set_target_properties(ondemand_ranges_tests PROPERTIES CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
if(HAVE_POSIX_FORK AND HAVE_POSIX_WAIT) # assert tests use fork and wait, which aren't on MSVC
|
if(HAVE_POSIX_FORK AND HAVE_POSIX_WAIT) # assert tests use fork and wait, which aren't on MSVC
|
||||||
add_cpp_test(ondemand_assert_out_of_order_values LABELS assert per_implementation explicitonly ondemand)
|
add_cpp_test(ondemand_assert_out_of_order_values LABELS assert per_implementation explicitonly ondemand)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
#ifdef __FILC__
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
int main() {
|
||||||
|
printf("This test is not relevant for FILC.\n");
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
#else // This test is not relevant for FILC
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <sysinfoapi.h>
|
#include <sysinfoapi.h>
|
||||||
@@ -87,3 +96,5 @@ int main() {
|
|||||||
}
|
}
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // This test is not relevant for FILC
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "simdjson.h"
|
||||||
|
#include "test_ondemand.h"
|
||||||
|
#include "document_stream_fuzz_test_common.h"
|
||||||
|
|
||||||
|
namespace document_stream_fuzz_tests {
|
||||||
|
|
||||||
|
std::string strip_stream_artifacts(simdjson::stream_format format, std::string_view text) {
|
||||||
|
size_t start = 0;
|
||||||
|
size_t end = text.size();
|
||||||
|
while (start < end && (text[start] == ' ' || text[start] == '\t' || text[start] == '\n' || text[start] == '\r' || text[start] == '\x1e')) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
while (end > start && (text[end - 1] == ' ' || text[end - 1] == '\t' || text[end - 1] == '\n' || text[end - 1] == '\r' || text[end - 1] == '\x1e')) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
std::string cleaned(text.substr(start, end - start));
|
||||||
|
if (format == simdjson::stream_format::comma_delimited || format == simdjson::stream_format::comma_delimited_array) {
|
||||||
|
while (!cleaned.empty() && cleaned.back() == ',') {
|
||||||
|
cleaned.pop_back();
|
||||||
|
while (!cleaned.empty() && (cleaned.back() == ' ' || cleaned.back() == '\t' || cleaned.back() == '\n' || cleaned.back() == '\r')) {
|
||||||
|
cleaned.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string canonicalize_document(simdjson::stream_format format, std::string_view text) {
|
||||||
|
std::string cleaned = strip_stream_artifacts(format, text);
|
||||||
|
simdjson::dom::parser parser;
|
||||||
|
simdjson::dom::element doc;
|
||||||
|
if (parser.parse(cleaned).get(doc)) {
|
||||||
|
return std::string("PARSE_ERROR:") + cleaned;
|
||||||
|
}
|
||||||
|
return simdjson::minify(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string> &expected_documents() {
|
||||||
|
static const std::vector<std::string> docs = document_stream_fuzz::make_documents();
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<document_stream_fuzz::stream_case> &stream_cases() {
|
||||||
|
static const std::vector<document_stream_fuzz::stream_case> cases =
|
||||||
|
document_stream_fuzz::make_stream_cases(expected_documents());
|
||||||
|
return cases;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verify_case(const document_stream_fuzz::stream_case &test_case) {
|
||||||
|
TEST_START();
|
||||||
|
const auto &expected = test_case.expected_documents;
|
||||||
|
ASSERT_TRUE(test_case.input.size() > document_stream_fuzz::batch_size * 4);
|
||||||
|
|
||||||
|
simdjson::padded_string input(test_case.input);
|
||||||
|
simdjson::ondemand::parser parser;
|
||||||
|
|
||||||
|
for (int pass = 0; pass < 2; pass++) {
|
||||||
|
simdjson::ondemand::document_stream stream;
|
||||||
|
ASSERT_SUCCESS(parser.iterate_many(input, document_stream_fuzz::batch_size, test_case.format).get(stream));
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
for (auto it = stream.begin(); it != stream.end(); ++it) {
|
||||||
|
auto doc_result = *it;
|
||||||
|
ASSERT_SUCCESS(doc_result.error());
|
||||||
|
ASSERT_TRUE(index < expected.size());
|
||||||
|
|
||||||
|
simdjson::ondemand::document_reference doc;
|
||||||
|
ASSERT_SUCCESS(doc_result.get(doc));
|
||||||
|
|
||||||
|
std::string_view actual;
|
||||||
|
ASSERT_SUCCESS(simdjson::to_json_string(doc).get(actual));
|
||||||
|
ASSERT_EQUAL(
|
||||||
|
canonicalize_document(test_case.format, actual),
|
||||||
|
canonicalize_document(test_case.format, expected[index])
|
||||||
|
);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(index, expected.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool run() {
|
||||||
|
for (const auto &test_case : stream_cases()) {
|
||||||
|
std::cout << "Running fuzz corpus against stream format: " << test_case.name << std::endl;
|
||||||
|
if (!verify_case(test_case)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace document_stream_fuzz_tests
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
return test_main(argc, argv, document_stream_fuzz_tests::run);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ bool normal() {
|
|||||||
auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
|
auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
|
||||||
ondemand::parser parser;
|
ondemand::parser parser;
|
||||||
ondemand::document_stream doc_stream;
|
ondemand::document_stream doc_stream;
|
||||||
ASSERT_SUCCESS(parser.iterate_many(json, json.size(), true).get(doc_stream));
|
ASSERT_SUCCESS(parser.iterate_many(json, json.size(), stream_format::comma_delimited).get(doc_stream));
|
||||||
|
|
||||||
for (auto doc : doc_stream)
|
for (auto doc : doc_stream)
|
||||||
{
|
{
|
||||||
@@ -28,7 +28,7 @@ bool small_batch_size() {
|
|||||||
auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
|
auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
|
||||||
ondemand::parser parser;
|
ondemand::parser parser;
|
||||||
ondemand::document_stream doc_stream;
|
ondemand::document_stream doc_stream;
|
||||||
ASSERT_SUCCESS(parser.iterate_many(json, 32, true).get(doc_stream));
|
ASSERT_SUCCESS(parser.iterate_many(json, 32, stream_format::comma_delimited).get(doc_stream));
|
||||||
|
|
||||||
for (auto doc : doc_stream)
|
for (auto doc : doc_stream)
|
||||||
{
|
{
|
||||||
@@ -43,7 +43,7 @@ bool trailing_comma() {
|
|||||||
auto json = R"(1,)"_padded;
|
auto json = R"(1,)"_padded;
|
||||||
ondemand::parser parser;
|
ondemand::parser parser;
|
||||||
ondemand::document_stream doc_stream;
|
ondemand::document_stream doc_stream;
|
||||||
ASSERT_SUCCESS(parser.iterate_many(json, json.size(), true).get(doc_stream));
|
ASSERT_SUCCESS(parser.iterate_many(json, json.size(), stream_format::comma_delimited).get(doc_stream));
|
||||||
|
|
||||||
for (auto doc : doc_stream)
|
for (auto doc : doc_stream)
|
||||||
{
|
{
|
||||||
@@ -59,7 +59,7 @@ bool check_parsed_values() {
|
|||||||
auto json = R"( 1 , "a" , [100, 1] , {"hello" : "world"} , )"_padded;
|
auto json = R"( 1 , "a" , [100, 1] , {"hello" : "world"} , )"_padded;
|
||||||
ondemand::parser parser;
|
ondemand::parser parser;
|
||||||
ondemand::document_stream doc_stream;
|
ondemand::document_stream doc_stream;
|
||||||
ASSERT_SUCCESS(parser.iterate_many(json, json.size(), true).get(doc_stream));
|
ASSERT_SUCCESS(parser.iterate_many(json, json.size(), stream_format::comma_delimited).get(doc_stream));
|
||||||
|
|
||||||
auto begin = doc_stream.begin();
|
auto begin = doc_stream.begin();
|
||||||
auto end = doc_stream.end();
|
auto end = doc_stream.end();
|
||||||
@@ -127,7 +127,7 @@ bool leading_comma() {
|
|||||||
auto json = R"(,1)"_padded;
|
auto json = R"(,1)"_padded;
|
||||||
ondemand::parser parser;
|
ondemand::parser parser;
|
||||||
ondemand::document_stream doc_stream;
|
ondemand::document_stream doc_stream;
|
||||||
ASSERT_SUCCESS(parser.iterate_many(json, json.size(), true).get(doc_stream));
|
ASSERT_SUCCESS(parser.iterate_many(json, json.size(), stream_format::comma_delimited).get(doc_stream));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto begin = doc_stream.begin();
|
auto begin = doc_stream.begin();
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
#ifdef __FILC__
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
int main() {
|
||||||
|
printf("This test is not relevant for FILC.\n");
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
#else // This test is not relevant for FILC
|
||||||
|
|
||||||
#include "simdjson.h"
|
#include "simdjson.h"
|
||||||
#include "simdjson/padded_string_view.h"
|
#include "simdjson/padded_string_view.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -195,3 +204,5 @@ int main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // SIMDJSON_CPLUSPLUS17
|
#endif // SIMDJSON_CPLUSPLUS17
|
||||||
|
|
||||||
|
#endif // This test is not relevant for FILC
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include "simdjson.h"
|
||||||
|
#include "test_macros.h"
|
||||||
|
#include "test_main.h"
|
||||||
|
|
||||||
|
#if SIMDJSON_SUPPORTS_RANGES
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ranges>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace simdjson;
|
||||||
|
|
||||||
|
namespace ondemand_ranges_tests {
|
||||||
|
|
||||||
|
#if SIMDJSON_EXCEPTIONS
|
||||||
|
|
||||||
|
bool array_get_range_basic() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"([10, 20, 30])"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto arr = doc.get_array();
|
||||||
|
auto range = ondemand::get_range(arr);
|
||||||
|
|
||||||
|
std::vector<int64_t> values;
|
||||||
|
for (auto elem : range) {
|
||||||
|
values.push_back(int64_t(elem));
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(values.size(), size_t(3));
|
||||||
|
ASSERT_EQUAL(values[0], int64_t(10));
|
||||||
|
ASSERT_EQUAL(values[1], int64_t(20));
|
||||||
|
ASSERT_EQUAL(values[2], int64_t(30));
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool array_range_with_transform() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"([1, 2, 3, 4, 5])"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto arr = doc.get_array();
|
||||||
|
|
||||||
|
auto doubled = ondemand::get_range(arr)
|
||||||
|
| std::views::transform([](auto v) -> int64_t { return int64_t(v) * 2; });
|
||||||
|
|
||||||
|
std::vector<int64_t> values;
|
||||||
|
for (auto val : doubled) {
|
||||||
|
values.push_back(val);
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(values.size(), size_t(5));
|
||||||
|
ASSERT_EQUAL(values[0], int64_t(2));
|
||||||
|
ASSERT_EQUAL(values[1], int64_t(4));
|
||||||
|
ASSERT_EQUAL(values[2], int64_t(6));
|
||||||
|
ASSERT_EQUAL(values[3], int64_t(8));
|
||||||
|
ASSERT_EQUAL(values[4], int64_t(10));
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool array_range_strings() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"(["alpha", "beta", "gamma"])"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto arr = doc.get_array();
|
||||||
|
|
||||||
|
auto to_string = [](auto v) -> std::string {
|
||||||
|
return std::string(std::string_view(v));
|
||||||
|
};
|
||||||
|
auto strings = ondemand::get_range(arr) | std::views::transform(to_string);
|
||||||
|
|
||||||
|
std::vector<std::string> values;
|
||||||
|
for (auto s : strings) {
|
||||||
|
values.push_back(s);
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(values.size(), size_t(3));
|
||||||
|
ASSERT_TRUE(values[0] == "alpha");
|
||||||
|
ASSERT_TRUE(values[1] == "beta");
|
||||||
|
ASSERT_TRUE(values[2] == "gamma");
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool array_range_empty() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"([])"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto arr = doc.get_array();
|
||||||
|
auto range = ondemand::get_range(arr);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (simdjson_unused auto elem : range) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(count, 0);
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool array_range_nested() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"([{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}])"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto arr = doc.get_array();
|
||||||
|
|
||||||
|
auto get_name = [](auto v) -> std::string {
|
||||||
|
return std::string(std::string_view(v["name"]));
|
||||||
|
};
|
||||||
|
auto names = ondemand::get_range(arr) | std::views::transform(get_name);
|
||||||
|
|
||||||
|
std::vector<std::string> values;
|
||||||
|
for (auto name : names) {
|
||||||
|
values.push_back(name);
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(values.size(), size_t(2));
|
||||||
|
ASSERT_TRUE(values[0] == "Alice");
|
||||||
|
ASSERT_TRUE(values[1] == "Bob");
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool object_get_range_basic() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"({"a": 1, "b": 2, "c": 3})"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto obj = doc.get_object();
|
||||||
|
auto range = ondemand::get_key_value_range(obj);
|
||||||
|
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
std::vector<int64_t> vals;
|
||||||
|
for (auto field_result : range) {
|
||||||
|
keys.push_back(std::string(std::string_view(field_result.escaped_key())));
|
||||||
|
vals.push_back(int64_t(field_result.value()));
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(keys.size(), size_t(3));
|
||||||
|
ASSERT_TRUE(keys[0] == "a");
|
||||||
|
ASSERT_TRUE(keys[1] == "b");
|
||||||
|
ASSERT_TRUE(keys[2] == "c");
|
||||||
|
ASSERT_EQUAL(vals[0], int64_t(1));
|
||||||
|
ASSERT_EQUAL(vals[1], int64_t(2));
|
||||||
|
ASSERT_EQUAL(vals[2], int64_t(3));
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool object_range_with_transform() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"({"x": 10, "y": 20, "z": 30})"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto obj = doc.get_object();
|
||||||
|
|
||||||
|
auto get_key = [](auto field_result) -> std::string {
|
||||||
|
return std::string(std::string_view(field_result.escaped_key()));
|
||||||
|
};
|
||||||
|
auto keys = ondemand::get_key_value_range(obj) | std::views::transform(get_key);
|
||||||
|
|
||||||
|
std::vector<std::string> values;
|
||||||
|
for (auto k : keys) {
|
||||||
|
values.push_back(k);
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(values.size(), size_t(3));
|
||||||
|
ASSERT_TRUE(values[0] == "x");
|
||||||
|
ASSERT_TRUE(values[1] == "y");
|
||||||
|
ASSERT_TRUE(values[2] == "z");
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool object_range_empty() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"({})"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto obj = doc.get_object();
|
||||||
|
auto range = ondemand::get_key_value_range(obj);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (simdjson_unused auto elem : range) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(count, 0);
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_range_from_result() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"([100, 200, 300])"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
|
||||||
|
// get_range with simdjson_result<array> - unwraps automatically
|
||||||
|
auto range = ondemand::get_range(doc.get_array());
|
||||||
|
|
||||||
|
std::vector<int64_t> values;
|
||||||
|
for (auto elem : range) {
|
||||||
|
values.push_back(int64_t(elem));
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(values.size(), size_t(3));
|
||||||
|
ASSERT_EQUAL(values[0], int64_t(100));
|
||||||
|
ASSERT_EQUAL(values[1], int64_t(200));
|
||||||
|
ASSERT_EQUAL(values[2], int64_t(300));
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool object_range_key_iteration() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"({"name": "Alice", "age": 30, "city": "New York"})"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
auto obj = doc.get_object();
|
||||||
|
|
||||||
|
// Test the specific pattern: iterating over field_result.key()
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
for (auto field_result : ondemand::get_key_value_range(obj)) {
|
||||||
|
keys.push_back(std::string(field_result.escaped_key().value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_EQUAL(keys.size(), size_t(3));
|
||||||
|
bool has_name = false, has_age = false, has_city = false;
|
||||||
|
for (const auto& key : keys) {
|
||||||
|
if (key == "name") has_name = true;
|
||||||
|
else if (key == "age") has_age = true;
|
||||||
|
else if (key == "city") has_city = true;
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(has_name);
|
||||||
|
ASSERT_TRUE(has_age);
|
||||||
|
ASSERT_TRUE(has_city);
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the types satisfy the expected C++20 concepts.
|
||||||
|
bool concept_checks() {
|
||||||
|
TEST_START();
|
||||||
|
static_assert(std::input_iterator<ondemand::array_range_iterator>);
|
||||||
|
static_assert(std::input_iterator<ondemand::object_range_iterator>);
|
||||||
|
static_assert(std::ranges::input_range<ondemand::array_range>);
|
||||||
|
static_assert(std::ranges::input_range<ondemand::object_range>);
|
||||||
|
static_assert(std::ranges::view<ondemand::array_range>);
|
||||||
|
static_assert(std::ranges::view<ondemand::object_range>);
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SIMDJSON_EXCEPTIONS
|
||||||
|
|
||||||
|
// These tests work without exceptions.
|
||||||
|
bool array_range_noexcept_basic() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"([1, 2, 3])"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
ondemand::array arr;
|
||||||
|
ASSERT_SUCCESS(doc.get_array().get(arr));
|
||||||
|
auto range = ondemand::get_range(arr);
|
||||||
|
ASSERT_SUCCESS(range.error());
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (auto elem : range) {
|
||||||
|
int64_t val;
|
||||||
|
ASSERT_SUCCESS(elem.get_int64().get(val));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(count, 3);
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool object_range_noexcept_basic() {
|
||||||
|
TEST_START();
|
||||||
|
auto json = R"({"a": 1, "b": 2})"_padded;
|
||||||
|
ondemand::parser parser;
|
||||||
|
auto doc = parser.iterate(json);
|
||||||
|
ondemand::object obj;
|
||||||
|
ASSERT_SUCCESS(doc.get_object().get(obj));
|
||||||
|
auto range = ondemand::get_key_value_range(obj);
|
||||||
|
ASSERT_SUCCESS(range.error());
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (auto field_result : range) {
|
||||||
|
simdjson_unused ondemand::field f;
|
||||||
|
ASSERT_SUCCESS(std::move(field_result).get(f));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
ASSERT_EQUAL(count, 2);
|
||||||
|
TEST_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool run() {
|
||||||
|
return
|
||||||
|
array_range_noexcept_basic() &&
|
||||||
|
object_range_noexcept_basic() &&
|
||||||
|
#if SIMDJSON_EXCEPTIONS
|
||||||
|
concept_checks() &&
|
||||||
|
array_get_range_basic() &&
|
||||||
|
array_range_with_transform() &&
|
||||||
|
array_range_strings() &&
|
||||||
|
array_range_empty() &&
|
||||||
|
array_range_nested() &&
|
||||||
|
object_get_range_basic() &&
|
||||||
|
object_range_with_transform() &&
|
||||||
|
object_range_empty() &&
|
||||||
|
get_range_from_result() &&
|
||||||
|
object_range_key_iteration() &&
|
||||||
|
#endif // SIMDJSON_EXCEPTIONS
|
||||||
|
true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ondemand_ranges_tests
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
return test_main(argc, argv, ondemand_ranges_tests::run);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // !SIMDJSON_SUPPORTS_RANGES
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Ranges tests require C++20 ranges support, skipping." << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SIMDJSON_SUPPORTS_RANGES
|
||||||
@@ -1706,10 +1706,7 @@ bool allow_comma_separated_example() {
|
|||||||
auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
|
auto json = R"( 1, 2, 3, 4, "a", "b", "c", {"hello": "world"} , [1, 2, 3])"_padded;
|
||||||
ondemand::parser parser;
|
ondemand::parser parser;
|
||||||
ondemand::document_stream doc_stream;
|
ondemand::document_stream doc_stream;
|
||||||
// We pass '32' as the batch size, but it is a bogus parameter because, since
|
auto error = parser.iterate_many(json, 32, simdjson::stream_format::comma_delimited).get(doc_stream);
|
||||||
// we pass 'true' to the allow_comma parameter, the batch size will be set to at least
|
|
||||||
// the document size.
|
|
||||||
auto error = parser.iterate_many(json, 32, true).get(doc_stream);
|
|
||||||
if(error) { std::cerr << error << std::endl; return false; }
|
if(error) { std::cerr << error << std::endl; return false; }
|
||||||
for (auto doc : doc_stream) {
|
for (auto doc : doc_stream) {
|
||||||
std::cout << doc.type() << std::endl;
|
std::cout << doc.type() << std::endl;
|
||||||
|
|||||||
Reference in New Issue
Block a user