mirror of
https://github.com/simdjson/simdjson
synced 2026-06-08 17:27:07 +00:00
Working!
This commit is contained in:
@@ -1,14 +1,9 @@
|
||||
/**
|
||||
* @file compile_time_json.h
|
||||
* @brief Compile-time JSON parsing using C++26 P2996 reflection
|
||||
* @brief Compile-time JSON parsing using C++26 reflection with std::meta::substitute()
|
||||
*
|
||||
* Parses JSON at compile-time and generates strongly-typed structs automatically.
|
||||
* Uses std::meta::substitute() pattern for recursive structures.
|
||||
*
|
||||
* @warning Requires C++26 with P2996R13 (experimental clang-p2996 compiler)
|
||||
*
|
||||
* Inspired by: https://brevzin.github.io/c++/2025/06/26/json-reflection/
|
||||
* https://godbolt.org/z/Kn5b46T8j
|
||||
* 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
|
||||
@@ -22,15 +17,19 @@
|
||||
|
||||
#include <meta>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
namespace simdjson {
|
||||
namespace SIMDJSON_IMPLEMENTATION {
|
||||
namespace compile_time {
|
||||
|
||||
/**
|
||||
* @brief Helper template for dynamic type generation via substitute()
|
||||
* @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 {
|
||||
@@ -40,322 +39,421 @@ struct Outer {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Type alias for the generated struct
|
||||
*/
|
||||
template <std::meta::info ...Ms>
|
||||
using Cls = Outer<Ms...>::Inner;
|
||||
|
||||
/**
|
||||
* @brief Helper template for aggregate initialization from reflected values
|
||||
* @brief Variable template for constructing instances with values
|
||||
*/
|
||||
template <typename T, auto ... Vs>
|
||||
constexpr auto construct_from = T{Vs...};
|
||||
|
||||
/**
|
||||
* @brief Parsing state and helper functions
|
||||
*/
|
||||
struct ParseContext {
|
||||
std::string_view json;
|
||||
std::size_t pos = 0;
|
||||
|
||||
constexpr ParseContext(std::string_view input) : json(input), pos(0) {}
|
||||
|
||||
constexpr void skip_whitespace() {
|
||||
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t' ||
|
||||
json[pos] == '\n' || json[pos] == '\r')) {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr char peek() const {
|
||||
return pos < json.size() ? json[pos] : '\0';
|
||||
}
|
||||
|
||||
constexpr char consume() {
|
||||
return pos < json.size() ? json[pos++] : '\0';
|
||||
}
|
||||
|
||||
constexpr bool expect(char ch) {
|
||||
skip_whitespace();
|
||||
if (peek() == ch) {
|
||||
consume();
|
||||
return true;
|
||||
}
|
||||
throw "expected character";
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool match(std::string_view str) {
|
||||
if (json.substr(pos, str.size()) == str) {
|
||||
pos += str.size();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view parse_string() {
|
||||
if (!expect('"')) throw "expected quote";
|
||||
std::size_t start = pos;
|
||||
while (peek() != '"' && peek() != '\0') {
|
||||
if (peek() == '\\') {
|
||||
consume();
|
||||
if (pos < json.size()) consume();
|
||||
} else {
|
||||
consume();
|
||||
}
|
||||
}
|
||||
std::size_t end = pos;
|
||||
expect('"');
|
||||
return json.substr(start, end - start);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr double parse_number() {
|
||||
std::size_t start = pos;
|
||||
if (peek() == '-') consume();
|
||||
while (peek() >= '0' && peek() <= '9') consume();
|
||||
if (peek() == '.') {
|
||||
consume();
|
||||
while (peek() >= '0' && peek() <= '9') consume();
|
||||
}
|
||||
|
||||
std::string_view num_str = json.substr(start, pos - start);
|
||||
double result = 0.0, sign = 1.0;
|
||||
std::size_t i = 0;
|
||||
|
||||
if (i < num_str.size() && num_str[i] == '-') {
|
||||
sign = -1.0;
|
||||
++i;
|
||||
}
|
||||
|
||||
while (i < num_str.size() && num_str[i] >= '0' && num_str[i] <= '9') {
|
||||
result = result * 10.0 + (num_str[i] - '0');
|
||||
++i;
|
||||
}
|
||||
|
||||
if (i < num_str.size() && num_str[i] == '.') {
|
||||
++i;
|
||||
double fraction = 0.0, divisor = 1.0;
|
||||
while (i < num_str.size() && num_str[i] >= '0' && num_str[i] <= '9') {
|
||||
fraction = fraction * 10.0 + (num_str[i] - '0');
|
||||
divisor *= 10.0;
|
||||
++i;
|
||||
}
|
||||
result += fraction / divisor;
|
||||
}
|
||||
|
||||
return result * sign;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view extract_nested() {
|
||||
std::size_t start = pos;
|
||||
char open_char = peek();
|
||||
char close_char = (open_char == '{') ? '}' : ']';
|
||||
int depth = 0;
|
||||
do {
|
||||
if (peek() == open_char) depth++;
|
||||
if (peek() == close_char) depth--;
|
||||
consume();
|
||||
} while (depth > 0 && pos < json.size());
|
||||
if (depth != 0) throw "unclosed bracket";
|
||||
return json.substr(start, pos - start);
|
||||
}
|
||||
};
|
||||
|
||||
// Forward declaration
|
||||
consteval std::meta::info parse_json_impl(std::string_view json);
|
||||
consteval std::meta::info parse_array_impl(std::string_view json);
|
||||
|
||||
consteval std::meta::info parse_array_impl(std::string_view json) {
|
||||
ParseContext ctx{json};
|
||||
ctx.skip_whitespace();
|
||||
/**
|
||||
* @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();
|
||||
|
||||
if (!ctx.expect('[')) {
|
||||
throw "expected '['";
|
||||
}
|
||||
auto is_whitespace = [](char c) {
|
||||
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
|
||||
};
|
||||
|
||||
ctx.skip_whitespace();
|
||||
auto skip_whitespace = [&]() -> void {
|
||||
while (cursor != end && is_whitespace(*cursor)) cursor++;
|
||||
};
|
||||
|
||||
if (ctx.peek() == ']') {
|
||||
ctx.consume();
|
||||
return std::meta::substitute(^^construct_from, {^^std::array<void, 0>, ^^void});
|
||||
}
|
||||
auto expect_consume = [&](char c) -> void {
|
||||
skip_whitespace();
|
||||
if (cursor == end || *(cursor++) != c) throw "unexpected character";
|
||||
};
|
||||
|
||||
std::vector<std::meta::info> element_values;
|
||||
auto parse_value = [&](std::string &out) -> void {
|
||||
skip_whitespace();
|
||||
|
||||
while (ctx.peek() != ']') {
|
||||
ctx.skip_whitespace();
|
||||
char ch = ctx.peek();
|
||||
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 (ch == '"') {
|
||||
auto str_value = ctx.parse_string();
|
||||
element_values.push_back(std::meta::reflect_constant_string(str_value));
|
||||
} else if (ch == 't' || ch == 'f') {
|
||||
bool bool_value = ctx.peek() == 't';
|
||||
if (bool_value) {
|
||||
if (!ctx.match("true")) throw "expected 'true'";
|
||||
} else {
|
||||
if (!ctx.match("false")) throw "expected 'false'";
|
||||
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;
|
||||
}
|
||||
element_values.push_back(std::meta::reflect_constant(bool_value));
|
||||
} else if (ch == 'n') {
|
||||
if (!ctx.match("null")) throw "expected 'null'";
|
||||
element_values.push_back(std::meta::reflect_constant(nullptr));
|
||||
} else if (ch == '{') {
|
||||
auto nested_json = ctx.extract_nested();
|
||||
std::meta::info parsed = parse_json_impl(nested_json);
|
||||
element_values.push_back(parsed);
|
||||
} else if (ch == '[') {
|
||||
auto nested_json = ctx.extract_nested();
|
||||
std::meta::info parsed = parse_array_impl(nested_json);
|
||||
element_values.push_back(parsed);
|
||||
} else if ((ch >= '0' && ch <= '9') || ch == '-') {
|
||||
double num_value = ctx.parse_number();
|
||||
element_values.push_back(std::meta::reflect_constant(num_value));
|
||||
} else {
|
||||
throw "unexpected array element type";
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ctx.skip_whitespace();
|
||||
if (ctx.peek() == ',') {
|
||||
ctx.consume();
|
||||
ctx.skip_whitespace();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
first = false;
|
||||
|
||||
skip_whitespace();
|
||||
if (cursor != end && *cursor == ',')
|
||||
++cursor;
|
||||
}
|
||||
|
||||
ctx.expect(']');
|
||||
if (cursor == end) throw "unexpected end";
|
||||
expect_consume(']');
|
||||
|
||||
if (element_values.empty()) {
|
||||
return std::meta::substitute(^^construct_from, {^^std::array<void, 0>, ^^void});
|
||||
}
|
||||
|
||||
std::meta::info element_type = std::meta::type_of(element_values[0]);
|
||||
std::size_t count = element_values.size();
|
||||
std::meta::info array_type = std::meta::substitute(^^std::array, {element_type, std::meta::reflect_constant(count)});
|
||||
|
||||
std::vector<std::meta::info> values;
|
||||
values.push_back(array_type);
|
||||
for (auto& elem : element_values) {
|
||||
values.push_back(elem);
|
||||
}
|
||||
// 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 Main compile-time JSON parser using substitute() pattern
|
||||
*
|
||||
* Returns std::meta::info containing type and value information.
|
||||
* This can be recursively called for nested objects without compiler crashes.
|
||||
* @brief Parse JSON and return std::meta::info for the generated type + instance
|
||||
*/
|
||||
consteval std::meta::info parse_json_impl(std::string_view json) {
|
||||
ParseContext ctx{json};
|
||||
ctx.skip_whitespace();
|
||||
auto cursor = json.begin();
|
||||
auto end = json.end();
|
||||
|
||||
if (!ctx.expect('{')) {
|
||||
throw "expected '{'";
|
||||
}
|
||||
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;
|
||||
using std::meta::reflect_constant, std::meta::reflect_constant_string;
|
||||
while (cursor != end && *cursor != '}') {
|
||||
std::string field_name;
|
||||
std::string value;
|
||||
|
||||
while (ctx.peek() != '}') {
|
||||
ctx.skip_whitespace();
|
||||
if (ctx.peek() == '}') break;
|
||||
parse_delimited('"', field_name, '"');
|
||||
expect_consume(':');
|
||||
parse_value(value);
|
||||
|
||||
auto field_name = ctx.parse_string();
|
||||
ctx.skip_whitespace();
|
||||
ctx.expect(':');
|
||||
ctx.skip_whitespace();
|
||||
if (value.empty()) throw "expected value";
|
||||
if (cursor == end) throw "unexpected end of stream";
|
||||
|
||||
char ch = ctx.peek();
|
||||
if (value[0] == '"') {
|
||||
if (value.back() != '"') throw "expected end of string";
|
||||
std::string_view contents(&value[1], value.size() - 2);
|
||||
|
||||
if (ch == '"') {
|
||||
auto str_value = ctx.parse_string();
|
||||
auto dms = std::meta::data_member_spec(^^char const*, {.name=field_name});
|
||||
members.push_back(reflect_constant(dms));
|
||||
values.push_back(std::meta::reflect_constant_string(str_value));
|
||||
} else if (ch == 't' || ch == 'f') {
|
||||
bool bool_value = ctx.peek() == 't';
|
||||
if (bool_value) {
|
||||
if (!ctx.match("true")) throw "expected 'true'";
|
||||
} else {
|
||||
if (!ctx.match("false")) throw "expected 'false'";
|
||||
}
|
||||
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(bool_value));
|
||||
} else if (ch == 'n') {
|
||||
if (!ctx.match("null")) throw "expected 'null'";
|
||||
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 (ch == '[') {
|
||||
auto array_json = ctx.extract_nested();
|
||||
std::meta::info parsed = parse_array_impl(array_json);
|
||||
} 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 (ch == '{') {
|
||||
auto nested_json = ctx.extract_nested();
|
||||
std::meta::info parsed = parse_json_impl(nested_json);
|
||||
} 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);
|
||||
} else if ((ch >= '0' && ch <= '9') || ch == '-') {
|
||||
double num_value = ctx.parse_number();
|
||||
auto dms = std::meta::data_member_spec(^^double, {.name=field_name});
|
||||
members.push_back(reflect_constant(dms));
|
||||
values.push_back(reflect_constant(num_value));
|
||||
} else {
|
||||
throw "unexpected value type";
|
||||
}
|
||||
|
||||
ctx.skip_whitespace();
|
||||
if (ctx.peek() == ',') ctx.consume();
|
||||
skip_whitespace();
|
||||
if (cursor != end && *cursor == ',')
|
||||
++cursor;
|
||||
}
|
||||
|
||||
ctx.expect('}');
|
||||
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 JSON string wrapper for template parameters
|
||||
* @brief Main parse_json function - template wrapper
|
||||
*/
|
||||
struct JSONString {
|
||||
std::meta::info Rep;
|
||||
consteval JSONString(const char *Json) : Rep{parse_json_impl(Json)} {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Main parse_json function - template variable pattern
|
||||
*
|
||||
* Usage:
|
||||
* constexpr auto config = json_to_object<R"({"port":8080,"host":"localhost"})">;
|
||||
* static_assert(config.port == 8080);
|
||||
* static_assert(std::string_view(config.host) == "localhost");
|
||||
*/
|
||||
template <JSONString json>
|
||||
inline constexpr auto json_to_object = [:json.Rep:];
|
||||
|
||||
/**
|
||||
* @brief Alternative: User-defined literal for JSON parsing
|
||||
*
|
||||
* Usage:
|
||||
* constexpr auto config = R"({"port":8080})"_json;
|
||||
*/
|
||||
template <JSONString json>
|
||||
consteval auto operator""_json() {
|
||||
return [:json.Rep:];
|
||||
template<constevalutil::fixed_string json_str>
|
||||
consteval auto parse_json() {
|
||||
constexpr std::meta::info result = parse_json_impl(json_str.view());
|
||||
return [:result:];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate JSON syntax at compile-time
|
||||
* @brief JSON validation
|
||||
*/
|
||||
template <JSONString json>
|
||||
template<constevalutil::fixed_string json_str>
|
||||
consteval bool validate_json() {
|
||||
return true;
|
||||
try {
|
||||
parse_json_impl(json_str.view());
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace compile_time
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @file compile_time_json_tests.cpp
|
||||
* @brief Tests for compile-time JSON parsing using C++26 P2996 reflection
|
||||
* @brief Comprehensive tests for compile-time JSON parsing using C++26 P2996 reflection
|
||||
*/
|
||||
|
||||
#include "simdjson.h"
|
||||
@@ -21,12 +21,12 @@ using namespace arm64::compile_time;
|
||||
bool test_basic_object() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = json_to_object<R"({
|
||||
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");
|
||||
@@ -47,7 +47,7 @@ bool test_basic_object() {
|
||||
bool test_nested_objects() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = json_to_object<R"({
|
||||
constexpr auto config = parse_json<R"({
|
||||
"server_port": 3000,
|
||||
"enable_ssl": true,
|
||||
"database": {
|
||||
@@ -55,7 +55,7 @@ bool test_nested_objects() {
|
||||
"port": 5432,
|
||||
"timeout_sec": 30.0
|
||||
}
|
||||
})">;
|
||||
})">();
|
||||
|
||||
static_assert(config.server_port == 3000);
|
||||
static_assert(config.enable_ssl == true);
|
||||
@@ -78,7 +78,7 @@ bool test_nested_objects() {
|
||||
bool test_deeply_nested_objects() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = json_to_object<R"({
|
||||
constexpr auto config = parse_json<R"({
|
||||
"app_name": "MyApp",
|
||||
"version": 1.5,
|
||||
"server": {
|
||||
@@ -90,7 +90,7 @@ bool test_deeply_nested_objects() {
|
||||
"min_version": 1.3
|
||||
}
|
||||
}
|
||||
})">;
|
||||
})">();
|
||||
|
||||
static_assert(std::string_view(config.app_name) == "MyApp");
|
||||
static_assert(config.version == 1.5);
|
||||
@@ -108,15 +108,166 @@ bool test_deeply_nested_objects() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 4: Arrays of primitives
|
||||
* 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 = json_to_object<R"({
|
||||
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);
|
||||
@@ -136,17 +287,17 @@ bool test_arrays_primitives() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 5: Arrays of objects
|
||||
* Test 12: Arrays of objects
|
||||
*/
|
||||
bool test_arrays_of_objects() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto data = json_to_object<R"({
|
||||
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");
|
||||
@@ -164,16 +315,16 @@ bool test_arrays_of_objects() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 6: Nested arrays in objects
|
||||
* Test 13: Nested arrays in objects
|
||||
*/
|
||||
bool test_nested_arrays() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto data = json_to_object<R"({
|
||||
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);
|
||||
@@ -187,12 +338,12 @@ bool test_nested_arrays() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 7: Complex mixed structure
|
||||
* Test 14: Complex mixed structure with arrays and nested objects
|
||||
*/
|
||||
bool test_complex_mixed() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = json_to_object<R"({
|
||||
constexpr auto config = parse_json<R"({
|
||||
"app": "myapp",
|
||||
"version": 1.0,
|
||||
"config": {
|
||||
@@ -203,7 +354,7 @@ bool test_complex_mixed() {
|
||||
{"host": "server1", "port": 3000},
|
||||
{"host": "server2", "port": 3001}
|
||||
]
|
||||
})">;
|
||||
})">();
|
||||
|
||||
static_assert(std::string_view(config.app) == "myapp");
|
||||
static_assert(config.version == 1.0);
|
||||
@@ -221,176 +372,18 @@ bool test_complex_mixed() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 8: Empty object
|
||||
* Test 15: Empty arrays
|
||||
*/
|
||||
bool test_empty_object() {
|
||||
bool test_empty_arrays() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = json_to_object<"{}">;
|
||||
(void)config; // Suppress unused warning
|
||||
constexpr auto data = parse_json<R"({
|
||||
"empty": []
|
||||
})">();
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
static_assert(data.empty.size() == 0);
|
||||
|
||||
/**
|
||||
* Test 9: Negative numbers
|
||||
*/
|
||||
bool test_negative_numbers() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto data = json_to_object<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 10: Whitespace handling
|
||||
*/
|
||||
bool test_whitespace() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto data = json_to_object<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 11: String unescaping from inline helpers
|
||||
*/
|
||||
bool test_string_unescape() {
|
||||
TEST_START();
|
||||
|
||||
{
|
||||
constexpr auto pair = unescape_json_string<100>("hello world");
|
||||
ASSERT_EQUAL(std::string_view(pair.first.data(), pair.second), "hello world"sv);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr auto pair = unescape_json_string<100>(R"(hello\nworld)");
|
||||
ASSERT_EQUAL(std::string_view(pair.first.data(), pair.second), "hello\nworld"sv);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr auto pair = unescape_json_string<100>(R"(quote: \")");
|
||||
ASSERT_EQUAL(std::string_view(pair.first.data(), pair.second), "quote: \""sv);
|
||||
}
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 12: 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 13: User-defined literal
|
||||
*/
|
||||
bool test_user_defined_literal() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = R"({"version": 1.0, "name": "test"})"_json;
|
||||
|
||||
static_assert(config.version == 1.0);
|
||||
static_assert(std::string_view(config.name) == "test");
|
||||
|
||||
ASSERT_EQUAL(config.version, 1.0);
|
||||
ASSERT_EQUAL(std::string_view(config.name), "test"sv);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 14: Real-time system configuration
|
||||
*/
|
||||
bool test_realtime_config() {
|
||||
TEST_START();
|
||||
|
||||
constexpr auto config = json_to_object<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 15: Future #embed support demonstration
|
||||
*
|
||||
* Note: This test shows how external JSON files would be used with #embed.
|
||||
* Currently uses inline JSON matching test_config.json for compatibility.
|
||||
*/
|
||||
bool test_external_json_embed() {
|
||||
TEST_START();
|
||||
|
||||
// Future C++26 with #embed:
|
||||
// constexpr auto config = json_to_object<#embed "test_config.json">;
|
||||
|
||||
// Current workaround - inline the JSON from test_config.json
|
||||
constexpr auto config = json_to_object<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);
|
||||
ASSERT_EQUAL(data.empty.size(), 0);
|
||||
|
||||
TEST_SUCCEED();
|
||||
}
|
||||
@@ -399,18 +392,18 @@ 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_object() &&
|
||||
test_negative_numbers() &&
|
||||
test_whitespace() &&
|
||||
test_string_unescape() &&
|
||||
test_json_validation() &&
|
||||
test_user_defined_literal() &&
|
||||
test_realtime_config() &&
|
||||
test_external_json_embed();
|
||||
test_empty_arrays();
|
||||
}
|
||||
|
||||
} // namespace compile_time_json_tests
|
||||
|
||||
Reference in New Issue
Block a user