Compare commits

..

5 Commits

Author SHA1 Message Date
Francisco Geiman Thiesen d326f2ce9f Working! 2025-10-10 21:32:10 -07:00
Francisco Geiman Thiesen ca42a49fba Compile-time support for parsing json objects! 2025-10-10 18:40:09 -07:00
0xflotus a7f8fb71c5 chore: fix small error in docs (#2497) 2025-10-03 11:03:17 -04:00
Howard Guo a553db4c67 Update workflow name to Ubuntu aarch64 (GCC 13) (#2484) 2025-10-03 09:56:07 -04:00
Jaël Champagne Gareau 1fa1af8c15 Fix yyjson leaks when running ./bench_ondemand (#2485) 2025-10-03 09:55:32 -04:00
15 changed files with 1057 additions and 16 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
name: Ubuntu ppc64le (GCC 11)
name: Ubuntu aarch64 (GCC 13)
on:
push:
+9 -2
View File
@@ -44,7 +44,10 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<uint64_t> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(distinct_user_id, yyjson)->UseManualTime();
@@ -52,11 +55,15 @@ BENCHMARK_TEMPLATE(distinct_user_id, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<uint64_t> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(distinct_user_id, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace distinct_user_id
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -2
View File
@@ -36,18 +36,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, uint64_t find_id, std::string_view &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), find_id, result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, find_id, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(find_tweet, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, uint64_t find_id, std::string_view &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), find_id, result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, find_id, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(find_tweet, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace find_tweet
#endif // SIMDJSON_COMPETITION_YYJSON
+3 -1
View File
@@ -100,6 +100,7 @@ struct yyjson : yyjson2msgpack {
std::string_view &result) {
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
result = to_msgpack(doc, reinterpret_cast<uint8_t*>(buffer));
yyjson_doc_free(doc);
return true;
}
};
@@ -113,6 +114,7 @@ struct yyjson_insitu : yyjson2msgpack {
yyjson_doc *doc =
yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
result = to_msgpack(doc, reinterpret_cast<uint8_t*>(buffer));
yyjson_doc_free(doc);
return true;
}
};
@@ -120,4 +122,4 @@ BENCHMARK_TEMPLATE(json2msgpack, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace json2msgpack
#endif // SIMDJSON_COMPETITION_YYJSON
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -2
View File
@@ -49,18 +49,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<point> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(kostya, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<point> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(kostya, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace kostya
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -2
View File
@@ -47,18 +47,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<point> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(large_random, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<point> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(large_random, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace large_random
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -3
View File
@@ -62,19 +62,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(partial_tweets, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, std::vector<tweet<std::string_view>> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(partial_tweets, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace partial_tweets
#endif // SIMDJSON_COMPETITION_YYJSON
+10 -2
View File
@@ -51,18 +51,26 @@ struct yyjson_base {
struct yyjson : yyjson_base {
bool run(simdjson::padded_string &json, int64_t max_retweet_count, top_tweet_result<StringType> &result) {
return yyjson_base::run(yyjson_read(json.data(), json.size(), 0), max_retweet_count, result);
yyjson_doc *doc = yyjson_read(json.data(), json.size(), 0);
bool b = yyjson_base::run(doc, max_retweet_count, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(top_tweet, yyjson)->UseManualTime();
#if SIMDJSON_COMPETITION_ONDEMAND_INSITU
struct yyjson_insitu : yyjson_base {
bool run(simdjson::padded_string &json, int64_t max_retweet_count, top_tweet_result<StringType> &result) {
return yyjson_base::run(yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0), max_retweet_count, result);
yyjson_doc *doc = yyjson_read_opts(json.data(), json.size(), YYJSON_READ_INSITU, 0, 0);
bool b = yyjson_base::run(doc, max_retweet_count, result);
yyjson_doc_free(doc);
return b;
}
};
BENCHMARK_TEMPLATE(top_tweet, yyjson_insitu)->UseManualTime();
#endif // SIMDJSON_COMPETITION_ONDEMAND_INSITU
} // namespace top_tweet
#endif // SIMDJSON_COMPETITION_YYJSON
+1 -1
View File
@@ -452,7 +452,7 @@ support for users who avoid exceptions. See [the simdjson error handling documen
* **Field Access:** To get the value of the "foo" field in an object, use `object["foo"]`. This will
scan through the object looking for the field with the matching string, doing a character-by-character
comparison. It may generate the error `simdjson::NO_SUCH_FIELD` if there is no such key in the object, it may throw an exception (see [Error handling](#error-handling)). For efficiency reason, you should avoid looking up the same field repeatedly: e.g., do
not do `object["foo"]` followed by `object["foo"]` with the same `object` instance. Generally, you should not mix and match iterating through an object (`for(auto field : object) {...}`) and key accesses (`object["foo"]`): if you need to iterate through an object after a key access, you need to call `reset()` on the object. Whenever you call `reset()`, you need to keep in mind that though you can iterate over the array repeatedly, values should be consumedonly once (e.g., repeatedly calling `unescaped_key()` on the same key is forbidden). Keep in mind that On-Demand does not buffer or save the result of the parsing: if you repeatedly access `object["foo"]`, then it must repeatedly seek the key and parse the content. The library does not provide a distinct function to check if a key is present, instead we recommend you attempt to access the key: e.g., by doing `ondemand::value val{}; if (!object["foo"].get(val)) {...}`, you have that `val` contains the requested value inside the if clause. It is your responsibility as a user to temporarily keep a reference to the value (`auto v = object["foo"]`), or to consume the content and store it in your own data structures. If you consume an
not do `object["foo"]` followed by `object["foo"]` with the same `object` instance. Generally, you should not mix and match iterating through an object (`for(auto field : object) {...}`) and key accesses (`object["foo"]`): if you need to iterate through an object after a key access, you need to call `reset()` on the object. Whenever you call `reset()`, you need to keep in mind that though you can iterate over the array repeatedly, values should be consumed only once (e.g., repeatedly calling `unescaped_key()` on the same key is forbidden). Keep in mind that On-Demand does not buffer or save the result of the parsing: if you repeatedly access `object["foo"]`, then it must repeatedly seek the key and parse the content. The library does not provide a distinct function to check if a key is present, instead we recommend you attempt to access the key: e.g., by doing `ondemand::value val{}; if (!object["foo"].get(val)) {...}`, you have that `val` contains the requested value inside the if clause. It is your responsibility as a user to temporarily keep a reference to the value (`auto v = object["foo"]`), or to consume the content and store it in your own data structures. If you consume an
object twice: `std::string_view(object["foo"]` followed by `std::string_view(object["foo"]` then your code
is in error. Furthermore, you can only consume one field at a time, on the same object. The
value instance you get from `content["bids"]` becomes invalid when you call `content["asks"]`.
+1
View File
@@ -53,6 +53,7 @@ consteval std::string consteval_to_quoted_escaped(std::string_view input) {
#if SIMDJSON_SUPPORTS_CONCEPTS
template <std::size_t N>
struct fixed_string {
constexpr fixed_string() : data{} {} // Default constructor for buffers
constexpr fixed_string(const char (&str)[N]) {
for (std::size_t i = 0; i < N; ++i) {
data[i] = str[i];
@@ -52,3 +52,7 @@
#include "simdjson/generic/ondemand/json_string_builder-inl.h"
#include "simdjson/generic/ondemand/json_builder.h"
// Compile-time JSON parsing (C++26 P2996 reflection)
#include "simdjson/generic/ondemand/compile_time_json.h"
#include "simdjson/generic/ondemand/compile_time_json-inl.h"
@@ -0,0 +1,98 @@
/**
* @file compile_time_json-inl.h
* @brief Implementation details for compile-time JSON parsing
*
* This file contains inline implementations and helper utilities for compile-time
* JSON parsing. Currently, the main implementation is self-contained in the header.
*/
#ifndef SIMDJSON_GENERIC_COMPILE_TIME_JSON_INL_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_COMPILE_TIME_JSON_INL_H
#include "simdjson/generic/ondemand/compile_time_json.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_STATIC_REFLECTION
#include <meta>
#include <array>
#include <string_view>
#include <cstdint>
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace compile_time {
/**
* @brief Optimized constexpr string to integer conversion
*
* This can be used for more efficient integer parsing in the future.
* Currently, we parse all numbers as doubles for simplicity.
*/
constexpr int64_t parse_int_fast(std::string_view str) {
int64_t result = 0;
bool negative = false;
std::size_t i = 0;
if (i < str.size() && str[i] == '-') {
negative = true;
++i;
}
while (i < str.size() && str[i] >= '0' && str[i] <= '9') {
result = result * 10 + (str[i] - '0');
++i;
}
return negative ? -result : result;
}
/**
* @brief Unescape JSON string at compile-time
*
* Currently, strings are returned as views into the original JSON.
* This function can be used in the future for proper escape handling.
*/
template<std::size_t MaxLen = 1024>
constexpr auto unescape_json_string(std::string_view escaped) {
std::array<char, MaxLen> result{};
std::size_t out_pos = 0;
std::size_t i = 0;
while (i < escaped.size() && out_pos < MaxLen) {
if (escaped[i] == '\\' && i + 1 < escaped.size()) {
++i;
switch (escaped[i]) {
case '"': result[out_pos++] = '"'; break;
case '\\': result[out_pos++] = '\\'; break;
case '/': result[out_pos++] = '/'; break;
case 'b': result[out_pos++] = '\b'; break;
case 'f': result[out_pos++] = '\f'; break;
case 'n': result[out_pos++] = '\n'; break;
case 'r': result[out_pos++] = '\r'; break;
case 't': result[out_pos++] = '\t'; break;
case 'u':
// Unicode escape - would need proper implementation
// For now, skip the escape sequence
i += 4; // Skip 4 hex digits
break;
default:
result[out_pos++] = escaped[i];
}
++i;
} else {
result[out_pos++] = escaped[i];
++i;
}
}
return std::pair{result, out_pos};
}
} // namespace compile_time
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION
#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_INL_H
@@ -0,0 +1,464 @@
/**
* @file compile_time_json.h
* @brief Compile-time JSON parsing using C++26 reflection with std::meta::substitute()
*
* Based on the godbolt example: https://godbolt.org/z/Kn5b46T8j
* Uses the Outer<Ms...>::Inner + substitute() pattern for recursive type generation.
*/
#ifndef SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
#ifndef SIMDJSON_CONDITIONAL_INCLUDE
#define SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
#include "simdjson/generic/ondemand/base.h"
#endif // SIMDJSON_CONDITIONAL_INCLUDE
#if SIMDJSON_STATIC_REFLECTION
#include <meta>
#include <string_view>
#include <array>
#include <vector>
#include <cstdint>
#include <string>
#include <algorithm>
namespace simdjson {
namespace SIMDJSON_IMPLEMENTATION {
namespace compile_time {
/**
* @brief Helper struct for substitute() pattern
* The consteval block can use define_aggregate because it's in a template context
*/
template <std::meta::info ...Ms>
struct Outer {
struct Inner;
consteval {
std::meta::define_aggregate(^^Inner, {Ms...});
}
};
/**
* @brief Type alias for the generated struct
*/
template <std::meta::info ...Ms>
using Cls = Outer<Ms...>::Inner;
/**
* @brief Variable template for constructing instances with values
*/
template <typename T, auto ... Vs>
constexpr auto construct_from = T{Vs...};
// Forward declaration
consteval std::meta::info parse_json_impl(std::string_view json);
/**
* @brief Parse JSON array and return std::meta::info for the generated array
*/
consteval std::meta::info parse_json_array_impl(std::string_view json) {
auto cursor = json.begin();
auto end = json.end();
auto is_whitespace = [](char c) {
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
};
auto skip_whitespace = [&]() -> void {
while (cursor != end && is_whitespace(*cursor)) cursor++;
};
auto expect_consume = [&](char c) -> void {
skip_whitespace();
if (cursor == end || *(cursor++) != c) throw "unexpected character";
};
auto parse_value = [&](std::string &out) -> void {
skip_whitespace();
bool quoted = false;
unsigned depth = 0;
while (true) {
if (cursor == end) throw "unexpected end of stream";
if (is_whitespace(*cursor) && !quoted && depth == 0)
break;
if (depth == 0 && (*cursor == ',' || *cursor == ']'))
break;
out += *(cursor++);
if (out.back() == '{')
++depth;
else if (out.back() == '}')
--depth;
else if (out.back() == '[')
++depth;
else if (out.back() == ']')
--depth;
else if (out.back() == '"') {
if (quoted && depth == 0)
break;
quoted = true;
}
}
};
skip_whitespace();
expect_consume('[');
std::vector<std::meta::info> values = {^^void};
std::meta::info element_type = ^^void;
bool first = true;
using std::meta::reflect_constant, std::meta::reflect_constant_string;
skip_whitespace();
if (cursor != end && *cursor == ']') {
expect_consume(']');
// Empty array - use int as placeholder type since void doesn't work
auto array_type = std::meta::substitute(^^std::array, {^^int, reflect_constant(0uz)});
values[0] = array_type;
return std::meta::substitute(^^construct_from, values);
}
while (cursor != end && *cursor != ']') {
std::string value;
parse_value(value);
if (value.empty()) throw "expected value";
if (value[0] == '"') {
if (value.back() != '"') throw "expected end of string";
std::string_view contents(&value[1], value.size() - 2);
if (first) element_type = ^^char const*;
values.push_back(reflect_constant_string(contents));
} else if (value == "true") {
if (first) element_type = ^^bool;
values.push_back(reflect_constant(true));
} else if (value == "false") {
if (first) element_type = ^^bool;
values.push_back(reflect_constant(false));
} else if (value == "null") {
if (first) element_type = ^^std::nullptr_t;
values.push_back(reflect_constant(nullptr));
} else if ((value[0] >= '0' && value[0] <= '9') || value[0] == '-') {
// Try to parse as integer first
bool is_int = true;
for (char c : value) {
if (c == '.' || c == 'e' || c == 'E') {
is_int = false;
break;
}
}
if (is_int) {
int contents = [](std::string_view in) {
int out = 0;
bool negative = false;
std::size_t i = 0;
if (in[0] == '-') {
negative = true;
i = 1;
}
for (; i < in.size(); ++i) {
out = out * 10 + (in[i] - '0');
}
return negative ? -out : out;
}(value);
if (first) element_type = ^^int;
values.push_back(reflect_constant(contents));
} else {
// Parse as double
double contents = [](std::string_view in) {
double result = 0.0;
double sign = 1.0;
std::size_t i = 0;
if (in[0] == '-') {
sign = -1.0;
i = 1;
}
while (i < in.size() && in[i] >= '0' && in[i] <= '9') {
result = result * 10.0 + (in[i] - '0');
++i;
}
if (i < in.size() && in[i] == '.') {
++i;
double fraction = 0.0;
double divisor = 1.0;
while (i < in.size() && in[i] >= '0' && in[i] <= '9') {
fraction = fraction * 10.0 + (in[i] - '0');
divisor *= 10.0;
++i;
}
result += fraction / divisor;
}
return result * sign;
}(value);
if (first) element_type = ^^double;
values.push_back(reflect_constant(contents));
}
} else if (value[0] == '{') {
// Nested object in array
std::meta::info parsed = parse_json_impl(value);
if (first) element_type = std::meta::type_of(parsed);
values.push_back(parsed);
} else if (value[0] == '[') {
// Nested array
std::meta::info parsed = parse_json_array_impl(value);
if (first) element_type = std::meta::type_of(parsed);
values.push_back(parsed);
}
first = false;
skip_whitespace();
if (cursor != end && *cursor == ',')
++cursor;
}
if (cursor == end) throw "unexpected end";
expect_consume(']');
// Create std::array<ElementType, Count> type
std::size_t count = values.size() - 1; // -1 because first element is ^^void placeholder
auto array_type = std::meta::substitute(^^std::array, {element_type, reflect_constant(count)});
// Create array instance with values
values[0] = array_type;
return std::meta::substitute(^^construct_from, values);
}
/**
* @brief Parse JSON and return std::meta::info for the generated type + instance
*/
consteval std::meta::info parse_json_impl(std::string_view json) {
auto cursor = json.begin();
auto end = json.end();
auto is_whitespace = [](char c) {
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
};
auto skip_whitespace = [&]() -> void {
while (cursor != end && is_whitespace(*cursor)) cursor++;
};
auto expect_consume = [&](char c) -> void {
skip_whitespace();
if (cursor == end || *(cursor++) != c) throw "unexpected character";
};
auto parse_until = [&](std::vector<char> delims, std::string &out) -> void {
skip_whitespace();
while (cursor != end &&
!std::ranges::any_of(delims, [&](char c) { return c == *cursor; }))
out += *(cursor++);
};
auto parse_delimited = [&](char lhs, std::string &out, char rhs) -> void {
skip_whitespace();
expect_consume(lhs);
parse_until({rhs}, out);
expect_consume(rhs);
};
auto parse_value = [&](std::string &out) -> void {
skip_whitespace();
bool quoted = false;
unsigned depth = 0;
bool in_array = false;
while (true) {
if (cursor == end) throw "unexpected end of stream";
if (is_whitespace(*cursor) && !quoted && depth == 0)
break;
if (depth == 0 && (*cursor == ',' || *cursor == '}' || *cursor == ']'))
break;
out += *(cursor++);
if (out.back() == '{')
++depth;
else if (out.back() == '}')
--depth;
else if (out.back() == '[') {
in_array = true;
++depth;
} else if (out.back() == ']')
--depth;
else if (out.back() == '"') {
if (quoted && depth == 0)
break;
quoted = true;
}
}
};
skip_whitespace();
expect_consume('{');
std::vector<std::meta::info> members;
std::vector<std::meta::info> values = {^^void};
using std::meta::reflect_constant, std::meta::reflect_constant_string;
while (cursor != end && *cursor != '}') {
std::string field_name;
std::string value;
parse_delimited('"', field_name, '"');
expect_consume(':');
parse_value(value);
if (value.empty()) throw "expected value";
if (cursor == end) throw "unexpected end of stream";
if (value[0] == '"') {
if (value.back() != '"') throw "expected end of string";
std::string_view contents(&value[1], value.size() - 2);
auto dms = std::meta::data_member_spec(^^char const*, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant_string(contents));
} else if (value == "true") {
auto dms = std::meta::data_member_spec(^^bool, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(true));
} else if (value == "false") {
auto dms = std::meta::data_member_spec(^^bool, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(false));
} else if (value == "null") {
auto dms = std::meta::data_member_spec(^^std::nullptr_t, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(nullptr));
} else if ((value[0] >= '0' && value[0] <= '9') || value[0] == '-') {
// Try to parse as integer first
bool is_int = true;
for (char c : value) {
if (c == '.' || c == 'e' || c == 'E') {
is_int = false;
break;
}
}
if (is_int) {
int contents = [](std::string_view in) {
int out = 0;
bool negative = false;
std::size_t i = 0;
if (in[0] == '-') {
negative = true;
i = 1;
}
for (; i < in.size(); ++i) {
out = out * 10 + (in[i] - '0');
}
return negative ? -out : out;
}(value);
auto dms = std::meta::data_member_spec(^^int, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(contents));
} else {
// Parse as double
double contents = [](std::string_view in) {
double result = 0.0;
double sign = 1.0;
std::size_t i = 0;
if (in[0] == '-') {
sign = -1.0;
i = 1;
}
while (i < in.size() && in[i] >= '0' && in[i] <= '9') {
result = result * 10.0 + (in[i] - '0');
++i;
}
if (i < in.size() && in[i] == '.') {
++i;
double fraction = 0.0;
double divisor = 1.0;
while (i < in.size() && in[i] >= '0' && in[i] <= '9') {
fraction = fraction * 10.0 + (in[i] - '0');
divisor *= 10.0;
++i;
}
result += fraction / divisor;
}
return result * sign;
}(value);
auto dms = std::meta::data_member_spec(^^double, {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(reflect_constant(contents));
}
} else if (value[0] == '{') {
// Nested object
std::meta::info parsed = parse_json_impl(value);
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(parsed);
} else if (value[0] == '[') {
// Array
std::meta::info parsed = parse_json_array_impl(value);
auto dms = std::meta::data_member_spec(std::meta::type_of(parsed), {.name=field_name});
members.push_back(reflect_constant(dms));
values.push_back(parsed);
}
skip_whitespace();
if (cursor != end && *cursor == ',')
++cursor;
}
if (cursor == end) throw "unexpected end";
expect_consume('}');
// The substitute() trick:
// 1. Create the type: Cls<member_specs...>
values[0] = std::meta::substitute(^^Cls, members);
// 2. Create instance: construct_from<Type, values...>
return std::meta::substitute(^^construct_from, values);
}
/**
* @brief Main parse_json function - template wrapper
*/
template<constevalutil::fixed_string json_str>
consteval auto parse_json() {
constexpr std::meta::info result = parse_json_impl(json_str.view());
return [:result:];
}
/**
* @brief JSON validation
*/
template<constevalutil::fixed_string json_str>
consteval bool validate_json() {
try {
parse_json_impl(json_str.view());
return true;
} catch (...) {
return false;
}
}
} // namespace compile_time
} // namespace SIMDJSON_IMPLEMENTATION
} // namespace simdjson
#endif // SIMDJSON_STATIC_REFLECTION
#endif // SIMDJSON_GENERIC_COMPILE_TIME_JSON_H
+418
View File
@@ -0,0 +1,418 @@
/**
* @file compile_time_json_tests.cpp
* @brief Comprehensive tests for compile-time JSON parsing using C++26 P2996 reflection
*/
#include "simdjson.h"
#include "test_ondemand.h"
using namespace simdjson;
using namespace std::string_view_literals;
namespace compile_time_json_tests {
#if SIMDJSON_STATIC_REFLECTION
using namespace arm64::compile_time;
#endif
/**
* Test 1: Basic object with primitives
*/
bool test_basic_object() {
TEST_START();
constexpr auto config = parse_json<R"({
"port": 8080,
"host": "localhost",
"debug": true,
"timeout": 30.5
})">();
static_assert(config.port == 8080);
static_assert(std::string_view(config.host) == "localhost");
static_assert(config.debug == true);
static_assert(config.timeout == 30.5);
ASSERT_EQUAL(config.port, 8080);
ASSERT_EQUAL(std::string_view(config.host), "localhost"sv);
ASSERT_TRUE(config.debug);
ASSERT_EQUAL(config.timeout, 30.5);
TEST_SUCCEED();
}
/**
* Test 2: Nested objects
*/
bool test_nested_objects() {
TEST_START();
constexpr auto config = parse_json<R"({
"server_port": 3000,
"enable_ssl": true,
"database": {
"host": "db.example.com",
"port": 5432,
"timeout_sec": 30.0
}
})">();
static_assert(config.server_port == 3000);
static_assert(config.enable_ssl == true);
static_assert(std::string_view(config.database.host) == "db.example.com");
static_assert(config.database.port == 5432);
static_assert(config.database.timeout_sec == 30.0);
ASSERT_EQUAL(config.server_port, 3000);
ASSERT_TRUE(config.enable_ssl);
ASSERT_EQUAL(std::string_view(config.database.host), "db.example.com"sv);
ASSERT_EQUAL(config.database.port, 5432);
ASSERT_EQUAL(config.database.timeout_sec, 30.0);
TEST_SUCCEED();
}
/**
* Test 3: Deeply nested objects (3+ levels)
*/
bool test_deeply_nested_objects() {
TEST_START();
constexpr auto config = parse_json<R"({
"app_name": "MyApp",
"version": 1.5,
"server": {
"host": "api.example.com",
"port": 443,
"tls": {
"enabled": true,
"cert_path": "/etc/ssl/cert.pem",
"min_version": 1.3
}
}
})">();
static_assert(std::string_view(config.app_name) == "MyApp");
static_assert(config.version == 1.5);
static_assert(std::string_view(config.server.host) == "api.example.com");
static_assert(config.server.port == 443);
static_assert(config.server.tls.enabled == true);
static_assert(std::string_view(config.server.tls.cert_path) == "/etc/ssl/cert.pem");
static_assert(config.server.tls.min_version == 1.3);
ASSERT_EQUAL(std::string_view(config.app_name), "MyApp"sv);
ASSERT_EQUAL(config.server.tls.enabled, true);
ASSERT_EQUAL(config.server.tls.min_version, 1.3);
TEST_SUCCEED();
}
/**
* Test 4: Empty object
*/
bool test_empty_object() {
TEST_START();
constexpr auto config = parse_json<"{}">();
(void)config; // Suppress unused warning
TEST_SUCCEED();
}
/**
* Test 5: Negative numbers
*/
bool test_negative_numbers() {
TEST_START();
constexpr auto data = parse_json<R"({
"temperature": -273.15,
"count": -42
})">();
static_assert(data.temperature == -273.15);
static_assert(data.count == -42);
ASSERT_EQUAL(data.temperature, -273.15);
ASSERT_EQUAL(data.count, -42);
TEST_SUCCEED();
}
/**
* Test 6: Whitespace handling
*/
bool test_whitespace() {
TEST_START();
constexpr auto data = parse_json<R"(
{
"key1" : "value1" ,
"key2" : 42
}
)">();
static_assert(std::string_view(data.key1) == "value1");
static_assert(data.key2 == 42);
ASSERT_EQUAL(std::string_view(data.key1), "value1"sv);
ASSERT_EQUAL(data.key2, 42);
TEST_SUCCEED();
}
/**
* Test 7: Real-time system configuration
*/
bool test_realtime_config() {
TEST_START();
constexpr auto config = parse_json<R"({
"control_loop_hz": 1000,
"max_acceleration": 9.8,
"min_velocity": -50.0,
"max_velocity": 50.0,
"enable_safety_checks": true,
"log_level": "INFO"
})">();
static_assert(config.control_loop_hz == 1000);
static_assert(config.max_acceleration == 9.8);
static_assert(config.enable_safety_checks == true);
ASSERT_EQUAL(config.control_loop_hz, 1000);
ASSERT_EQUAL(config.max_acceleration, 9.8);
ASSERT_EQUAL(config.min_velocity, -50.0);
ASSERT_EQUAL(config.max_velocity, 50.0);
ASSERT_TRUE(config.enable_safety_checks);
ASSERT_EQUAL(std::string_view(config.log_level), "INFO"sv);
TEST_SUCCEED();
}
/**
* Test 8: External JSON file (future #embed support)
*/
bool test_external_json_embed() {
TEST_START();
// Future C++26 with #embed:
// constexpr auto config = parse_json<#embed "test_config.json">();
// Current workaround - inline the JSON from test_config.json
constexpr auto config = parse_json<R"({
"system_name": "RealTimeController",
"version": "2.1.0",
"control_loop_hz": 1000,
"max_latency_us": 500,
"enable_diagnostics": true,
"log_level": "INFO"
})">();
static_assert(std::string_view(config.system_name) == "RealTimeController");
static_assert(std::string_view(config.version) == "2.1.0");
static_assert(config.control_loop_hz == 1000);
static_assert(config.max_latency_us == 500);
static_assert(config.enable_diagnostics == true);
static_assert(std::string_view(config.log_level) == "INFO");
ASSERT_EQUAL(std::string_view(config.system_name), "RealTimeController"sv);
ASSERT_EQUAL(std::string_view(config.version), "2.1.0"sv);
ASSERT_EQUAL(config.control_loop_hz, 1000);
ASSERT_EQUAL(config.max_latency_us, 500);
ASSERT_TRUE(config.enable_diagnostics);
ASSERT_EQUAL(std::string_view(config.log_level), "INFO"sv);
TEST_SUCCEED();
}
/**
* Test 9: JSON validation
*/
bool test_json_validation() {
TEST_START();
static_assert(validate_json<R"({"valid": true})">());
static_assert(validate_json<R"({"nested": {"deep": 42}})">());
TEST_SUCCEED();
}
/**
* Test 10: Null values
*/
bool test_null_values() {
TEST_START();
constexpr auto data = parse_json<R"({
"nullable_field": null,
"number": 42
})">();
static_assert(data.nullable_field == nullptr);
static_assert(data.number == 42);
ASSERT_EQUAL(data.nullable_field, nullptr);
ASSERT_EQUAL(data.number, 42);
TEST_SUCCEED();
}
/**
* Test 11: Arrays of primitives
*/
bool test_arrays_primitives() {
TEST_START();
constexpr auto data = parse_json<R"({
"values": [1, 2, 3, 4, 5],
"flags": [true, false, true]
})">();
static_assert(data.values.size() == 5);
static_assert(data.values[0] == 1);
static_assert(data.values[4] == 5);
static_assert(data.flags.size() == 3);
static_assert(data.flags[0] == true);
static_assert(data.flags[1] == false);
ASSERT_EQUAL(data.values.size(), 5);
ASSERT_EQUAL(data.values[0], 1);
ASSERT_EQUAL(data.values[4], 5);
ASSERT_EQUAL(data.flags.size(), 3);
ASSERT_TRUE(data.flags[0]);
ASSERT_FALSE(data.flags[1]);
TEST_SUCCEED();
}
/**
* Test 12: Arrays of objects
*/
bool test_arrays_of_objects() {
TEST_START();
constexpr auto data = parse_json<R"({
"users": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
]
})">();
static_assert(data.users.size() == 2);
static_assert(std::string_view(data.users[0].name) == "Alice");
static_assert(data.users[0].age == 30);
static_assert(std::string_view(data.users[1].name) == "Bob");
static_assert(data.users[1].age == 25);
ASSERT_EQUAL(data.users.size(), 2);
ASSERT_EQUAL(std::string_view(data.users[0].name), "Alice"sv);
ASSERT_EQUAL(data.users[0].age, 30);
ASSERT_EQUAL(std::string_view(data.users[1].name), "Bob"sv);
ASSERT_EQUAL(data.users[1].age, 25);
TEST_SUCCEED();
}
/**
* Test 13: Nested arrays in objects
*/
bool test_nested_arrays() {
TEST_START();
constexpr auto data = parse_json<R"({
"config": {
"ports": [8080, 8081, 8082]
}
})">();
static_assert(data.config.ports.size() == 3);
static_assert(data.config.ports[0] == 8080);
static_assert(data.config.ports[2] == 8082);
ASSERT_EQUAL(data.config.ports.size(), 3);
ASSERT_EQUAL(data.config.ports[0], 8080);
ASSERT_EQUAL(data.config.ports[2], 8082);
TEST_SUCCEED();
}
/**
* Test 14: Complex mixed structure with arrays and nested objects
*/
bool test_complex_mixed() {
TEST_START();
constexpr auto config = parse_json<R"({
"app": "myapp",
"version": 1.0,
"config": {
"ports": [8080, 8081, 8082],
"enabled": true
},
"servers": [
{"host": "server1", "port": 3000},
{"host": "server2", "port": 3001}
]
})">();
static_assert(std::string_view(config.app) == "myapp");
static_assert(config.version == 1.0);
static_assert(config.config.ports[0] == 8080);
static_assert(config.config.enabled == true);
static_assert(std::string_view(config.servers[0].host) == "server1");
static_assert(config.servers[1].port == 3001);
ASSERT_EQUAL(std::string_view(config.app), "myapp"sv);
ASSERT_EQUAL(config.config.ports[0], 8080);
ASSERT_EQUAL(std::string_view(config.servers[0].host), "server1"sv);
ASSERT_EQUAL(config.servers[1].port, 3001);
TEST_SUCCEED();
}
/**
* Test 15: Empty arrays
*/
bool test_empty_arrays() {
TEST_START();
constexpr auto data = parse_json<R"({
"empty": []
})">();
static_assert(data.empty.size() == 0);
ASSERT_EQUAL(data.empty.size(), 0);
TEST_SUCCEED();
}
bool run() {
return test_basic_object() &&
test_nested_objects() &&
test_deeply_nested_objects() &&
test_empty_object() &&
test_negative_numbers() &&
test_whitespace() &&
test_realtime_config() &&
test_external_json_embed() &&
test_json_validation() &&
test_null_values() &&
test_arrays_primitives() &&
test_arrays_of_objects() &&
test_nested_arrays() &&
test_complex_mixed() &&
test_empty_arrays();
}
} // namespace compile_time_json_tests
int main(int argc, char *argv[]) {
#if SIMDJSON_STATIC_REFLECTION
return test_main(argc, argv, compile_time_json_tests::run);
#else
std::cout << "Compile-time JSON tests require SIMDJSON_STATIC_REFLECTION=ON" << std::endl;
return EXIT_SUCCESS;
#endif
}
+8
View File
@@ -0,0 +1,8 @@
{
"system_name": "RealTimeController",
"version": "2.1.0",
"control_loop_hz": 1000,
"max_latency_us": 500,
"enable_diagnostics": true,
"log_level": "INFO"
}