From 4df9bcdd5b42546f2f4470c0a8182a9ae4da5b81 Mon Sep 17 00:00:00 2001 From: nook24 Date: Fri, 21 Aug 2026 19:55:59 +0200 Subject: [PATCH 01/28] Declare RabbitMQ queues and exchange durable by default A queue that is neither durable nor exclusive is RabbitMQ's deprecated transient_nonexcl_queues feature. That is no longer only a deprecation: RabbitMQ 4 reports it as denied_by_default and refuses every queue.declare with a connection exception. Because Connect() returns false as soon as a declare fails, RabbitMQ does not merely lose those queues - it fails to connect at all, and publishes nothing. Verified against RabbitMQ 4.3.5: rabbitmqctl list_deprecated_features transient_nonexcl_queues | denied_by_default | denied Durable queues have worked since AMQP 0-9-1, so this is the only setting that works on every RabbitMQ version, from 3.x through 4.x. This does not put monitoring events on disk. Queue durability and message persistence are separate AMQP properties: durable stores the queue *definition*, while a message is only written durably when its publisher marks it persistent. SendMessage passes properties=nullptr, so every message this module publishes is transient and stays that way. The RabbitMQ documentation is explicit that transient messages "will be discarded during recovery, even if they were stored in durable queues". So the behaviour Statusengine wants is unchanged: the queues buffer in RAM while no worker is connected, and a RabbitMQ restart empties them. Measured rather than assumed - 5 messages in a durable queue, RabbitMQ restart, queue present with 0 messages. Publishing 20,000 events took 0.22-0.23s with durable queues and 0.22-0.23s without, three runs each. The exchange follows the queues. A transient exchange loses its bindings on a RabbitMQ restart while the durable queues survive, so the pair is kept consistent; both are metadata only and neither costs per-message I/O. Both values remain configurable, so an installation that needs the old behaviour can still set DurableQueues = false - on a RabbitMQ old enough to accept it. Tested with Naemon 1.4.1 against RabbitMQ 3.9.27, publishing to all configured queues, alongside the Go worker consuming them. Because both sides declare the same queues and AMQP answers a mismatched redeclare with a 406 PRECONDITION_FAILED rather than reconciling it, this change belongs with the matching one in Statusengine Go Worker; both start orders were checked, module first and worker first, with no 406 either way. Note for existing installations: the queues already exist as non-durable and cannot be redeclared. They have to be deleted once, with the monitoring core and the worker stopped. Messages waiting in them are lost, which is acceptable for the same reason the design is - they are transient and would not have survived a broker restart either. --- src/Configuration.h | 19 +++++++++++++++++-- statusengine.toml | 8 ++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/Configuration.h b/src/Configuration.h index ee072a9..673601d 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -132,8 +132,23 @@ namespace statusengine { Exchange = GetTomlDefault<>(tbl, "Exchange", std::string("statusengine")); - DurableExchange = GetTomlDefault<>(tbl, "DurableExchange", false); - DurableQueues = GetTomlDefault<>(tbl, "DurableQueues", false); + // Durable by default. A queue that is neither durable nor exclusive is + // RabbitMQ's deprecated transient_nonexcl_queues feature: 3.13 warns once per + // broker start, 4.x refuses the declare outright, and the broker then fails to + // connect at all (see Connect()). Durable has worked since AMQP 0-9-1, so it is + // the only value that works on every supported broker version. + // + // This does not put events on disk. Queue durability and message persistence + // are separate: durable stores the queue *definition*, while messages are only + // written durably when the publisher marks them persistent - and SendMessage + // passes properties=nullptr, i.e. transient. So the queues still buffer in RAM + // and a broker restart still empties them, which is the intended behaviour. + // + // The exchange follows the queues: a transient exchange loses its bindings on a + // broker restart while the durable queues survive, and keeping the pair + // consistent costs nothing, both being metadata only. + DurableExchange = GetTomlDefault<>(tbl, "DurableExchange", true); + DurableQueues = GetTomlDefault<>(tbl, "DurableQueues", true); SSL = GetTomlDefault<>(tbl, "SSL", false); diff --git a/statusengine.toml b/statusengine.toml index 7139368..0bf2b5a 100644 --- a/statusengine.toml +++ b/statusengine.toml @@ -45,8 +45,12 @@ WorkerCommand = "statusngin_cmd" ##Vhost = "/" ##Timeout = 30 ##Exchange = "statusengine" -##DurableExchange = false -##DurableQueues = false +## Durable queues and exchange. Required by RabbitMQ 4, which refuses to declare a queue +## that is neither durable nor exclusive. This stores queue and exchange *definitions* on +## disk, not the events: messages are published transient, so the queues still buffer in +## RAM and are still emptied by a broker restart. +##DurableExchange = true +##DurableQueues = true ##SSL = false ##SSL_verify = true ##SSL_cacert = "" From 166b02871f0956b2e83c354bf5f138eed1300682 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 18:58:14 +0200 Subject: [PATCH 02/28] Fix memory errors and crashes in the event hot paths The module runs inside the monitoring core's address space, so each of these takes naemon down with it or grows unbounded over a long uptime. EncodeString() had four independent defects and ran on every check output: * the charset comparison was inverted, so the function did the opposite of its job - non UTF-8 was passed through untouched while UTF-8 was sent through a pointless conversion * uchardet owns the string returned by uchardet_get_charset(), deleting it corrupted the heap * iconv() advances the output pointer, so the buffer was released from an address in its middle * iconv() returns the number of irreversible conversions, which was used as if it were the output length It now keeps separate cursors, derives the written length from how far the output cursor moved, and falls back to the unchanged input when the charset cannot be detected or iconv_open() fails. It moved to Encoding.{h,cpp}, which depends on neither naemon nor IStatusengine, so it can be tested on its own; conversion problems are reported through a callback that Nebmodule wires to the log. Strings stored in a check_result were allocated with new[] but are released by naemon's free_check_result() with free(). get_json_string_c() now allocates those with strndup, while get_json_string() keeps new[] for the strings that stay on the C++ side. Assembling the plugin output moved into BuildCheckOutput() and hands ownership over explicitly instead of aliasing, which also fixes the leak of perf_data when only long_output was present. RabbitmqClient::Connect() allocated a connection state and returned from nine failure paths without destroying it. Since SendMessage() retries the connection for every single message while the broker is unreachable, this leaked about 9 kB per attempt - measured at 17.5 MB over 2000 attempts. CloseConnection() now also tolerates a connection that was never opened, which is reachable when an earlier handler fails during startup, and Worker() no longer touches a cleared handle. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- src/Encoding.cpp | 76 ++++++++++++++++ src/Encoding.h | 44 +++++++++ src/MessageHandler/GearmanClient.cpp | 12 ++- src/MessageHandler/MessageHandler.h | 123 ++++++++++++++++++-------- src/MessageHandler/RabbitmqClient.cpp | 40 ++++++++- src/Nebmodule.cpp | 39 ++------ src/Nebmodule.h | 9 +- src/gsl.h | 2 + src/meson.build | 2 +- 9 files changed, 269 insertions(+), 78 deletions(-) create mode 100644 src/Encoding.cpp create mode 100644 src/Encoding.h diff --git a/src/Encoding.cpp b/src/Encoding.cpp new file mode 100644 index 0000000..b5d202e --- /dev/null +++ b/src/Encoding.cpp @@ -0,0 +1,76 @@ +#include "Encoding.h" + +#include +#include + +#include "gsl.h" + +namespace statusengine { + + Encoder::Encoder() : uc(uchardet_new()), warn() {} + + Encoder::~Encoder() { + uchardet_delete(uc); + uc = nullptr; + } + + void Encoder::SetWarnCallback(WarnCallback callback) { + warn = std::move(callback); + } + + std::string Encoder::ToUtf8(const char *inputData) { + if (inputData == nullptr) { + return std::string(); + } + // we can't use strnlen here, we don't have any idea of the length here... + const auto inputLength = std::strlen(inputData); + if (inputLength == 0) { + return std::string(); + } + + uchardet_handle_data(uc, inputData, inputLength); //TODO error handling + uchardet_data_end(uc); + // uchardet owns the returned string, it must not be freed. Copy it before the reset + // below, which clears the detection result. + auto detectedCharset = uchardet_get_charset(uc); + const std::string charset(detectedCharset == nullptr ? "" : detectedCharset); + uchardet_reset(uc); + + // An empty charset is uchardet's documented failure return, and there is nothing to + // convert if the input already is UTF-8. + if (charset.empty() || charset == "UTF-8") { + return std::string(inputData, inputLength); + } + + auto cd = iconv_open("UTF-8", charset.c_str()); + if (cd == reinterpret_cast(-1)) { + if (warn) { + warn("Cannot convert from charset '" + charset + "' to UTF-8, passing the string through unchanged"); + } + return std::string(inputData, inputLength); + } + auto closeIconv = gsl::finally([&cd] { iconv_close(cd); }); + + // utf-8 possibly needs up to 4 bytes for a single character :/ + std::string result(inputLength * 4, '\0'); + + // iconv advances both cursors, so it must not be handed the pointers we still need: + // inputData belongs to the caller, and result's buffer has to be freed from its start. + char *inputCursor = const_cast(inputData); + size_t inputLeft = inputLength; + char *outputCursor = &result[0]; + size_t outputLeft = result.size(); + + if (iconv(cd, &inputCursor, &inputLeft, &outputCursor, &outputLeft) == static_cast(-1)) { + if (warn) { + warn("Could not convert a string from charset '" + charset + "' to UTF-8, passing it through unchanged"); + } + return std::string(inputData, inputLength); + } + + // iconv returns the number of irreversible conversions, not the output length. The + // number of bytes written is how far the output cursor moved. + result.resize(static_cast(outputCursor - &result[0])); + return result; + } +} // namespace statusengine diff --git a/src/Encoding.h b/src/Encoding.h new file mode 100644 index 0000000..527b7e4 --- /dev/null +++ b/src/Encoding.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include + +namespace statusengine { + + /** + * Detects the charset of a string and converts it to UTF-8. + * + * This deliberately does not depend on naemon or on IStatusengine, so that it can be + * built and tested on its own. Problems during a conversion are reported through the + * warning callback instead of being logged directly. + * + * Holds a uchardet handle and is not thread safe. + */ + class Encoder { + public: + using WarnCallback = std::function; + + Encoder(); + ~Encoder(); + + Encoder(const Encoder &) = delete; + Encoder(Encoder &&) = delete; + Encoder &operator=(const Encoder &) = delete; + Encoder &operator=(Encoder &&) = delete; + + void SetWarnCallback(WarnCallback callback); + + /** + * Returns inputData as UTF-8. A null or empty input yields an empty string. If the + * charset cannot be detected or the conversion fails, the input is returned + * unchanged and the warning callback is invoked. + */ + std::string ToUtf8(const char *inputData); + + private: + uchardet_t uc; + WarnCallback warn; + }; +} // namespace statusengine diff --git a/src/MessageHandler/GearmanClient.cpp b/src/MessageHandler/GearmanClient.cpp index 550e169..56296a3 100644 --- a/src/MessageHandler/GearmanClient.cpp +++ b/src/MessageHandler/GearmanClient.cpp @@ -88,9 +88,15 @@ namespace statusengine { } void GearmanClient::SendMessage(Queue queue, const std::string &message) { - auto queueName = queueNames->find(queue)->second; - auto ret = gearman_client_do_background(client, queueName.c_str(), nullptr, message.c_str(), message.length(), - nullptr); + auto queueNameIt = queueNames->find(queue); + if (queueNameIt == queueNames->end()) { + auto QueueId = QueueNameHandler::Instance().QueueIds(); + se->Log() << "No gearman queue configured for " << QueueId.at(queue) << ", dropping message" + << LogLevel::Error; + return; + } + auto ret = gearman_client_do_background(client, queueNameIt->second.c_str(), nullptr, message.c_str(), + message.length(), nullptr); if (!gearman_success(ret)) { se->Log() << "Could not write message to gearman queue: " << gearman_client_error(client) << LogLevel::Error; diff --git a/src/MessageHandler/MessageHandler.h b/src/MessageHandler/MessageHandler.h index 15ac24c..2d43abb 100644 --- a/src/MessageHandler/MessageHandler.h +++ b/src/MessageHandler/MessageHandler.h @@ -20,15 +20,34 @@ namespace statusengine { explicit MessageHandler(IStatusengine *se) : se(se) {} + /** + * Copy a json string into a buffer owned by the C++ side. Release it with delete[]. + */ inline static char *get_json_string(json_object *obj) { auto jsonChars = json_object_get_string(obj); + if (jsonChars == nullptr) { + return nullptr; + } auto jsonCharsLen = json_object_get_string_len(obj); char *chars = new char[jsonCharsLen + 1]; - std::strncpy(chars, jsonChars, jsonCharsLen); + std::memcpy(chars, jsonChars, jsonCharsLen); chars[jsonCharsLen] = 0; // set last byte to zero return chars; } + /** + * Copy a json string into a buffer allocated by the malloc family. Everything that + * is stored in a check_result has to be allocated this way: free_check_result() + * releases those strings with free(), which must not be paired with new[]. + */ + inline static char *get_json_string_c(json_object *obj) { + auto jsonChars = json_object_get_string(obj); + if (jsonChars == nullptr) { + return nullptr; + } + return strndup(jsonChars, json_object_get_string_len(obj)); + } + void ProcessMessage(WorkerQueue workerQueue, const std::string &message) override { json_object *obj = json_tokener_parse(message.c_str()); if (obj == nullptr) { @@ -149,30 +168,79 @@ namespace statusengine { IStatusengine *se; + /** + * Join output, long output and perf data the way naemon expects them in a single + * plugin output string. The result is allocated with malloc, so that + * free_check_result() can release it. Returns nullptr if there is nothing to join, + * i.e. if at most one of the parts is present. + */ + inline static char *BuildCheckOutput(const char *output, const char *longOutput, const char *perfData) { + if (output == nullptr || (longOutput == nullptr && perfData == nullptr)) { + return nullptr; + } + + size_t strLen; + if (longOutput == nullptr) { + // output + pipe + perfData + newline + zero byte + strLen = std::strlen(output) + std::strlen(perfData) + 3; + } + else if (perfData == nullptr) { + // output + newline + longOutput + zero byte + strLen = std::strlen(output) + std::strlen(longOutput) + 2; + } + else { + // output + pipe + perfData + newline + longOutput + zero byte + strLen = std::strlen(output) + std::strlen(perfData) + std::strlen(longOutput) + 3; + } + + char *fullOutput = static_cast(malloc(strLen)); + if (fullOutput == nullptr) { + return nullptr; + } + + if (longOutput == nullptr) { + std::snprintf(fullOutput, strLen, "%s|%s\n", output, perfData); + } + else if (perfData == nullptr) { + std::snprintf(fullOutput, strLen, "%s\n%s", output, longOutput); + } + else { + std::snprintf(fullOutput, strLen, "%s|%s\n%s", output, perfData, longOutput); + } + return fullOutput; + } + void ParseCheckResult(json_object *obj) { check_result cr; init_check_result(&cr); char *output = nullptr; char *longOutput = nullptr; char *perfData = nullptr; - char *fullOutput = nullptr; + // These three stay ours unless ownership is explicitly handed to cr.output below, + // in which case the local pointer is cleared. Everything still held here at the + // end of the function is ours to release; free_check_result() takes care of cr. + auto freeParts = gsl::finally([&] { + free(output); + free(longOutput); + free(perfData); + }); json_object_object_foreach(obj, cKey, jsonValue) { std::string jsonKey(cKey); if (jsonKey.compare("host_name") == 0) { - cr.host_name = get_json_string(jsonValue); + cr.host_name = get_json_string_c(jsonValue); } else if (jsonKey.compare("service_description") == 0) { - cr.service_description = get_json_string(jsonValue); + cr.service_description = get_json_string_c(jsonValue); } else if (jsonKey.compare("output") == 0) { - output = get_json_string(jsonValue); + output = get_json_string_c(jsonValue); } else if (jsonKey.compare("long_output") == 0) { - longOutput = get_json_string(jsonValue); + longOutput = get_json_string_c(jsonValue); } else if (jsonKey.compare("perf_data") == 0) { - perfData = get_json_string(jsonValue); + perfData = get_json_string_c(jsonValue); } else if (jsonKey.compare("check_type") == 0) { cr.check_type = json_object_get_int64(jsonValue); @@ -197,35 +265,18 @@ namespace statusengine { } } - if (output != nullptr && longOutput == nullptr) { - if (perfData == nullptr) { + cr.output = BuildCheckOutput(output, longOutput, perfData); + if (cr.output == nullptr) { + // Only a single part was given, hand it over instead of copying it. + if (output != nullptr) { cr.output = output; - } else { - // we need a new string with size of strings + pipe + newline + zero byte - auto strLen = std::strlen(output) + std::strlen(perfData) + 3; - fullOutput = new char[strLen]; - std::snprintf(fullOutput, strLen, "%s|%s\n", output, perfData); - cr.output = fullOutput; + output = nullptr; } - } - else if (output != nullptr && longOutput != nullptr) { - if (perfData == nullptr) { - // we need a new string with size of strings + newline + zero byte - auto strLen = std::strlen(output) + std::strlen(longOutput) + 2; - fullOutput = new char[strLen]; - std::snprintf(fullOutput, strLen, "%s\n%s", output, longOutput); - cr.output = fullOutput; - } else { - // we need a new string with size of strings + pipe + newline + zero byte - auto strLen = std::strlen(output) + std::strlen(longOutput) + std::strlen(perfData) + 3; - fullOutput = new char[strLen]; - std::snprintf(fullOutput, strLen, "%s|%s\n%s", output, perfData, longOutput); - cr.output = fullOutput; + else if (longOutput != nullptr) { + cr.output = longOutput; + longOutput = nullptr; } } - else if (longOutput != nullptr && output == nullptr) { - cr.output = longOutput; - } if (cr.host_name == nullptr) { se->Log() << "Received hostcheck without host_name" << LogLevel::Warning; @@ -242,14 +293,8 @@ namespace statusengine { process_check_result(&cr); } - // deletes hostname, service_description and output + // frees host_name, service_description and output free_check_result(&cr); - if (fullOutput != nullptr) { - // free_check_result only frees fulloutput in this case - delete output; - delete longOutput; - delete perfData; - } } void ParseScheduleCheck(json_object *obj) { diff --git a/src/MessageHandler/RabbitmqClient.cpp b/src/MessageHandler/RabbitmqClient.cpp index 8259ee2..ea40178 100644 --- a/src/MessageHandler/RabbitmqClient.cpp +++ b/src/MessageHandler/RabbitmqClient.cpp @@ -18,6 +18,13 @@ namespace statusengine { } bool RabbitmqClient::CloseConnection(bool quiet) { + // Reachable with conn == nullptr: if an earlier handler fails in + // MessageHandlerList::Connect(), the remaining clients are destroyed without ever + // having connected, and the amqp calls below do not accept a null connection. + if (conn == nullptr) { + return true; + } + bool result = true; if (!CheckAMQPReply(amqp_channel_close(conn, 1, AMQP_REPLY_SUCCESS), "Closing amqp channel", quiet)) { result = false; @@ -32,6 +39,9 @@ namespace statusengine { } result = false; } + conn = nullptr; + socket = nullptr; + connected = false; return result; } @@ -97,6 +107,20 @@ namespace statusengine { bool RabbitmqClient::Connect(bool quiet) { conn = amqp_new_connection(); + + // Every failure path below used to return without releasing the connection state, + // and with it the socket's file descriptor once the socket was open. SendMessage() + // retries Connect() for every single message while the broker is unreachable, so + // that leak grows without bound. + bool connectOk = false; + auto cleanup = gsl::finally([&] { + if (!connectOk) { + amqp_destroy_connection(conn); + conn = nullptr; + socket = nullptr; + } + }); + if (cfg->SSL) { socket = amqp_ssl_socket_new(conn); #ifndef WITH_RABBITMQ_CX080 @@ -198,12 +222,20 @@ namespace statusengine { } connected = true; + connectOk = true; se->Log() << "Rabbitmq (re)connected" << LogLevel::Info; return true; } void RabbitmqClient::SendMessage(Queue queue, const std::string &message) { - auto queueName = queueNames->find(queue)->second; + auto queueNameIt = queueNames->find(queue); + if (queueNameIt == queueNames->end()) { + auto QueueId = QueueNameHandler::Instance().QueueIds(); + se->Log() << "No rabbitmq queue configured for " << QueueId.at(queue) << ", dropping message" + << LogLevel::Error; + return; + } + auto queueName = queueNameIt->second; if (connected || Connect(true)) { amqp_bytes_t message_bytes; message_bytes.len = message.length(); @@ -223,6 +255,12 @@ namespace statusengine { } bool RabbitmqClient::Worker(unsigned long &counter) { + // CloseConnection() clears conn, and the amqp calls below do not accept a null + // connection. Reconnecting is SendMessage()'s job, not the worker's. + if (conn == nullptr || !connected) { + return false; + } + amqp_rpc_reply_t res; amqp_envelope_t envelope; diff --git a/src/Nebmodule.cpp b/src/Nebmodule.cpp index 44be9d0..f5a8112 100644 --- a/src/Nebmodule.cpp +++ b/src/Nebmodule.cpp @@ -3,9 +3,9 @@ #include #include #include -#include #include "EventCallback.h" +#include "gsl.h" #include "Statusengine.h" // This is required by naemon @@ -17,14 +17,17 @@ namespace statusengine { int Nebmodule::Init(nebmodule *handle, std::string args) { se = new Statusengine(handle, std::move(args)); - uc = uchardet_new(); + encoder.SetWarnCallback([this](const std::string &message) { + se->Log() << message << LogLevel::Warning; + }); return se->Init(); } int Nebmodule::Deinit(int) { + // The encoder outlives se, so it must not keep logging through it. + encoder.SetWarnCallback(nullptr); delete se; - uchardet_delete(uc); - uc = nullptr; + se = nullptr; return 0; } @@ -218,32 +221,8 @@ namespace statusengine { #endif // BUILD_NAGIOS } - std::string Nebmodule::EncodeString(char *inputData) { - if(inputData == nullptr) { - return std::string(); - } - auto lendata = std::strlen(inputData); // we can't use strnlen here, we don't have any idea of the length here... - uchardet_handle_data(uc, inputData, lendata); //TODO error handling - uchardet_data_end(uc); - auto charset = uchardet_get_charset(uc); - uchardet_reset(uc); - - if(std::strcmp(charset, "UTF-8")) { - // We don't have to convert it, if it is already UTF-8 - return std::string(inputData, lendata); - } - auto outputDataLength = lendata*4; - char *outputData = new char[outputDataLength]; // utf-8 possibly needs up to 4 bytes for a single character :/ - - auto cd = iconv_open("UTF-8", charset); - auto outputLength = iconv(cd, &inputData, &lendata, &outputData, &outputDataLength); - std::string result(outputData, outputLength); - iconv_close(cd); - - delete [] outputData; - delete [] charset; - - return result; + std::string Nebmodule::EncodeString(const char *inputData) { + return encoder.ToUtf8(inputData); } } // namespace statusengine diff --git a/src/Nebmodule.h b/src/Nebmodule.h index c8d54c8..a3ee767 100644 --- a/src/Nebmodule.h +++ b/src/Nebmodule.h @@ -1,7 +1,8 @@ #pragma once #include -#include + +#include "Encoding.h" #ifndef BUILD_NAGIOS extern "C" { @@ -62,13 +63,13 @@ namespace statusengine { void DeleteDowntime(const char *hostname, const char *service_description, time_t start_time, time_t end_time, const char *comment); - std::string EncodeString(char *data); + std::string EncodeString(const char *data); private: - explicit Nebmodule() : se(nullptr), uc(nullptr) {} + explicit Nebmodule() : se(nullptr), encoder() {} Statusengine *se; - uchardet_t uc; + Encoder encoder; }; } // namespace statusengine diff --git a/src/gsl.h b/src/gsl.h index cd6351e..11531ad 100644 --- a/src/gsl.h +++ b/src/gsl.h @@ -17,6 +17,8 @@ #pragma once +#include // std::move, std::forward + namespace gsl { template< class F > diff --git a/src/meson.build b/src/meson.build index 0206048..738bdc2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,4 @@ -statusengine_sources = ['Nebmodule.cpp', 'Statusengine.cpp', 'MessageHandler/MessageHandlerList.cpp'] +statusengine_sources = ['Encoding.cpp', 'Nebmodule.cpp', 'Statusengine.cpp', 'MessageHandler/MessageHandlerList.cpp'] statusengine_deps = [uchardet_dep, jsonc_dep, toml_dep] statusengine_cpp_args = [] From c8ae2dcaea0315f4cd4706d90e4b2166ef4f8590 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 18:59:45 +0200 Subject: [PATCH 03/28] Revive the build, CI and packaging The move from cmake to meson in df3837f deleted CMakeLists.txt but left the infrastructure that used it behind, so both the CI and the documented developer workflow have been broken ever since: * .gitlab-ci.yml ran cmake, and was GitLab CI in a repository hosted on GitHub. All of its targets (ubuntu trusty/xenial/bionic, debian jessie/stretch) are end of life, and it never installed libuchardet-dev, which meson requires - so it would not have built even with CMakeLists.txt still in place. Replaced by a GitHub Actions workflow on ubuntu 22.04/24.04 and debian 12 that also covers -Dnagios=true, -Dgearman=false and -Drabbitmq=false; none of the build options were exercised before. * naemon.Dockerfile ran cmake too, so docker-compose up --build failed. It now uses meson, is based on ubuntu 24.04 instead of the EOL bionic, and builds naemon 1.5.2 instead of 1.0.10 from 2019. Meson installs into /lib/ by default, while statusengine.cfg and the README both expect /lib. The module therefore ended up somewhere naemon would not load it from. Pinned with libdir=lib. The version existed four times over with three different values: meson.build said 4.2.0, VERSION said 4.0.2, Statusengine.cpp hardcoded 4.0.0 and the last release tag is v4.0.4. meson.build is now the single source and is generated into version.h; 4.0.4 was chosen because it is the version that was actually released - 4.2.0 was set once during the meson migration and never shipped. VERSION is gone, its only consumer was the CI that this commit replaces. Also switches to C++17 and replaces the deprecated get_pkgconfig_variable, and drops cmake from the README dependency lists, where it was no longer needed. The first line of .dockerignore was "/builddevtools/**", a merge of "/build" and "devtools/**" that ignored neither. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- .dockerignore | 1 + .github/workflows/build.yml | 93 ++++++++++++++ .gitlab-ci.yml | 236 ------------------------------------ README.md | 13 +- VERSION | 1 - meson.build | 9 +- naemon.Dockerfile | 10 +- src/Statusengine.cpp | 3 +- src/meson.build | 6 +- 9 files changed, 123 insertions(+), 249 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .gitlab-ci.yml delete mode 100644 VERSION diff --git a/.dockerignore b/.dockerignore index 14ef32f..ac7ddbc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ +/build devtools/** !devtools/buildenv/** .vscode/** diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8bc0cc8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,93 @@ +name: build + +on: + push: + branches: [ master, rabbitmq-4 ] + pull_request: + workflow_dispatch: + +jobs: + naemon: + name: naemon (${{ matrix.image }}, ${{ matrix.options }}) + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: + - ubuntu:22.04 + - ubuntu:24.04 + - debian:12 + options: + - '' + include: + # The build options are only exercised on a single image, they are + # independent of the distribution. + - image: ubuntu:24.04 + options: '-Dgearman=false' + - image: ubuntu:24.04 + options: '-Drabbitmq=false' + steps: + - name: Install dependencies + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + g++ pkg-config meson ninja-build git ca-certificates \ + libglib2.0-dev libgearman-dev libjson-c-dev libuchardet-dev \ + librabbitmq-dev libssl-dev uuid-dev + + - uses: actions/checkout@v4 + + - name: Provide naemon headers + # We only need the headers to build the module, not libnaemon itself, so the + # checked in stubs stand in for an installed naemon-dev package. + run: | + set -e + mkdir -p /opt/naemon/include /usr/local/lib/pkgconfig + ln -s "$(readlink -f devtools/ci/naemon)" /opt/naemon/include/naemon + ln -s "$(readlink -f devtools/ci/naemon.pc)" /usr/local/lib/pkgconfig/naemon.pc + + - name: Build + env: + PKG_CONFIG_PATH: /usr/local/lib/pkgconfig + run: | + meson setup --buildtype=release ${{ matrix.options }} build + ninja -C build + + nagios: + name: nagios (${{ matrix.image }}) + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: + - ubuntu:24.04 + - debian:12 + steps: + - name: Install dependencies + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + g++ pkg-config meson ninja-build git ca-certificates \ + libglib2.0-dev libgearman-dev libjson-c-dev libuchardet-dev \ + librabbitmq-dev libssl-dev uuid-dev + + - uses: actions/checkout@v4 + + - name: Provide nagios headers + # The include dir has to live outside the source tree, meson rejects + # include_directories() pointing back into it. + run: | + set -e + mkdir -p /opt/nagios/include + ln -s "$(readlink -f devtools/ci/nagios)" /opt/nagios/include/nagios + + - name: Build + run: | + meson setup --buildtype=release -Dnagios=true \ + -Dnagios_include_dir=/opt/nagios/include build + ninja -C build + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index b91c977..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,236 +0,0 @@ -build:trusty: - image: ubuntu:trusty - stage: build - script: - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install cmake3 gcc g++ build-essential libglib2.0-dev libgearman-dev uuid-dev libicu-dev libjson-c-dev libssl-dev pkg-config librabbitmq-dev - - | - set -e - mkdir -p /usr/local/lib/pkgconfig /opt/naemon/include - ln -s $(readlink -f devtools/ci/naemon) /opt/naemon/include/naemon - ln -s $(readlink -f devtools/ci/naemon.pc) /usr/local/lib/pkgconfig/naemon.pc - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE - -build:xenial: - image: ubuntu:xenial - stage: build - script: - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install cmake gcc g++ build-essential libglib2.0-dev libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev - - | - set -e - mkdir -p /usr/local/lib/pkgconfig /opt/naemon/include - ln -s $(readlink -f devtools/ci/naemon) /opt/naemon/include/naemon - ln -s $(readlink -f devtools/ci/naemon.pc) /usr/local/lib/pkgconfig/naemon.pc - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE - -build:bionic: - image: ubuntu:bionic - stage: build - script: - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install cmake gcc g++ build-essential libglib2.0-dev libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev - - | - set -e - mkdir -p /usr/local/lib/pkgconfig /opt/naemon/include - ln -s $(readlink -f devtools/ci/naemon) /opt/naemon/include/naemon - ln -s $(readlink -f devtools/ci/naemon.pc) /usr/local/lib/pkgconfig/naemon.pc - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE - -build:jessie: - image: debian:jessie - stage: build - script: - - echo "deb http://ftp.debian.org/debian jessie-backports main" > /etc/apt/sources.list.d/backports.list - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y -t jessie-backports install cmake gcc g++ build-essential libglib2.0-dev libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev - - | - set -e - mkdir -p /usr/local/lib/pkgconfig /opt/naemon/include - ln -s $(readlink -f devtools/ci/naemon) /opt/naemon/include/naemon - ln -s $(readlink -f devtools/ci/naemon.pc) /usr/local/lib/pkgconfig/naemon.pc - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE - -build:stretch: - image: debian:stretch - stage: build - script: - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install cmake gcc g++ build-essential libglib2.0-dev libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev - - | - set -e - mkdir -p /usr/local/lib/pkgconfig /opt/naemon/include - ln -s $(readlink -f devtools/ci/naemon) /opt/naemon/include/naemon - ln -s $(readlink -f devtools/ci/naemon.pc) /usr/local/lib/pkgconfig/naemon.pc - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE - -build:nagios:trusty: - image: ubuntu:trusty - stage: build - script: - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install cmake3 gcc g++ build-essential libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev libglib2.0-dev librabbitmq-dev - - | - set -e - mkdir -p /opt/nagios/include - ln -s $(readlink -f devtools/ci/nagios) /opt/nagios/include/nagios - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DBUILD_NAGIOS=ON -DNAGIOS_INCLUDE_DIR=/opt/nagios/include .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE - -build:nagios:xenial: - image: ubuntu:xenial - stage: build - script: - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install cmake gcc g++ build-essential libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev libglib2.0-dev - - | - set -e - mkdir -p /opt/nagios/include - ln -s $(readlink -f devtools/ci/nagios) /opt/nagios/include/nagios - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DBUILD_NAGIOS=ON -DNAGIOS_INCLUDE_DIR=/opt/nagios/include .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE - -build:nagios:bionic: - image: ubuntu:bionic - stage: build - script: - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install cmake gcc g++ build-essential libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev libglib2.0-dev - - | - set -e - mkdir -p /opt/nagios/include - ln -s $(readlink -f devtools/ci/nagios) /opt/nagios/include/nagios - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DBUILD_NAGIOS=ON -DNAGIOS_INCLUDE_DIR=/opt/nagios/include .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE - -build:nagios:jessie: - image: debian:jessie - stage: build - script: - - echo "deb http://ftp.debian.org/debian jessie-backports main" > /etc/apt/sources.list.d/backports.list - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y -t jessie-backports install cmake gcc g++ build-essential libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev libglib2.0-dev - - | - set -e - mkdir -p /opt/nagios/include - ln -s $(readlink -f devtools/ci/nagios) /opt/nagios/include/nagios - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DBUILD_NAGIOS=ON -DNAGIOS_INCLUDE_DIR=/opt/nagios/include .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE - -build:nagios:stretch: - image: debian:stretch - stage: build - script: - - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install cmake gcc g++ build-essential libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev libglib2.0-dev - - | - set -e - mkdir -p /opt/nagios/include - ln -s $(readlink -f devtools/ci/nagios) /opt/nagios/include/nagios - - | - mkdir build - cd build - cmake -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -DBUILD_NAGIOS=ON -DNAGIOS_INCLUDE_DIR=/opt/nagios/include .. - make -j5 - mv libstatusengine.so .. - artifacts: - name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" - paths: - - libstatusengine.so - - README.md - - VERSION - - LICENSE diff --git a/README.md b/README.md index 5bc905f..9056062 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ https://mesonbuild.com/Getting-meson.html #### Ubuntu/Debian ```bash -apt install git python3-pip gcc g++ cmake build-essential libglib2.0-dev libgearman-dev uuid-dev libuchardet-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev +apt install git python3-pip gcc g++ build-essential libglib2.0-dev libgearman-dev uuid-dev libuchardet-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev pip3 install meson ninja ``` #### CentOS ```bash -yum install git python-pip gcc gcc-c++ cmake3 pkgconfig librabbitmq-devel libgearman-devel libuchardet-devel json-c-devel openssl-devel glib2-devel +yum install git python-pip gcc gcc-c++ pkgconfig librabbitmq-devel libgearman-devel libuchardet-devel json-c-devel openssl-devel glib2-devel pip install meson ninja ``` @@ -45,6 +45,13 @@ ninja -C build ``` ### Nagios + +> **Note:** Nagios support is a deprecation candidate. It is kept for now, but it doubles +> the maintenance cost of some parts of the module (`Nebmodule::DeleteDowntime()` for +> example exists twice in nearly identical form). If you build the broker against Nagios, +> please open an issue and say so, otherwise the Nagios code paths may be removed in a +> future release. + Please make sure you have set up Nagios like described in the [docs](https://statusengine.org/tutorials/install-nagios4-focal/). #### Ubuntu/Debian @@ -120,7 +127,7 @@ https://github.com/naemon/naemon-core/tree/master/.vscode 2. Install Statusengine Broker dependencies ``` -apt-get install git python3-pip gcc g++ cmake build-essential libglib2.0-dev libgearman-dev uuid-dev libuchardet-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev gearman-job-server +apt-get install git python3-pip gcc g++ build-essential libglib2.0-dev libgearman-dev uuid-dev libuchardet-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev gearman-job-server pip3 install meson ninja ``` diff --git a/VERSION b/VERSION deleted file mode 100644 index 4d54dad..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -4.0.2 diff --git a/meson.build b/meson.build index b0f1c06..b98cbdd 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,9 @@ project('statusengine-broker', 'cpp', - version: '4.2.0', - default_options: ['warning_level=3', 'cpp_std=c++11'], + version: '4.0.4', + default_options: ['warning_level=3', 'cpp_std=c++17', + # The module is loaded by an absolute path from naemon.cfg, so it goes + # straight into /lib rather than a multiarch subdirectory. + 'libdir=lib'], license: 'GPLv2+') uchardet_dep = dependency('uchardet') @@ -14,7 +17,7 @@ else glib_dep = dependency('glib-2.0') naemon_full_dep = dependency('naemon') # We don't want to link against libnaemon - naemon_dep = declare_dependency(include_directories: include_directories(naemon_full_dep.get_pkgconfig_variable('includedir'))).as_system('system') + naemon_dep = declare_dependency(include_directories: include_directories(naemon_full_dep.get_variable(pkgconfig: 'includedir'))).as_system('system') endif toml_dep = declare_dependency(include_directories: include_directories('vendor')).as_system('system') diff --git a/naemon.Dockerfile b/naemon.Dockerfile index b6b5746..d2d9896 100644 --- a/naemon.Dockerfile +++ b/naemon.Dockerfile @@ -1,10 +1,10 @@ -FROM ubuntu:bionic +FROM ubuntu:24.04 RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y cmake gcc g++ gdb build-essential libglib2.0-dev libgearman-dev uuid-dev libicu-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev gearman-job-server gearman-tools automake gperf help2man libtool rabbitmq-server wget tar && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y gcc g++ gdb build-essential meson ninja-build libglib2.0-dev libgearman-dev uuid-dev libuchardet-dev libjson-c-dev pkg-config libssl-dev librabbitmq-dev gearman-job-server gearman-tools automake gperf help2man libtool rabbitmq-server wget tar && \ apt-get clean -ENV NAEMON_VERSION=1.0.10 NAEMON_CHECKSUM=37353dd51a547b9e7f1b7d5e7e0b1716f9ea526d737c797d85431a83874736e5 +ENV NAEMON_VERSION=1.5.2 NAEMON_CHECKSUM=7cc09f25d6350a778fa475f1fe60b4dbb2386a79c6c4e8151446231ece0cd345 RUN useradd -d /opt/naemon -m -s /bin/bash --system naemon && \ wget -O /v${NAEMON_VERSION}.tar.gz https://github.com/naemon/naemon-core/archive/v${NAEMON_VERSION}.tar.gz && \ @@ -25,6 +25,8 @@ CMD ["/usr/bin/wait-for-it.sh", "rabbit:5672", "--", "/opt/naemon/bin/naemon", " COPY . /broker/source/ -RUN cd /broker/build && export PKG_CONFIG_PATH=/opt/naemon/lib/pkgconfig && cmake -DCMAKE_INSTALL_PREFIX:PATH=/opt/naemon ../source && make -j2 && make install +RUN export PKG_CONFIG_PATH=/opt/naemon/lib/pkgconfig && \ + meson setup --buildtype=release --prefix=/opt/naemon /broker/build /broker/source && \ + ninja -C /broker/build install USER naemon diff --git a/src/Statusengine.cpp b/src/Statusengine.cpp index 3606b3a..8a40dbf 100644 --- a/src/Statusengine.cpp +++ b/src/Statusengine.cpp @@ -9,6 +9,7 @@ #include "NagiosObject.h" #include "Nebmodule.h" #include "Utility.h" +#include "version.h" namespace statusengine { @@ -22,7 +23,7 @@ namespace statusengine { SetModuleInfo(NEBMODULE_MODINFO_TITLE, "Statusengine - the missing event broker"); SetModuleInfo(NEBMODULE_MODINFO_AUTHOR, "Johannes Drummer"); SetModuleInfo(NEBMODULE_MODINFO_TITLE, "Copyright (c) 2018 - present Johannes Drummer"); - SetModuleInfo(NEBMODULE_MODINFO_VERSION, "4.0.0"); + SetModuleInfo(NEBMODULE_MODINFO_VERSION, STATUSENGINE_VERSION); SetModuleInfo(NEBMODULE_MODINFO_LICENSE, "GPL v2"); SetModuleInfo(NEBMODULE_MODINFO_DESC, "A powerful and flexible event broker"); diff --git a/src/meson.build b/src/meson.build index 738bdc2..8173a42 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,3 +1,7 @@ +version_conf = configuration_data() +version_conf.set_quoted('STATUSENGINE_VERSION', meson.project_version()) +version_h = configure_file(output: 'version.h', configuration: version_conf) + statusengine_sources = ['Encoding.cpp', 'Nebmodule.cpp', 'Statusengine.cpp', 'MessageHandler/MessageHandlerList.cpp'] statusengine_deps = [uchardet_dep, jsonc_dep, toml_dep] @@ -29,7 +33,7 @@ if get_option('rabbitmq') and librabbitmq_dep.found() endif statusengine_lib = shared_module('statusengine', - statusengine_sources, + statusengine_sources, version_h, dependencies: statusengine_deps, cpp_args: statusengine_cpp_args, install: true) From 9c60a68e46108083faea0c46a546e05d42cfb333 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 19:00:46 +0200 Subject: [PATCH 04/28] Modernise ownership, logging and configuration parsing LogStream's level check was not monotonic and got it wrong in both directions: at Level = Error warnings were still written, and at Level = Info - the most verbose setting - warnings were dropped entirely. LogLevel now has explicit ordinals and a single "message level >= configured level" comparison. The thirteen hand written operator<< overloads collapse into one template. The stream still starts at Info, because Configuration::Load() reports before SetLogLevel() can apply the configured level and that output is what explains a broker that fails to start. The vendored toml11 dates from 2018. Replaced with v4.4.0 as a single header, keeping it vendored rather than pulling in a wrap subproject so the build needs no network. Its API changed completely (toml::Table -> toml::value, cast<> and get_or gone, its own exception hierarchy that no longer derives from std::runtime_error), so Configuration::Load() was rewritten around explicit contains() checks and two helpers instead of control flow through out_of_range. The two identical private GetTomlDefault templates became one free function. Ownership moves to unique_ptr in Statusengine, MessageQueueHandler::bulkMessages and GearmanClient::workerContexts, which makes Utility.h and its manual clearContainer helpers unnecessary. Statusengine's members are ordered so that ls outlives everything that logs while being torn down, and the destructor resets them explicitly rather than relying on declaration order. Queue.h listed all 23 queue identifiers three times, so adding a queue meant three edits that could disagree. Both lookup directions are now derived from one table. Smaller corrections: NEBMODULE_MODINFO_TITLE was set twice, so the copyright string overwrote the title; toml::syntax_error was caught after std::runtime_error, which it derived from, making the handler dead code; a doubled null check and a stray empty statement are gone. The long_output duplication on the five events whose nebstruct has no such member is deliberately left as it is, to avoid breaking the message format for existing consumers. It is now documented in README.md and marked at each site so it does not get "fixed" as a typo later. .clang-format is added - it was referenced in .gitignore but never committed. The existing sources are not reformatted here, that would be about 1300 lines and would bury everything else; the CI job is advisory for now. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- .clang-format | 15 + .github/workflows/build.yml | 17 + .gitignore | 3 +- README.md | 18 + src/Configuration.h | 277 +- src/LogStream.h | 134 +- src/MessageHandler/GearmanClient.cpp | 10 +- src/MessageHandler/GearmanClient.h | 2 +- src/MessageHandler/MessageHandler.h | 13 +- src/NagiosObject.h | 15 + src/NebmoduleCallback.h | 1 - src/Queue.h | 92 +- src/Statusengine.cpp | 33 +- src/Statusengine.h | 10 +- src/Utility.h | 19 - vendor/toml.hpp | 18185 ++++++++++++++++++++++++- vendor/toml/acceptor.hpp | 777 -- vendor/toml/datetime.hpp | 231 - vendor/toml/exception.hpp | 52 - vendor/toml/format.hpp | 386 - vendor/toml/from_toml.hpp | 121 - vendor/toml/get.hpp | 70 - vendor/toml/parser.hpp | 1130 -- vendor/toml/to_toml.hpp | 52 - vendor/toml/traits.hpp | 72 - vendor/toml/utility.hpp | 43 - vendor/toml/value.hpp | 719 - vendor/toml11-LICENSE | 21 + 28 files changed, 18509 insertions(+), 4009 deletions(-) create mode 100644 .clang-format delete mode 100644 src/Utility.h delete mode 100644 vendor/toml/acceptor.hpp delete mode 100644 vendor/toml/datetime.hpp delete mode 100644 vendor/toml/exception.hpp delete mode 100644 vendor/toml/format.hpp delete mode 100644 vendor/toml/from_toml.hpp delete mode 100644 vendor/toml/get.hpp delete mode 100644 vendor/toml/parser.hpp delete mode 100644 vendor/toml/to_toml.hpp delete mode 100644 vendor/toml/traits.hpp delete mode 100644 vendor/toml/utility.hpp delete mode 100644 vendor/toml/value.hpp create mode 100644 vendor/toml11-LICENSE diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..285f5c1 --- /dev/null +++ b/.clang-format @@ -0,0 +1,15 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 120 +AccessModifierOffset: -2 +NamespaceIndentation: All +BreakBeforeBraces: Stroustrup +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakTemplateDeclarations: Yes +PointerAlignment: Right +SortIncludes: false +SpaceAfterTemplateKeyword: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8bc0cc8..54f1342 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,3 +91,20 @@ jobs: -Dnagios_include_dir=/opt/nagios/include build ninja -C build + format: + name: clang-format (advisory) + runs-on: ubuntu-latest + # Not blocking: the existing sources predate .clang-format and would need a one off + # reformat first. This job shows what would change so new code can converge on it. + continue-on-error: true + steps: + - uses: actions/checkout@v4 + + - name: Install clang-format + run: sudo apt-get update && sudo apt-get install -y clang-format + + - name: Show formatting differences + run: | + find src tests -name '*.cpp' -o -name '*.h' \ + | grep -v '^src/gsl.h$' \ + | while read -r f; do clang-format "$f" | diff -u "$f" - || true; done diff --git a/.gitignore b/.gitignore index 395f52b..12b00c6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ /.vs /libgearman-1.0 /CppProperties.json -/.clang-format /devtools/headers.zip /devtools/include /.idea @@ -12,4 +11,4 @@ /vendor/json-c /vendor/iconv /vendor/yaml_cpp -/build \ No newline at end of file +/build diff --git a/README.md b/README.md index 9056062..02f97b6 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,24 @@ broker module like this: broker_module=/opt/naemon/lib/libstatusengine.so /path/to/statusengine.toml ``` +## Message format + +### `long_output` on events that have no long output + +For the following event types the `long_output` field is a verbatim copy of `output` and +carries no additional information: + +* `ContactNotificationData` +* `EventHandlerData` +* `NotificationData` +* `StateChange` +* `SystemCommandData` + +The reason is that the underlying naemon/nagios `nebstruct_*` structs simply have no +`long_output` member for these events; only host and service checks have one. The +duplication is kept for backwards compatibility, so that existing consumers do not break. +Do not read a separate long plugin output out of these five event types. + ## Developer build + test If you want to build and test the broker, you can use the docker-compose configuration: diff --git a/src/Configuration.h b/src/Configuration.h index 673601d..654bd43 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -14,11 +14,26 @@ namespace statusengine { + /** + * Reads an optional key from a table, falling back to opt. A value of the wrong type is + * reported and also falls back, so a single bad key does not take the whole config down. + */ + template + T GetTomlDefault(IStatusengine &se, const toml::value &tab, const char *ky, T opt) { + try { + return toml::find_or(tab, ky, opt); + } + catch (const toml::type_error &tte) { + se.Log() << "Invalid configuration: Invalid value for key " << ky << LogLevel::Error; + } + return opt; + } + class MessageHandlerConfiguration { public: - bool InitLoad(const toml::Table &tbl) { - for (auto &tableEntry : tbl) { + bool InitLoad(const toml::value &tbl) { + for (auto &tableEntry : tbl.as_table()) { auto QueueName = QueueNameHandler::Instance().QueueNames(); auto qName = QueueName.find(tableEntry.first); if (qName != QueueName.end()) { @@ -73,7 +88,7 @@ namespace statusengine { return queueIds; } - virtual bool Load(const toml::Table &tbl) = 0; + virtual bool Load(const toml::value &tbl) = 0; virtual ~MessageHandlerConfiguration() = default; @@ -89,15 +104,6 @@ namespace statusengine { std::shared_ptr> queues; std::shared_ptr> workerQueues; - template T GetTomlDefault(const toml::Table &tab, const char *ky, T &&opt) const { - try { - return toml::get_or(tab, ky, opt); - } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Invalid value for key " << ky << LogLevel::Error; - } - return std::move(opt); - } }; @@ -109,28 +115,28 @@ namespace statusengine { virtual ~RabbitmqConfiguration() = default; - bool Load(const toml::Table &tbl) override { - Hostname = GetTomlDefault<>(tbl, "Hostname", std::string("")); + bool Load(const toml::value &tbl) override { + Hostname = GetTomlDefault(se, tbl, "Hostname", std::string("")); if (Hostname.empty()) { se.Log() << "Please specify a hostname in rabbitmq configuration" << LogLevel::Error; return false; } - Port = GetTomlDefault<>(tbl, "Port", 5672); + Port = GetTomlDefault(se, tbl, "Port", 5672); - Vhost = GetTomlDefault<>(tbl, "Vhost", std::string("/")); - Username = GetTomlDefault<>(tbl, "Username", std::string("statusengine")); - Password = GetTomlDefault<>(tbl, "Password", std::string("")); + Vhost = GetTomlDefault(se, tbl, "Vhost", std::string("/")); + Username = GetTomlDefault(se, tbl, "Username", std::string("statusengine")); + Password = GetTomlDefault(se, tbl, "Password", std::string("")); if (Password.empty()) { se.Log() << "Warning, no password specified" << LogLevel::Warning; } - int tov = GetTomlDefault<>(tbl, "Timeout", 30); + int tov = GetTomlDefault(se, tbl, "Timeout", 30); Timeout.tv_sec = tov; Timeout.tv_usec = 0; - Exchange = GetTomlDefault<>(tbl, "Exchange", std::string("statusengine")); + Exchange = GetTomlDefault(se, tbl, "Exchange", std::string("statusengine")); // Durable by default. A queue that is neither durable nor exclusive is // RabbitMQ's deprecated transient_nonexcl_queues feature: 3.13 warns once per @@ -147,15 +153,15 @@ namespace statusengine { // The exchange follows the queues: a transient exchange loses its bindings on a // broker restart while the durable queues survive, and keeping the pair // consistent costs nothing, both being metadata only. - DurableExchange = GetTomlDefault<>(tbl, "DurableExchange", true); - DurableQueues = GetTomlDefault<>(tbl, "DurableQueues", true); + DurableExchange = GetTomlDefault(se, tbl, "DurableExchange", true); + DurableQueues = GetTomlDefault(se, tbl, "DurableQueues", true); - SSL = GetTomlDefault<>(tbl, "SSL", false); + SSL = GetTomlDefault(se, tbl, "SSL", false); - SSLVerify = GetTomlDefault<>(tbl, "SSL_verify", true); - SSLCacert = GetTomlDefault<>(tbl, "SSL_cacert", std::string("")); - SSLCert = GetTomlDefault<>(tbl, "SSL_cert", std::string("")); - SSLKey = GetTomlDefault<>(tbl, "SSL_key", std::string("")); + SSLVerify = GetTomlDefault(se, tbl, "SSL_verify", true); + SSLCacert = GetTomlDefault(se, tbl, "SSL_cacert", std::string("")); + SSLCert = GetTomlDefault(se, tbl, "SSL_cert", std::string("")); + SSLKey = GetTomlDefault(se, tbl, "SSL_key", std::string("")); return true; } @@ -181,12 +187,12 @@ namespace statusengine { public: explicit GearmanConfiguration(IStatusengine &se) : MessageHandlerConfiguration(se) {} - bool Load(const toml::Table &tbl) override { + bool Load(const toml::value &tbl) override { try { - URL = toml::get(tbl.at("URL")); + URL = toml::find(tbl, "URL"); } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Invalid value for key URL" << LogLevel::Error; + catch (const std::exception &e) { + se.Log() << "Invalid configuration: Invalid or missing value for key URL" << LogLevel::Error; return false; } @@ -198,7 +204,9 @@ namespace statusengine { class Configuration { public: - explicit Configuration(IStatusengine &se) : se(se), maxWorkerMessagesPerInterval(0), logLevel(LogLevel::Warning) {} + explicit Configuration(IStatusengine &se) + : se(se), cfg(toml::table{}), bulkTable(toml::table{}), schedulerTable(toml::table{}), + maxWorkerMessagesPerInterval(0), logLevel(LogLevel::Warning) {} ~Configuration() { rabbitmq.clear(); // shared_ptr } @@ -207,125 +215,80 @@ namespace statusengine { try { cfg = toml::parse(configurationPath); } - catch (std::runtime_error &rte) { - se.Log() << "Could not read file: " << rte.what() << LogLevel::Error; - return false; - } - catch (toml::syntax_error &ste) { + catch (const toml::syntax_error &ste) { se.Log() << "configuration syntax error: " << ste.what() << LogLevel::Error; return false; } - - try { - bulkTable = cfg.at("Bulk").cast(); - } - catch (std::out_of_range &oor) { - } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Bulk isn't a table!" << LogLevel::Error; + catch (const std::exception &e) { + se.Log() << "Could not read file: " << e.what() << LogLevel::Error; return false; } - try { - auto logTable = cfg.at("Log").cast(); - auto logLevelStr = toml::get_or(logTable, "Level", "Warning"); - if (logLevelStr == "Info") { - logLevel = LogLevel::Info; - } - else if(logLevelStr == "Warning") { - logLevel = LogLevel::Warning; - } - else if(logLevelStr == "Error") { - logLevel = LogLevel::Error; - } - else { - se.Log() << "Invalid configuration: Unknown log level: " << logLevelStr << LogLevel::Error; - return false; - } - } - catch (std::out_of_range &oor) { - } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Log isn't a table!" << LogLevel::Error; + if (!ReadSection("Bulk", bulkTable) || !ReadSection("Scheduler", schedulerTable)) { return false; } - try { - auto QueueName = QueueNameHandler::Instance().QueueNames(); - std::vector bulkQueueList = toml::get>(bulkTable.at("Queues")); - for (auto &bulkQueueItem : bulkQueueList) { - try { - bulkQueues.insert(QueueName.at(bulkQueueItem)); - } - catch (std::out_of_range &oor) { - se.Log() << "Invalid configuration: Bulk::Queues contains an unknown queue identifier: " << bulkQueueItem << LogLevel::Error; - return false; - } - } - } - catch (std::out_of_range &oor) { - } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Bulk::Queues isn't an array!" << LogLevel::Error; + toml::value logTable; + if (!ReadSection("Log", logTable)) { return false; } - - try { - schedulerTable = cfg.at("Scheduler").cast(); + auto logLevelStr = GetTomlDefault(se, logTable, "Level", std::string("Warning")); + if (logLevelStr == "Info") { + logLevel = LogLevel::Info; } - catch (std::out_of_range &oor) { - } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Scheduler isn't a table!" << LogLevel::Error; - return false; - } - - try { - std::vector gearmans = toml::get>(cfg.at("Gearman")); - for (auto &gearmanConfig : gearmans) { - auto gfg = std::make_shared(se); - if (!gfg->InitLoad(gearmanConfig)) { - return false; - } - gearman.push_back(gfg); - } + else if (logLevelStr == "Warning") { + logLevel = LogLevel::Warning; } - catch (const std::out_of_range &oor) { + else if (logLevelStr == "Error") { + logLevel = LogLevel::Error; } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Gearman isn't an Array of Tables!" << LogLevel::Error; + else { + se.Log() << "Invalid configuration: Unknown log level: " << logLevelStr << LogLevel::Error; return false; } - try { - std::vector rabbits = toml::get>(cfg.at("Rabbitmq")); - for (auto &rabbitConfig : rabbits) { - auto rfg = std::make_shared(se); - if (!rfg->InitLoad(rabbitConfig)) { + if (bulkTable.contains("Queues")) { + std::vector bulkQueueList; + try { + bulkQueueList = toml::find>(bulkTable, "Queues"); + } + catch (const toml::type_error &tte) { + se.Log() << "Invalid configuration: Bulk::Queues isn't an array!" << LogLevel::Error; + return false; + } + const auto &QueueName = QueueNameHandler::Instance().QueueNames(); + for (auto &bulkQueueItem : bulkQueueList) { + auto queue = QueueName.find(bulkQueueItem); + if (queue == QueueName.end()) { + se.Log() << "Invalid configuration: Bulk::Queues contains an unknown queue identifier: " + << bulkQueueItem << LogLevel::Error; return false; } - rabbitmq.push_back(rfg); + bulkQueues.insert(queue->second); } } - catch (const std::out_of_range &oor) { - } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Rabbitmq isn't an Array of Tables!" << LogLevel::Error; + + if (!ReadHandlerConfigs("Gearman", gearman) || + !ReadHandlerConfigs("Rabbitmq", rabbitmq)) { return false; } - try { - maxWorkerMessagesPerInterval = toml::get_or(toml::get(cfg.at("Worker")), - "MaxWorkerMessagesPerInterval", 1000000ul); - } - catch (const std::out_of_range &oor) { - maxWorkerMessagesPerInterval = 1000000ul; - } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Invalid value for key " - << "MaxWorkerMessagesPerInterval" << LogLevel::Error; + toml::value workerTable; + if (!ReadSection("Worker", workerTable)) { return false; } + maxWorkerMessagesPerInterval = 1000000ul; + if (workerTable.contains("MaxWorkerMessagesPerInterval")) { + try { + maxWorkerMessagesPerInterval = + toml::find(workerTable, "MaxWorkerMessagesPerInterval"); + } + catch (const toml::type_error &tte) { + se.Log() << "Invalid configuration: Invalid value for key " + << "MaxWorkerMessagesPerInterval" << LogLevel::Error; + return false; + } + } se.Log() << "Finished loading config" << LogLevel::Info; se.Log() << "Gearman Clients: " << gearman.size() << LogLevel::Info; @@ -352,11 +315,11 @@ namespace statusengine { } time_t GetBulkFlushInterval() const { - return GetTomlDefault<>(bulkTable, "FlushInterval", static_cast(10)); + return GetTomlDefault(se, bulkTable, "FlushInterval", static_cast(10)); } unsigned long GetBulkMaximum() const { - return GetTomlDefault<>(bulkTable, "Maximum", 200ul); + return GetTomlDefault(se, bulkTable, "Maximum", 200ul); } bool IsBulkQueue(Queue queue) const { @@ -364,7 +327,7 @@ namespace statusengine { } time_t GetStartupScheduleMax() const { - return GetTomlDefault<>(schedulerTable, "StartupScheduleMax", 0); + return GetTomlDefault(se, schedulerTable, "StartupScheduleMax", 0); } std::vector> *GetGearmanConfiguration() { @@ -384,10 +347,53 @@ namespace statusengine { } private: + /// Copies an optional top level table into target. An absent section leaves target + /// as an empty table, so the lookups on it still work. + bool ReadSection(const char *name, toml::value &target) { + target = toml::value(toml::table{}); + if (!cfg.contains(name)) { + return true; + } + const auto §ion = cfg.at(name); + if (!section.is_table()) { + se.Log() << "Invalid configuration: " << name << " isn't a table!" << LogLevel::Error; + return false; + } + target = section; + return true; + } + + /// Loads an optional array of tables, one connection configuration per entry. + template + bool ReadHandlerConfigs(const char *name, std::vector> &out) { + if (!cfg.contains(name)) { + return true; + } + const auto §ion = cfg.at(name); + if (!section.is_array()) { + se.Log() << "Invalid configuration: " << name << " isn't an Array of Tables!" << LogLevel::Error; + return false; + } + for (const auto &entry : section.as_array()) { + if (!entry.is_table()) { + se.Log() << "Invalid configuration: " << name << " isn't an Array of Tables!" << LogLevel::Error; + return false; + } + auto handlerConfig = std::make_shared(se); + if (!handlerConfig->InitLoad(entry)) { + return false; + } + out.push_back(handlerConfig); + } + return true; + } + IStatusengine &se; - toml::Table cfg; - toml::Table bulkTable; - toml::Table schedulerTable; + toml::value cfg; + // Kept as (possibly empty) tables so the GetTomlDefault lookups below always have + // something well formed to search, even when the section is absent. + toml::value bulkTable; + toml::value schedulerTable; std::vector> rabbitmq; std::vector> gearman; @@ -397,14 +403,5 @@ namespace statusengine { LogLevel logLevel; - template T GetTomlDefault(const toml::Table &tab, const char *ky, T &&opt) const { - try { - return toml::get_or(tab, ky, opt); - } - catch (const toml::type_error &tte) { - se.Log() << "Invalid configuration: Invalid value for key " << ky << LogLevel::Error; - } - return opt; - } }; } // namespace statusengine diff --git a/src/LogStream.h b/src/LogStream.h index e07bbcd..a316995 100644 --- a/src/LogStream.h +++ b/src/LogStream.h @@ -1,105 +1,49 @@ #pragma once #include +#include #include "Nebmodule.h" namespace statusengine { - enum class LogLevel { Info, Warning, Error }; - + /** + * Severity of a log message, ordered from least to most severe. The numbering is what + * makes the threshold comparison in LogStream work, so keep it ascending. + */ + enum class LogLevel { Info = 0, Warning = 1, Error = 2 }; + + /** + * Collects a message and writes it to the naemon log once a LogLevel is streamed in: + * + * Log() << "something happened: " << value << LogLevel::Warning; + * + * The LogLevel both terminates the message and gives its severity. Messages below the + * configured level are dropped. + */ class LogStream { public: - LogStream() : ss(), level(LogLevel::Info) { - *this << "Logstream initalized" << LogLevel::Info; - } + // Starts at Info so that everything Configuration::Load() reports is visible: it + // runs before SetLogLevel() can apply the configured level, and that startup + // output is what tells you why a broker failed to come up. + LogStream() : ss(), level(LogLevel::Info) {} ~LogStream() = default; - LogStream &operator<<(const char *o) { - ss << o; - return *this; - } - - LogStream &operator<<(const bool o) { - ss << o; - return *this; - } - - LogStream &operator<<(const short o) { - ss << o; - return *this; - } - - LogStream &operator<<(const unsigned short o) { - ss << o; - return *this; - } - - LogStream &operator<<(const int o) { - ss << o; - return *this; - } - - LogStream &operator<<(const unsigned int o) { - ss << o; - return *this; - } - - LogStream &operator<<(const long o) { - ss << o; - return *this; - } - - LogStream &operator<<(const unsigned long o) { - ss << o; - return *this; - } - - LogStream &operator<<(const long long o) { - ss << o; - return *this; - } - - LogStream &operator<<(const unsigned long long o) { - ss << o; - return *this; - } - - LogStream &operator<<(const float o) { - ss << o; - return *this; - } - - LogStream &operator<<(const double o) { - ss << o; - return *this; - } - - LogStream &operator<<(const long double o) { - ss << o; - return *this; - } + LogStream(const LogStream &LogStream) = delete; + LogStream(LogStream &&LogStream) = delete; + LogStream &operator=(const LogStream &) = delete; - LogStream &operator<<(const std::string o) { + /// Appends anything std::stringstream can format. + template + LogStream &operator<<(const T &o) { ss << o; return *this; } - LogStream &operator<<(const LogLevel o) { - int logLevel = -1; - - if (o == LogLevel::Error) { - logLevel = NSLOG_RUNTIME_ERROR; - } - else if(o == LogLevel::Info && level == LogLevel::Info) { - logLevel = NSLOG_INFO_MESSAGE; - } - else if(o == LogLevel::Warning && (level == LogLevel::Warning || level == LogLevel::Error)) { - logLevel = NSLOG_RUNTIME_WARNING; - } - - if (logLevel != -1) { - nm_log(logLevel, "%s", ("Statusengine: " + ss.str()).c_str()); + /// Terminates the message and writes it out if it reaches the configured level. + LogStream &operator<<(LogLevel messageLevel) { + if (messageLevel >= level) { + nm_log(NaemonLogLevel(messageLevel), "%s", ("Statusengine: " + ss.str()).c_str()); } ss.str(""); @@ -107,15 +51,27 @@ namespace statusengine { return *this; } - LogStream(const LogStream &LogStream) = delete; - LogStream(LogStream &&LogStream) = delete; - LogStream &operator=(const LogStream &) = delete; - void SetLogLevel(LogLevel ll) { level = ll; } + LogLevel GetLogLevel() const { + return level; + } + private: + static int NaemonLogLevel(LogLevel messageLevel) { + switch (messageLevel) { + case LogLevel::Error: + return NSLOG_RUNTIME_ERROR; + case LogLevel::Warning: + return NSLOG_RUNTIME_WARNING; + case LogLevel::Info: + break; + } + return NSLOG_INFO_MESSAGE; + } + std::stringstream ss; LogLevel level; }; diff --git a/src/MessageHandler/GearmanClient.cpp b/src/MessageHandler/GearmanClient.cpp index 56296a3..eb96867 100644 --- a/src/MessageHandler/GearmanClient.cpp +++ b/src/MessageHandler/GearmanClient.cpp @@ -1,7 +1,6 @@ #include "GearmanClient.h" #include "Configuration.h" -#include "Utility.h" #include @@ -44,7 +43,7 @@ namespace statusengine { if (worker != nullptr) { se->Log() << "Destroy gearman worker" << LogLevel::Info; gearman_worker_free(worker); - clearContainer<>(&workerContexts); + workerContexts.clear(); } } @@ -70,8 +69,11 @@ namespace statusengine { return false; } for (auto &queue : *workerQueueNames) { - auto ctx = new GearmanWorkerContext(queue.first, this); - workerContexts[queue.first] = ctx; + // The raw pointer is handed to libgearman as the callback context; the + // context object itself stays owned by workerContexts. + std::unique_ptr ownedCtx(new GearmanWorkerContext(queue.first, this)); + auto ctx = ownedCtx.get(); + workerContexts[queue.first] = std::move(ownedCtx); auto cbfn = gearman_function_create_v2(se_gearman_worker_callback); ret = gearman_worker_define_function(worker, queue.second.c_str(), queue.second.size(), cbfn, 0, ctx); if (gearman_success(ret)) { diff --git a/src/MessageHandler/GearmanClient.h b/src/MessageHandler/GearmanClient.h index 83344f0..513b5e4 100644 --- a/src/MessageHandler/GearmanClient.h +++ b/src/MessageHandler/GearmanClient.h @@ -28,6 +28,6 @@ namespace statusengine { std::shared_ptr> queueNames; std::shared_ptr> workerQueueNames; - std::map workerContexts; + std::map> workerContexts; }; } // namespace statusengine diff --git a/src/MessageHandler/MessageHandler.h b/src/MessageHandler/MessageHandler.h index 2d43abb..5c7afe7 100644 --- a/src/MessageHandler/MessageHandler.h +++ b/src/MessageHandler/MessageHandler.h @@ -10,7 +10,6 @@ #include "Configuration.h" #include "IStatusengine.h" #include "IMessageHandler.h" -#include "Utility.h" #include "gsl.h" @@ -373,10 +372,8 @@ namespace statusengine { } if (hostname == nullptr) { - if (hostname == nullptr) { - se->Log() << "Received delete_downtime command without hostname " << LogLevel::Warning; - return; - } + se->Log() << "Received delete_downtime command without hostname " << LogLevel::Warning; + return; } Nebmodule::Instance().DeleteDowntime(hostname, service_description, start_time, end_time, comment); @@ -405,7 +402,7 @@ namespace statusengine { void SendMessage(NagiosObject &obj) override { if (bulk) { if(!obj.isEmpty()){ - bulkMessages.push_back(new NagiosObject(&obj)); + bulkMessages.push_back(std::unique_ptr(new NagiosObject(&obj))); if (++(*globalBulkCounter) >= maxBulkSize) { mhlist.FlushBulkQueue(); } @@ -442,7 +439,7 @@ namespace statusengine { se.Log() << "Sent bulk message (" << bulkMessages.size() << ") for queue " << QueueId.at(queue) << LogLevel::Info; - clearContainer<>(&bulkMessages); + bulkMessages.clear(); } } @@ -452,7 +449,7 @@ namespace statusengine { Queue queue; std::shared_ptr>> handlers; - std::vector bulkMessages; + std::vector> bulkMessages; unsigned long maxBulkSize; unsigned long *globalBulkCounter; diff --git a/src/NagiosObject.h b/src/NagiosObject.h index 2ad7963..39a4356 100644 --- a/src/NagiosObject.h +++ b/src/NagiosObject.h @@ -206,6 +206,9 @@ namespace statusengine { contactnotificationdata.SetData("host_name", contactNotificationData->host_name); contactnotificationdata.SetData("service_description", contactNotificationData->service_description); contactnotificationdata.SetData("output", nebmodule.EncodeString(contactNotificationData->output)); + // Intentional: this nebstruct has no long_output member, only host and service + // checks do. long_output repeats output so the message format stays backwards + // compatible - see the "Message format" section in README.md. Not a typo. contactnotificationdata.SetData("long_output", nebmodule.EncodeString(contactNotificationData->output)); contactnotificationdata.SetData("ack_author", contactNotificationData->ack_author); contactnotificationdata.SetData("ack_data", contactNotificationData->ack_data); @@ -314,6 +317,9 @@ namespace statusengine { eventhandler.SetData("host_name", eventHandlerData->host_name); eventhandler.SetData("service_description", eventHandlerData->service_description); eventhandler.SetData("output", nebmodule.EncodeString(eventHandlerData->output)); + // Intentional: this nebstruct has no long_output member, only host and service + // checks do. long_output repeats output so the message format stays backwards + // compatible - see the "Message format" section in README.md. Not a typo. eventhandler.SetData("long_output", nebmodule.EncodeString(eventHandlerData->output)); eventhandler.SetData("command_name", eventHandlerData->command_name); eventhandler.SetData("command_args", eventHandlerData->command_args); @@ -531,6 +537,9 @@ namespace statusengine { notification_data.SetData("host_name", notificationData->host_name); notification_data.SetData("service_description", notificationData->service_description); notification_data.SetData("output", nebmodule.EncodeString(notificationData->output)); + // Intentional: this nebstruct has no long_output member, only host and service + // checks do. long_output repeats output so the message format stays backwards + // compatible - see the "Message format" section in README.md. Not a typo. notification_data.SetData("long_output", nebmodule.EncodeString(notificationData->output)); notification_data.SetData("ack_author", notificationData->ack_author); notification_data.SetData("ack_data", notificationData->ack_data); @@ -742,6 +751,9 @@ namespace statusengine { statechange.SetData("host_name", stateChangeData->host_name); statechange.SetData("service_description", stateChangeData->service_description); statechange.SetData("output", nebmodule.EncodeString(stateChangeData->output)); + // Intentional: this nebstruct has no long_output member, only host and service + // checks do. long_output repeats output so the message format stays backwards + // compatible - see the "Message format" section in README.md. Not a typo. statechange.SetData("long_output", nebmodule.EncodeString(stateChangeData->output)); statechange.SetData("statechange_type", stateChangeData->statechange_type); statechange.SetData("state", stateChangeData->state); @@ -768,6 +780,9 @@ namespace statusengine { systemcommand.SetData("command_line", systemCommandData->command_line); systemcommand.SetData("output", nebmodule.EncodeString(systemCommandData->output)); + // Intentional: this nebstruct has no long_output member, only host and service + // checks do. long_output repeats output so the message format stays backwards + // compatible - see the "Message format" section in README.md. Not a typo. systemcommand.SetData("long_output", nebmodule.EncodeString(systemCommandData->output)); systemcommand.SetData("start_time", systemCommandData->start_time.tv_sec); systemcommand.SetData("end_time", systemCommandData->end_time.tv_sec); diff --git a/src/NebmoduleCallback.h b/src/NebmoduleCallback.h index 27ff846..cc36f2b 100644 --- a/src/NebmoduleCallback.h +++ b/src/NebmoduleCallback.h @@ -83,7 +83,6 @@ namespace statusengine { if (data->type == NEBTYPE_SERVICECHECK_PROCESSED) { if (servicechecks || ocsp) { NagiosServiceCheckData checkData(data); - ; if (servicechecks) { serviceCheckHandler->SendMessage(checkData); } diff --git a/src/Queue.h b/src/Queue.h index 6431335..71334e7 100644 --- a/src/Queue.h +++ b/src/Queue.h @@ -1,7 +1,7 @@ #pragma once #include - +#include namespace statusengine { enum class Queue { @@ -32,9 +32,16 @@ namespace statusengine { enum class WorkerQueue { OCSP, OCHP, Command }; + /** + * Maps between the queue enums and the identifiers used in statusengine.toml. + * + * The identifier of a queue is written down exactly once, in the tables below. Both + * lookup directions are derived from them, so adding a queue means adding an enum value + * and one table row. + */ class QueueNameHandler { - public: - static QueueNameHandler& Instance() { + public: + static QueueNameHandler &Instance() { static QueueNameHandler instance; return instance; } @@ -47,41 +54,23 @@ namespace statusengine { return queueIds; } - const std::map &WorkerQueueNames() { + const std::map &WorkerQueueNames() const { return workerQueueNames; } - const std::map &WorkerQueueIds() { + const std::map &WorkerQueueIds() const { return workerQueueIds; } - private: - explicit QueueNameHandler() : queueNames(), queueIds() { - queueNames = { - {"HostStatus", Queue::HostStatus}, - {"HostCheck", Queue::HostCheck}, - {"ServiceStatus", Queue::ServiceStatus}, - {"ServiceCheck", Queue::ServiceCheck}, - {"ServicePerfData", Queue::ServicePerfData}, - {"StateChange", Queue::StateChange}, - {"LogData", Queue::LogData}, - {"AcknowledgementData", Queue::AcknowledgementData}, - {"FlappingData", Queue::FlappingData}, - {"DowntimeData", Queue::DowntimeData}, - {"ContactNotificationMethodData", Queue::ContactNotificationMethodData}, - {"RestartData", Queue::RestartData}, - {"SystemCommandData", Queue::SystemCommandData}, - {"CommentData", Queue::CommentData}, - {"ExternalCommandData", Queue::ExternalCommandData}, - {"NotificationData", Queue::NotificationData}, - {"ProgramStatusData", Queue::ProgramStatusData}, - {"ContactStatusData", Queue::ContactStatusData}, - {"ContactNotificationData", Queue::ContactNotificationData}, - {"EventHandlerData", Queue::EventHandlerData}, - {"ProcessData", Queue::ProcessData}, - {"OCSP", Queue::OCSP}, - {"OCHP", Queue::OCHP}}; - - queueIds = { + + private: + template + struct Entry { + T id; + const char *name; + }; + + static const Entry *QueueTable(size_t &count) { + static const Entry table[] = { {Queue::HostStatus, "HostStatus"}, {Queue::HostCheck, "HostCheck"}, {Queue::ServiceStatus, "ServiceStatus"}, @@ -105,16 +94,39 @@ namespace statusengine { {Queue::ProcessData, "ProcessData"}, {Queue::OCSP, "OCSP"}, {Queue::OCHP, "OCHP"}}; + count = sizeof(table) / sizeof(table[0]); + return table; + } - workerQueueNames = { - {"WorkerOCSP", WorkerQueue::OCSP}, - {"WorkerOCHP", WorkerQueue::OCHP}, - {"WorkerCommand", WorkerQueue::Command}}; - - workerQueueIds = { + static const Entry *WorkerQueueTable(size_t &count) { + static const Entry table[] = { {WorkerQueue::OCSP, "WorkerOCSP"}, {WorkerQueue::OCHP, "WorkerOCHP"}, {WorkerQueue::Command, "WorkerCommand"}}; + count = sizeof(table) / sizeof(table[0]); + return table; + } + + template + static void Fill(const Entry *table, size_t count, std::map &byName, + std::map &byId) { + for (size_t i = 0; i < count; ++i) { + byName[table[i].name] = table[i].id; + byId[table[i].id] = table[i].name; + } + } + + QueueNameHandler() : queueNames(), queueIds(), workerQueueNames(), workerQueueIds() { + // The table has to be fetched in its own statement: the order in which function + // arguments are evaluated is unspecified, so passing QueueTable(count) and count + // to the same call may read count before QueueTable() has set it. + size_t queueCount = 0; + const Entry *queues = QueueTable(queueCount); + Fill(queues, queueCount, queueNames, queueIds); + + size_t workerCount = 0; + const Entry *workers = WorkerQueueTable(workerCount); + Fill(workers, workerCount, workerQueueNames, workerQueueIds); } std::map queueNames; @@ -123,4 +135,4 @@ namespace statusengine { std::map workerQueueIds; }; -} // namespace statusengine \ No newline at end of file +} // namespace statusengine diff --git a/src/Statusengine.cpp b/src/Statusengine.cpp index 8a40dbf..de9ad12 100644 --- a/src/Statusengine.cpp +++ b/src/Statusengine.cpp @@ -8,21 +8,19 @@ #include "NebmoduleCallback.h" #include "NagiosObject.h" #include "Nebmodule.h" -#include "Utility.h" #include "version.h" namespace statusengine { Statusengine::Statusengine(nebmodule *handle, std::string configurationPath) - : nebhandle(handle), configurationPath(std::move(configurationPath)), messageHandler(nullptr), ls(), - callbacks(), bulkCallback(nullptr), messageWorkerCallback(nullptr) { - configuration = new Configuration(*this); - } + : nebhandle(handle), configurationPath(std::move(configurationPath)), ls(), + configuration(new Configuration(*this)), messageHandler(), callbacks(), bulkCallback(), + messageWorkerCallback() {} int Statusengine::Init() { SetModuleInfo(NEBMODULE_MODINFO_TITLE, "Statusengine - the missing event broker"); SetModuleInfo(NEBMODULE_MODINFO_AUTHOR, "Johannes Drummer"); - SetModuleInfo(NEBMODULE_MODINFO_TITLE, "Copyright (c) 2018 - present Johannes Drummer"); + SetModuleInfo(NEBMODULE_MODINFO_COPYRIGHT, "Copyright (c) 2018 - present Johannes Drummer"); SetModuleInfo(NEBMODULE_MODINFO_VERSION, STATUSENGINE_VERSION); SetModuleInfo(NEBMODULE_MODINFO_LICENSE, "GPL v2"); SetModuleInfo(NEBMODULE_MODINFO_DESC, "A powerful and flexible event broker"); @@ -53,7 +51,7 @@ namespace statusengine { ls.SetLogLevel(configuration->GetLogLevel()); - messageHandler = new MessageHandlerList(*this, *configuration); + messageHandler.reset(new MessageHandlerList(*this, *configuration)); if (!messageHandler->Connect()) { return 1; } @@ -120,21 +118,24 @@ namespace statusengine { Statusengine::~Statusengine() { Log() << "unloading..." << LogLevel::Info; neb_deregister_module_callbacks(nebhandle); + // Explicit and ordered: messageHandler flushes and logs while it is torn down, so + // it has to go before configuration and while ls is still alive. callbacks.clear(); - delete bulkCallback; - delete messageWorkerCallback; - delete configuration; - delete messageHandler; + bulkCallback.reset(); + messageWorkerCallback.reset(); + messageHandler.reset(); + configuration.reset(); Log() << "unloading finished" << LogLevel::Info; } void Statusengine::InitEventCallbacks() { Log() << "Initialize event callbacks" << LogLevel::Info; - bulkCallback = new BulkMessageCallback(*this, configuration->GetBulkFlushInterval()); - messageWorkerCallback = new MessageWorkerCallback(*this, 1); - RegisterEventCallback(bulkCallback); - RegisterEventCallback(messageWorkerCallback); + bulkCallback.reset(new BulkMessageCallback(*this, configuration->GetBulkFlushInterval())); + messageWorkerCallback.reset(new MessageWorkerCallback(*this, 1)); + // naemon keeps these pointers across events, they stay owned by this object. + RegisterEventCallback(bulkCallback.get()); + RegisterEventCallback(messageWorkerCallback.get()); } LogStream &Statusengine::Log() { @@ -165,6 +166,6 @@ namespace statusengine { } IMessageHandlerList *Statusengine::GetMessageHandler() const { - return messageHandler; + return messageHandler.get(); } } // namespace statusengine diff --git a/src/Statusengine.h b/src/Statusengine.h index 337d127..a5b9edf 100644 --- a/src/Statusengine.h +++ b/src/Statusengine.h @@ -68,11 +68,13 @@ namespace statusengine { nebmodule *nebhandle; std::string configurationPath; - Configuration *configuration; - IMessageHandlerList *messageHandler; + // ls is declared before everything that logs while being torn down, so that it + // outlives them: members are destroyed in reverse declaration order. LogStream ls; + std::unique_ptr configuration; + std::unique_ptr messageHandler; std::multimap> callbacks; - BulkMessageCallback *bulkCallback; - MessageWorkerCallback *messageWorkerCallback; + std::unique_ptr bulkCallback; + std::unique_ptr messageWorkerCallback; }; } // namespace statusengine diff --git a/src/Utility.h b/src/Utility.h deleted file mode 100644 index defaa4f..0000000 --- a/src/Utility.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include - -template -void clearContainer(T *container) { - for (auto it = container->begin(); it != container->end();) { - delete *it; - it = container->erase(it); - } -} - -template -void clearContainer(std::map *container) { - for (auto it = container->begin(); it != container->end();) { - delete it->second; - it = container->erase(it); - } -} diff --git a/vendor/toml.hpp b/vendor/toml.hpp index cfae56b..ee7f9cf 100644 --- a/vendor/toml.hpp +++ b/vendor/toml.hpp @@ -1,41 +1,18162 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2017 Toru Niina - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef TOML_FOR_MODERN_CPP -#define TOML_FOR_MODERN_CPP +#ifndef TOML11_VERSION_HPP +#define TOML11_VERSION_HPP + +#define TOML11_VERSION_MAJOR 4 +#define TOML11_VERSION_MINOR 4 +#define TOML11_VERSION_PATCH 0 #ifndef __cplusplus # error "__cplusplus is not defined" #endif -#if __cplusplus < 201103L && _MSC_VER < 1900 +// Since MSVC does not define `__cplusplus` correctly unless you pass +// `/Zc:__cplusplus` when compiling, the workaround macros are added. +// +// The value of `__cplusplus` macro is defined in the C++ standard spec, but +// MSVC ignores the value, maybe because of backward compatibility. Instead, +// MSVC defines _MSVC_LANG that has the same value as __cplusplus defined in +// the C++ standard. So we check if _MSVC_LANG is defined before using `__cplusplus`. +// +// FYI: https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-170 +// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170 +// + +#if defined(_MSVC_LANG) && defined(_MSC_VER) && 190024210 <= _MSC_FULL_VER +# define TOML11_CPLUSPLUS_STANDARD_VERSION _MSVC_LANG +#else +# define TOML11_CPLUSPLUS_STANDARD_VERSION __cplusplus +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION < 201103L # error "toml11 requires C++11 or later." #endif -#include "toml/parser.hpp" -#include "toml/to_toml.hpp" -#include "toml/from_toml.hpp" -#include "toml/get.hpp" +#if ! defined(__has_include) +# define __has_include(x) 0 +#endif + +#if ! defined(__has_cpp_attribute) +# define __has_cpp_attribute(x) 0 +#endif + +#if ! defined(__has_builtin) +# define __has_builtin(x) 0 +#endif + +// hard to remember + +#ifndef TOML11_CXX14_VALUE +#define TOML11_CXX14_VALUE 201402L +#endif//TOML11_CXX14_VALUE + +#ifndef TOML11_CXX17_VALUE +#define TOML11_CXX17_VALUE 201703L +#endif//TOML11_CXX17_VALUE + +#ifndef TOML11_CXX20_VALUE +#define TOML11_CXX20_VALUE 202002L +#endif//TOML11_CXX20_VALUE + +#if defined(__cpp_char8_t) +# if __cpp_char8_t >= 201811L +# define TOML11_HAS_CHAR8_T 1 +# endif +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if __has_include() +# define TOML11_HAS_STRING_VIEW 1 +# endif +#endif + +#ifndef TOML11_DISABLE_STD_FILESYSTEM +# if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if __has_include() +# define TOML11_HAS_FILESYSTEM 1 +# endif +# endif +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if __has_include() +# define TOML11_HAS_OPTIONAL 1 +# endif +#endif + +#if defined(TOML11_COMPILE_SOURCES) +# define TOML11_INLINE +#else +# define TOML11_INLINE inline +#endif + +namespace toml +{ + +inline const char* license_notice() noexcept +{ + return R"(The MIT License (MIT) + +Copyright (c) 2017-now Toru Niina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.)"; +} + +} // toml +#endif // TOML11_VERSION_HPP +#ifndef TOML11_FORMAT_HPP +#define TOML11_FORMAT_HPP + +#ifndef TOML11_FORMAT_FWD_HPP +#define TOML11_FORMAT_FWD_HPP + +#include +#include +#include + +#include +#include + +namespace toml +{ + +// toml types with serialization info + +enum class indent_char : std::uint8_t +{ + space, // use space + tab, // use tab + none // no indent +}; + +std::ostream& operator<<(std::ostream& os, const indent_char& c); +std::string to_string(const indent_char c); + +// ---------------------------------------------------------------------------- +// boolean + +struct boolean_format_info +{ + // nothing, for now +}; + +inline bool operator==(const boolean_format_info&, const boolean_format_info&) noexcept +{ + return true; +} +inline bool operator!=(const boolean_format_info&, const boolean_format_info&) noexcept +{ + return false; +} + +// ---------------------------------------------------------------------------- +// integer + +enum class integer_format : std::uint8_t +{ + dec = 0, + bin = 1, + oct = 2, + hex = 3, +}; + +std::ostream& operator<<(std::ostream& os, const integer_format f); +std::string to_string(const integer_format); + +struct integer_format_info +{ + integer_format fmt = integer_format::dec; + bool uppercase = true; // hex with uppercase + std::size_t width = 0; // minimal width (may exceed) + std::size_t spacer = 0; // position of `_` (if 0, no spacer) + std::string suffix = ""; // _suffix (library extension) +}; + +bool operator==(const integer_format_info&, const integer_format_info&) noexcept; +bool operator!=(const integer_format_info&, const integer_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// floating + +enum class floating_format : std::uint8_t +{ + defaultfloat = 0, + fixed = 1, // does not include exponential part + scientific = 2, // always include exponential part + hex = 3 // hexfloat extension +}; + +std::ostream& operator<<(std::ostream& os, const floating_format f); +std::string to_string(const floating_format); + +struct floating_format_info +{ + floating_format fmt = floating_format::defaultfloat; + std::size_t prec = 0; // precision (if 0, use the default) + std::string suffix = ""; // 1.0e+2_suffix (library extension) +}; + +bool operator==(const floating_format_info&, const floating_format_info&) noexcept; +bool operator!=(const floating_format_info&, const floating_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// string + +enum class string_format : std::uint8_t +{ + basic = 0, + literal = 1, + multiline_basic = 2, + multiline_literal = 3 +}; + +std::ostream& operator<<(std::ostream& os, const string_format f); +std::string to_string(const string_format); + +struct string_format_info +{ + string_format fmt = string_format::basic; + bool start_with_newline = false; +}; + +bool operator==(const string_format_info&, const string_format_info&) noexcept; +bool operator!=(const string_format_info&, const string_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// datetime + +enum class datetime_delimiter_kind : std::uint8_t +{ + upper_T = 0, + lower_t = 1, + space = 2, +}; +std::ostream& operator<<(std::ostream& os, const datetime_delimiter_kind d); +std::string to_string(const datetime_delimiter_kind); + +struct offset_datetime_format_info +{ + datetime_delimiter_kind delimiter = datetime_delimiter_kind::upper_T; + bool has_seconds = true; + std::size_t subsecond_precision = 6; // [us] +}; + +bool operator==(const offset_datetime_format_info&, const offset_datetime_format_info&) noexcept; +bool operator!=(const offset_datetime_format_info&, const offset_datetime_format_info&) noexcept; + +struct local_datetime_format_info +{ + datetime_delimiter_kind delimiter = datetime_delimiter_kind::upper_T; + bool has_seconds = true; + std::size_t subsecond_precision = 6; // [us] +}; + +bool operator==(const local_datetime_format_info&, const local_datetime_format_info&) noexcept; +bool operator!=(const local_datetime_format_info&, const local_datetime_format_info&) noexcept; + +struct local_date_format_info +{ + // nothing, for now +}; + +bool operator==(const local_date_format_info&, const local_date_format_info&) noexcept; +bool operator!=(const local_date_format_info&, const local_date_format_info&) noexcept; + +struct local_time_format_info +{ + bool has_seconds = true; + std::size_t subsecond_precision = 6; // [us] +}; + +bool operator==(const local_time_format_info&, const local_time_format_info&) noexcept; +bool operator!=(const local_time_format_info&, const local_time_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// array + +enum class array_format : std::uint8_t +{ + default_format = 0, + oneline = 1, + multiline = 2, + array_of_tables = 3 // [[format.in.this.way]] +}; + +std::ostream& operator<<(std::ostream& os, const array_format f); +std::string to_string(const array_format); + +struct array_format_info +{ + array_format fmt = array_format::default_format; + indent_char indent_type = indent_char::space; + std::int32_t body_indent = 4; // indent in case of multiline + std::int32_t closing_indent = 0; // indent of `]` +}; + +bool operator==(const array_format_info&, const array_format_info&) noexcept; +bool operator!=(const array_format_info&, const array_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// table + +enum class table_format : std::uint8_t +{ + multiline = 0, // [foo] \n bar = "baz" + oneline = 1, // foo = {bar = "baz"} + dotted = 2, // foo.bar = "baz" + multiline_oneline = 3, // foo = { \n bar = "baz" \n } + implicit = 4 // [x] defined by [x.y.z]. skip in serializer. +}; + +std::ostream& operator<<(std::ostream& os, const table_format f); +std::string to_string(const table_format); + +struct table_format_info +{ + table_format fmt = table_format::multiline; + indent_char indent_type = indent_char::space; + std::int32_t body_indent = 0; // indent of values + std::int32_t name_indent = 0; // indent of [table] + std::int32_t closing_indent = 0; // in case of {inline-table} +}; + +bool operator==(const table_format_info&, const table_format_info&) noexcept; +bool operator!=(const table_format_info&, const table_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// wrapper + +namespace detail +{ +template +struct value_with_format +{ + using value_type = T; + using format_type = F; + + value_with_format() = default; + ~value_with_format() = default; + value_with_format(const value_with_format&) = default; + value_with_format(value_with_format&&) = default; + value_with_format& operator=(const value_with_format&) = default; + value_with_format& operator=(value_with_format&&) = default; + + value_with_format(value_type v, format_type f) + : value{std::move(v)}, format{std::move(f)} + {} + + template + value_with_format(value_with_format other) + : value{std::move(other.value)}, format{std::move(other.format)} + {} + + value_type value; + format_type format; +}; +} // detail + +} // namespace toml +#endif // TOML11_FORMAT_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_FORMAT_IMPL_HPP +#define TOML11_FORMAT_IMPL_HPP + + +#include +#include + +namespace toml +{ + +// toml types with serialization info + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const indent_char& c) +{ + switch(c) + { + case indent_char::space: {os << "space" ; break;} + case indent_char::tab: {os << "tab" ; break;} + case indent_char::none: {os << "none" ; break;} + default: + { + os << "unknown indent char: " << static_cast(c); + } + } + return os; +} + +TOML11_INLINE std::string to_string(const indent_char c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +// ---------------------------------------------------------------------------- +// boolean + +// ---------------------------------------------------------------------------- +// integer + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const integer_format f) +{ + switch(f) + { + case integer_format::dec: {os << "dec"; break;} + case integer_format::bin: {os << "bin"; break;} + case integer_format::oct: {os << "oct"; break;} + case integer_format::hex: {os << "hex"; break;} + default: + { + os << "unknown integer_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const integer_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + + +TOML11_INLINE bool operator==(const integer_format_info& lhs, const integer_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.uppercase == rhs.uppercase && + lhs.width == rhs.width && + lhs.spacer == rhs.spacer && + lhs.suffix == rhs.suffix ; +} +TOML11_INLINE bool operator!=(const integer_format_info& lhs, const integer_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// floating + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const floating_format f) +{ + switch(f) + { + case floating_format::defaultfloat: {os << "defaultfloat"; break;} + case floating_format::fixed : {os << "fixed" ; break;} + case floating_format::scientific : {os << "scientific" ; break;} + case floating_format::hex : {os << "hex" ; break;} + default: + { + os << "unknown floating_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const floating_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const floating_format_info& lhs, const floating_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.prec == rhs.prec && + lhs.suffix == rhs.suffix ; +} +TOML11_INLINE bool operator!=(const floating_format_info& lhs, const floating_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// string + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const string_format f) +{ + switch(f) + { + case string_format::basic : {os << "basic" ; break;} + case string_format::literal : {os << "literal" ; break;} + case string_format::multiline_basic : {os << "multiline_basic" ; break;} + case string_format::multiline_literal: {os << "multiline_literal"; break;} + default: + { + os << "unknown string_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const string_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const string_format_info& lhs, const string_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.start_with_newline == rhs.start_with_newline ; +} +TOML11_INLINE bool operator!=(const string_format_info& lhs, const string_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} +// ---------------------------------------------------------------------------- +// datetime + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const datetime_delimiter_kind d) +{ + switch(d) + { + case datetime_delimiter_kind::upper_T: { os << "upper_T, "; break; } + case datetime_delimiter_kind::lower_t: { os << "lower_t, "; break; } + case datetime_delimiter_kind::space: { os << "space, "; break; } + default: + { + os << "unknown datetime delimiter: " << static_cast(d); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const datetime_delimiter_kind c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const offset_datetime_format_info& lhs, const offset_datetime_format_info& rhs) noexcept +{ + return lhs.delimiter == rhs.delimiter && + lhs.has_seconds == rhs.has_seconds && + lhs.subsecond_precision == rhs.subsecond_precision ; +} +TOML11_INLINE bool operator!=(const offset_datetime_format_info& lhs, const offset_datetime_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +TOML11_INLINE bool operator==(const local_datetime_format_info& lhs, const local_datetime_format_info& rhs) noexcept +{ + return lhs.delimiter == rhs.delimiter && + lhs.has_seconds == rhs.has_seconds && + lhs.subsecond_precision == rhs.subsecond_precision ; +} +TOML11_INLINE bool operator!=(const local_datetime_format_info& lhs, const local_datetime_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +TOML11_INLINE bool operator==(const local_date_format_info&, const local_date_format_info&) noexcept +{ + return true; +} +TOML11_INLINE bool operator!=(const local_date_format_info& lhs, const local_date_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +TOML11_INLINE bool operator==(const local_time_format_info& lhs, const local_time_format_info& rhs) noexcept +{ + return lhs.has_seconds == rhs.has_seconds && + lhs.subsecond_precision == rhs.subsecond_precision ; +} +TOML11_INLINE bool operator!=(const local_time_format_info& lhs, const local_time_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// array + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const array_format f) +{ + switch(f) + { + case array_format::default_format : {os << "default_format" ; break;} + case array_format::oneline : {os << "oneline" ; break;} + case array_format::multiline : {os << "multiline" ; break;} + case array_format::array_of_tables: {os << "array_of_tables"; break;} + default: + { + os << "unknown array_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const array_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const array_format_info& lhs, const array_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.indent_type == rhs.indent_type && + lhs.body_indent == rhs.body_indent && + lhs.closing_indent == rhs.closing_indent ; +} +TOML11_INLINE bool operator!=(const array_format_info& lhs, const array_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// table + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const table_format f) +{ + switch(f) + { + case table_format::multiline : {os << "multiline" ; break;} + case table_format::oneline : {os << "oneline" ; break;} + case table_format::dotted : {os << "dotted" ; break;} + case table_format::multiline_oneline: {os << "multiline_oneline"; break;} + case table_format::implicit : {os << "implicit" ; break;} + default: + { + os << "unknown table_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const table_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const table_format_info& lhs, const table_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.indent_type == rhs.indent_type && + lhs.body_indent == rhs.body_indent && + lhs.name_indent == rhs.name_indent && + lhs.closing_indent == rhs.closing_indent ; +} +TOML11_INLINE bool operator!=(const table_format_info& lhs, const table_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +} // namespace toml +#endif // TOML11_FORMAT_IMPL_HPP +#endif + +#endif// TOML11_FORMAT_HPP +#ifndef TOML11_DATETIME_HPP +#define TOML11_DATETIME_HPP + +#ifndef TOML11_DATETIME_FWD_HPP +#define TOML11_DATETIME_FWD_HPP + +#include +#include +#include + +#include +#include +#include + +namespace toml +{ + +enum class month_t : std::uint8_t +{ + Jan = 0, + Feb = 1, + Mar = 2, + Apr = 3, + May = 4, + Jun = 5, + Jul = 6, + Aug = 7, + Sep = 8, + Oct = 9, + Nov = 10, + Dec = 11 +}; + +// ---------------------------------------------------------------------------- + +struct local_date +{ + std::int16_t year{0}; // A.D. (like, 2018) + std::uint8_t month{0}; // [0, 11] + std::uint8_t day{0}; // [1, 31] + + local_date(int y, month_t m, int d) + : year {static_cast(y)}, + month{static_cast(m)}, + day {static_cast(d)} + {} + + explicit local_date(const std::tm& t) + : year {static_cast(t.tm_year + 1900)}, + month{static_cast(t.tm_mon)}, + day {static_cast(t.tm_mday)} + {} + + explicit local_date(const std::chrono::system_clock::time_point& tp); + explicit local_date(const std::time_t t); + + operator std::chrono::system_clock::time_point() const; + operator std::time_t() const; + + local_date() = default; + ~local_date() = default; + local_date(local_date const&) = default; + local_date(local_date&&) = default; + local_date& operator=(local_date const&) = default; + local_date& operator=(local_date&&) = default; +}; +bool operator==(const local_date& lhs, const local_date& rhs); +bool operator!=(const local_date& lhs, const local_date& rhs); +bool operator< (const local_date& lhs, const local_date& rhs); +bool operator<=(const local_date& lhs, const local_date& rhs); +bool operator> (const local_date& lhs, const local_date& rhs); +bool operator>=(const local_date& lhs, const local_date& rhs); + +std::ostream& operator<<(std::ostream& os, const local_date& date); +std::string to_string(const local_date& date); + +// ----------------------------------------------------------------------------- + +struct local_time +{ + std::uint8_t hour{0}; // [0, 23] + std::uint8_t minute{0}; // [0, 59] + std::uint8_t second{0}; // [0, 60] + std::uint16_t millisecond{0}; // [0, 999] + std::uint16_t microsecond{0}; // [0, 999] + std::uint16_t nanosecond{0}; // [0, 999] + + local_time(int h, int m, int s, + int ms = 0, int us = 0, int ns = 0) + : hour {static_cast(h)}, + minute{static_cast(m)}, + second{static_cast(s)}, + millisecond{static_cast(ms)}, + microsecond{static_cast(us)}, + nanosecond {static_cast(ns)} + {} + + explicit local_time(const std::tm& t) + : hour {static_cast(t.tm_hour)}, + minute{static_cast(t.tm_min )}, + second{static_cast(t.tm_sec )}, + millisecond{0}, microsecond{0}, nanosecond{0} + {} + + template + explicit local_time(const std::chrono::duration& t) + { + const auto h = std::chrono::duration_cast(t); + this->hour = static_cast(h.count()); + const auto t2 = t - h; + const auto m = std::chrono::duration_cast(t2); + this->minute = static_cast(m.count()); + const auto t3 = t2 - m; + const auto s = std::chrono::duration_cast(t3); + this->second = static_cast(s.count()); + const auto t4 = t3 - s; + const auto ms = std::chrono::duration_cast(t4); + this->millisecond = static_cast(ms.count()); + const auto t5 = t4 - ms; + const auto us = std::chrono::duration_cast(t5); + this->microsecond = static_cast(us.count()); + const auto t6 = t5 - us; + const auto ns = std::chrono::duration_cast(t6); + this->nanosecond = static_cast(ns.count()); + } + + operator std::chrono::nanoseconds() const; + + local_time() = default; + ~local_time() = default; + local_time(local_time const&) = default; + local_time(local_time&&) = default; + local_time& operator=(local_time const&) = default; + local_time& operator=(local_time&&) = default; +}; + +bool operator==(const local_time& lhs, const local_time& rhs); +bool operator!=(const local_time& lhs, const local_time& rhs); +bool operator< (const local_time& lhs, const local_time& rhs); +bool operator<=(const local_time& lhs, const local_time& rhs); +bool operator> (const local_time& lhs, const local_time& rhs); +bool operator>=(const local_time& lhs, const local_time& rhs); + +std::ostream& operator<<(std::ostream& os, const local_time& time); +std::string to_string(const local_time& time); + +// ---------------------------------------------------------------------------- + +struct time_offset +{ + std::int8_t hour{0}; // [-12, 12] + std::int8_t minute{0}; // [-59, 59] + + time_offset(int h, int m) + : hour {static_cast(h)}, + minute{static_cast(m)} + {} + + operator std::chrono::minutes() const; + + time_offset() = default; + ~time_offset() = default; + time_offset(time_offset const&) = default; + time_offset(time_offset&&) = default; + time_offset& operator=(time_offset const&) = default; + time_offset& operator=(time_offset&&) = default; +}; + +bool operator==(const time_offset& lhs, const time_offset& rhs); +bool operator!=(const time_offset& lhs, const time_offset& rhs); +bool operator< (const time_offset& lhs, const time_offset& rhs); +bool operator<=(const time_offset& lhs, const time_offset& rhs); +bool operator> (const time_offset& lhs, const time_offset& rhs); +bool operator>=(const time_offset& lhs, const time_offset& rhs); + +std::ostream& operator<<(std::ostream& os, const time_offset& offset); + +std::string to_string(const time_offset& offset); + +// ----------------------------------------------------------------------------- + +struct local_datetime +{ + local_date date{}; + local_time time{}; + + local_datetime(local_date d, local_time t): date{d}, time{t} {} + + explicit local_datetime(const std::tm& t): date{t}, time{t}{} + + explicit local_datetime(const std::chrono::system_clock::time_point& tp); + explicit local_datetime(const std::time_t t); + + operator std::chrono::system_clock::time_point() const; + operator std::time_t() const; + + local_datetime() = default; + ~local_datetime() = default; + local_datetime(local_datetime const&) = default; + local_datetime(local_datetime&&) = default; + local_datetime& operator=(local_datetime const&) = default; + local_datetime& operator=(local_datetime&&) = default; +}; + +bool operator==(const local_datetime& lhs, const local_datetime& rhs); +bool operator!=(const local_datetime& lhs, const local_datetime& rhs); +bool operator< (const local_datetime& lhs, const local_datetime& rhs); +bool operator<=(const local_datetime& lhs, const local_datetime& rhs); +bool operator> (const local_datetime& lhs, const local_datetime& rhs); +bool operator>=(const local_datetime& lhs, const local_datetime& rhs); + +std::ostream& operator<<(std::ostream& os, const local_datetime& dt); + +std::string to_string(const local_datetime& dt); + +// ----------------------------------------------------------------------------- + +struct offset_datetime +{ + local_date date{}; + local_time time{}; + time_offset offset{}; + + offset_datetime(local_date d, local_time t, time_offset o) + : date{d}, time{t}, offset{o} + {} + offset_datetime(const local_datetime& dt, time_offset o) + : date{dt.date}, time{dt.time}, offset{o} + {} + // use the current local timezone offset + explicit offset_datetime(const local_datetime& ld); + explicit offset_datetime(const std::chrono::system_clock::time_point& tp); + explicit offset_datetime(const std::time_t& t); + explicit offset_datetime(const std::tm& t); + + operator std::chrono::system_clock::time_point() const; + + operator std::time_t() const; + + offset_datetime() = default; + ~offset_datetime() = default; + offset_datetime(offset_datetime const&) = default; + offset_datetime(offset_datetime&&) = default; + offset_datetime& operator=(offset_datetime const&) = default; + offset_datetime& operator=(offset_datetime&&) = default; + + private: + + static time_offset get_local_offset(const std::time_t* tp); +}; + +bool operator==(const offset_datetime& lhs, const offset_datetime& rhs); +bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs); +bool operator< (const offset_datetime& lhs, const offset_datetime& rhs); +bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs); +bool operator> (const offset_datetime& lhs, const offset_datetime& rhs); +bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs); + +std::ostream& operator<<(std::ostream& os, const offset_datetime& dt); + +std::string to_string(const offset_datetime& dt); + +}//toml +#endif // TOML11_DATETIME_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_DATETIME_IMPL_HPP +#define TOML11_DATETIME_IMPL_HPP + + +#include +#include +#include +#include +#include + +#include +#include + +namespace toml +{ + +// To avoid non-threadsafe std::localtime. In C11 (not C++11!), localtime_s is +// provided in the absolutely same purpose, but C++11 is actually not compatible +// with C11. We need to dispatch the function depending on the OS. +namespace detail +{ +// TODO: find more sophisticated way to handle this +#if defined(_MSC_VER) +TOML11_INLINE std::tm localtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::localtime_s(&dst, src); + if (result) { throw std::runtime_error("localtime_s failed."); } + return dst; +} +TOML11_INLINE std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_s(&dst, src); + if (result) { throw std::runtime_error("gmtime_s failed."); } + return dst; +} +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_POSIX_SOURCE) +TOML11_INLINE std::tm localtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::localtime_r(src, &dst); + if (!result) { throw std::runtime_error("localtime_r failed."); } + return dst; +} +TOML11_INLINE std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_r(src, &dst); + if (!result) { throw std::runtime_error("gmtime_r failed."); } + return dst; +} +#else // fallback. not threadsafe +TOML11_INLINE std::tm localtime_s(const std::time_t* src) +{ + const auto result = std::localtime(src); + if (!result) { throw std::runtime_error("localtime failed."); } + return *result; +} +TOML11_INLINE std::tm gmtime_s(const std::time_t* src) +{ + const auto result = std::gmtime(src); + if (!result) { throw std::runtime_error("gmtime failed."); } + return *result; +} +#endif +} // detail + +// ---------------------------------------------------------------------------- + +TOML11_INLINE local_date::local_date(const std::chrono::system_clock::time_point& tp) +{ + const auto t = std::chrono::system_clock::to_time_t(tp); + const auto time = detail::localtime_s(&t); + *this = local_date(time); +} + +TOML11_INLINE local_date::local_date(const std::time_t t) + : local_date{std::chrono::system_clock::from_time_t(t)} +{} + +TOML11_INLINE local_date::operator std::chrono::system_clock::time_point() const +{ + // std::mktime returns date as local time zone. no conversion needed + std::tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = static_cast(this->day); + t.tm_mon = static_cast(this->month); + t.tm_year = static_cast(this->year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + return std::chrono::system_clock::from_time_t(std::mktime(&t)); +} + +TOML11_INLINE local_date::operator std::time_t() const +{ + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); +} + +TOML11_INLINE bool operator==(const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) == + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +TOML11_INLINE bool operator!=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) < + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +TOML11_INLINE bool operator<=(const local_date& lhs, const local_date& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const local_date& lhs, const local_date& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_date& date) +{ + os << std::setfill('0') << std::setw(4) << static_cast(date.year ) << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.month) + 1 << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.day ) ; + return os; +} + +TOML11_INLINE std::string to_string(const local_date& date) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << date; + return oss.str(); +} + +// ----------------------------------------------------------------------------- + +TOML11_INLINE local_time::operator std::chrono::nanoseconds() const +{ + return std::chrono::nanoseconds (this->nanosecond) + + std::chrono::microseconds(this->microsecond) + + std::chrono::milliseconds(this->millisecond) + + std::chrono::seconds(this->second) + + std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); +} + +TOML11_INLINE bool operator==(const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) == + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +TOML11_INLINE bool operator!=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) < + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +TOML11_INLINE bool operator<=(const local_time& lhs, const local_time& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const local_time& lhs, const local_time& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_time& time) +{ + os << std::setfill('0') << std::setw(2) << static_cast(time.hour ) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.minute) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.second); + if(time.millisecond != 0 || time.microsecond != 0 || time.nanosecond != 0) + { + os << '.'; + os << std::setfill('0') << std::setw(3) << static_cast(time.millisecond); + if(time.microsecond != 0 || time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.microsecond); + if(time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.nanosecond); + } + } + } + return os; +} + +TOML11_INLINE std::string to_string(const local_time& time) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << time; + return oss.str(); +} + +// ---------------------------------------------------------------------------- + +TOML11_INLINE time_offset::operator std::chrono::minutes() const +{ + return std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); +} + +TOML11_INLINE bool operator==(const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) == + std::make_tuple(rhs.hour, rhs.minute); +} +TOML11_INLINE bool operator!=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) < + std::make_tuple(rhs.hour, rhs.minute); +} +TOML11_INLINE bool operator<=(const time_offset& lhs, const time_offset& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const time_offset& offset) +{ + if(offset.hour == 0 && offset.minute == 0) + { + os << 'Z'; + return os; + } + int minute = static_cast(offset.hour) * 60 + offset.minute; + if(minute < 0){os << '-'; minute = std::abs(minute);} else {os << '+';} + os << std::setfill('0') << std::setw(2) << minute / 60 << ':'; + os << std::setfill('0') << std::setw(2) << minute % 60; + return os; +} + +TOML11_INLINE std::string to_string(const time_offset& offset) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << offset; + return oss.str(); +} + +// ----------------------------------------------------------------------------- + +TOML11_INLINE local_datetime::local_datetime(const std::chrono::system_clock::time_point& tp) +{ + const auto t = std::chrono::system_clock::to_time_t(tp); + std::tm ltime = detail::localtime_s(&t); + + this->date = local_date(ltime); + this->time = local_time(ltime); + + // std::tm lacks subsecond information, so diff between tp and tm + // can be used to get millisecond & microsecond information. + const auto t_diff = tp - + std::chrono::system_clock::from_time_t(std::mktime(<ime)); + this->time.millisecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.microsecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.nanosecond = static_cast( + std::chrono::duration_cast(t_diff).count()); +} + +TOML11_INLINE local_datetime::local_datetime(const std::time_t t) + : local_datetime{std::chrono::system_clock::from_time_t(t)} +{} + +TOML11_INLINE local_datetime::operator std::chrono::system_clock::time_point() const +{ + using internal_duration = + typename std::chrono::system_clock::time_point::duration; + + // Normally DST begins at A.M. 3 or 4. If we re-use conversion operator + // of local_date and local_time independently, the conversion fails if + // it is the day when DST begins or ends. Since local_date considers the + // time is 00:00 A.M. and local_time does not consider DST because it + // does not have any date information. We need to consider both date and + // time information at the same time to convert it correctly. + + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + + // std::mktime returns date as local time zone. no conversion needed + auto dt = std::chrono::system_clock::from_time_t(std::mktime(&t)); + dt += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); + return dt; +} + +TOML11_INLINE local_datetime::operator std::time_t() const +{ + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); +} + +TOML11_INLINE bool operator==(const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) == + std::make_tuple(rhs.date, rhs.time); +} +TOML11_INLINE bool operator!=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) < + std::make_tuple(rhs.date, rhs.time); +} +TOML11_INLINE bool operator<=(const local_datetime& lhs, const local_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_datetime& dt) +{ + os << dt.date << 'T' << dt.time; + return os; +} + +TOML11_INLINE std::string to_string(const local_datetime& dt) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << dt; + return oss.str(); +} + +// ----------------------------------------------------------------------------- + + +TOML11_INLINE offset_datetime::offset_datetime(const local_datetime& ld) + : date{ld.date}, time{ld.time}, offset{get_local_offset(nullptr)} + // use the current local timezone offset +{} +TOML11_INLINE offset_datetime::offset_datetime(const std::chrono::system_clock::time_point& tp) + : offset{0, 0} // use gmtime +{ + const auto timet = std::chrono::system_clock::to_time_t(tp); + const auto tm = detail::gmtime_s(&timet); + this->date = local_date(tm); + this->time = local_time(tm); +} +TOML11_INLINE offset_datetime::offset_datetime(const std::time_t& t) + : offset{0, 0} // use gmtime +{ + const auto tm = detail::gmtime_s(&t); + this->date = local_date(tm); + this->time = local_time(tm); +} +TOML11_INLINE offset_datetime::offset_datetime(const std::tm& t) + : offset{0, 0} // assume gmtime +{ + this->date = local_date(t); + this->time = local_time(t); +} + +TOML11_INLINE offset_datetime::operator std::chrono::system_clock::time_point() const +{ + // get date-time + using internal_duration = + typename std::chrono::system_clock::time_point::duration; + + // first, convert it to local date-time information in the same way as + // local_datetime does. later we will use time_t to adjust time offset. + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + const std::time_t tp_loc = std::mktime(std::addressof(t)); + + auto tp = std::chrono::system_clock::from_time_t(tp_loc); + tp += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); + + // Since mktime uses local time zone, it should be corrected. + // `12:00:00+09:00` means `03:00:00Z`. So mktime returns `03:00:00Z` if + // we are in `+09:00` timezone. To represent `12:00:00Z` there, we need + // to add `+09:00` to `03:00:00Z`. + // Here, it uses the time_t converted from date-time info to handle + // daylight saving time. + const auto ofs = get_local_offset(std::addressof(tp_loc)); + tp += std::chrono::hours (ofs.hour); + tp += std::chrono::minutes(ofs.minute); + + // We got `12:00:00Z` by correcting local timezone applied by mktime. + // Then we will apply the offset. Let's say `12:00:00-08:00` is given. + // And now, we have `12:00:00Z`. `12:00:00-08:00` means `20:00:00Z`. + // So we need to subtract the offset. + tp -= std::chrono::minutes(this->offset); + return tp; +} + +TOML11_INLINE offset_datetime::operator std::time_t() const +{ + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); +} + +TOML11_INLINE time_offset offset_datetime::get_local_offset(const std::time_t* tp) +{ + // get local timezone with the same date-time information as mktime + const auto t = detail::localtime_s(tp); + + std::array buf; + const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0 + if(result != 5) + { + throw std::runtime_error("toml::offset_datetime: cannot obtain " + "timezone information of current env"); + } + const int ofs = std::atoi(buf.data()); + const int ofs_h = ofs / 100; + const int ofs_m = ofs - (ofs_h * 100); + return time_offset(ofs_h, ofs_m); +} + +TOML11_INLINE bool operator==(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) == + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +TOML11_INLINE bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) < + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +TOML11_INLINE bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const offset_datetime& dt) +{ + os << dt.date << 'T' << dt.time << dt.offset; + return os; +} + +TOML11_INLINE std::string to_string(const offset_datetime& dt) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << dt; + return oss.str(); +} + +}//toml +#endif // TOML11_DATETIME_IMPL_HPP +#endif + +#endif // TOML11_DATETIME_HPP +#ifndef TOML11_COMPAT_HPP +#define TOML11_COMPAT_HPP + + +#include +#include +#include +#include +#include + +#include + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE +# if __has_include() +# include +# endif +#endif + +#include + +// ---------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if __has_cpp_attribute(deprecated) +# define TOML11_HAS_ATTR_DEPRECATED 1 +# endif +#endif + +#if defined(TOML11_HAS_ATTR_DEPRECATED) +# define TOML11_DEPRECATED(msg) [[deprecated(msg)]] +#elif defined(__GNUC__) +# define TOML11_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) +# define TOML11_DEPRECATED(msg) __declspec(deprecated(msg)) +#else +# define TOML11_DEPRECATED(msg) +#endif + +// ---------------------------------------------------------------------------- + +#if defined(__cpp_if_constexpr) +# if __cpp_if_constexpr >= 201606L +# define TOML11_HAS_CONSTEXPR_IF 1 +# endif +#endif + +#if defined(TOML11_HAS_CONSTEXPR_IF) +# define TOML11_CONSTEXPR_IF if constexpr +#else +# define TOML11_CONSTEXPR_IF if +#endif + +// ---------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if defined(__cpp_lib_make_unique) +# if __cpp_lib_make_unique >= 201304L +# define TOML11_HAS_STD_MAKE_UNIQUE 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ + +#if defined(TOML11_HAS_STD_MAKE_UNIQUE) + +using std::make_unique; + +#else + +template +std::unique_ptr make_unique(Ts&& ... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +#endif // TOML11_HAS_STD_MAKE_UNIQUE + +} // cxx +} // toml + +// --------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if defined(__cpp_lib_make_reverse_iterator) +# if __cpp_lib_make_reverse_iterator >= 201402L +# define TOML11_HAS_STD_MAKE_REVERSE_ITERATOR 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +# if defined(TOML11_HAS_STD_MAKE_REVERSE_ITERATOR) + +using std::make_reverse_iterator; + +#else + +template +std::reverse_iterator make_reverse_iterator(Iterator iter) +{ + return std::reverse_iterator(iter); +} + +#endif // TOML11_HAS_STD_MAKE_REVERSE_ITERATOR + +} // cxx +} // toml + +// --------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE +# if defined(__cpp_lib_clamp) +# if __cpp_lib_clamp >= 201603L +# define TOML11_HAS_STD_CLAMP 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_CLAMP) + +using std::clamp; + +#else + +template +T clamp(const T& x, const T& low, const T& high) noexcept +{ + assert(low <= high); + return (std::min)((std::max)(x, low), high); +} + +#endif // TOML11_HAS_STD_CLAMP + +} // cxx +} // toml + +// --------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE +# if defined(__cpp_lib_bit_cast) +# if __cpp_lib_bit_cast >= 201806L +# define TOML11_HAS_STD_BIT_CAST 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_BIT_CAST) + +using std::bit_cast; + +#else + +template +U bit_cast(const T& x) noexcept +{ + static_assert(sizeof(T) == sizeof(U), ""); + static_assert(std::is_default_constructible::value, ""); + + U z; + std::memcpy(reinterpret_cast(std::addressof(z)), + reinterpret_cast(std::addressof(x)), + sizeof(T)); + + return z; +} + +#endif // TOML11_HAS_STD_BIT_CAST + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// C++20 remove_cvref_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE +# if defined(__cpp_lib_remove_cvref) +# if __cpp_lib_remove_cvref >= 201711L +# define TOML11_HAS_STD_REMOVE_CVREF 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_REMOVE_CVREF) + +using std::remove_cvref; +using std::remove_cvref_t; + +#else + +template +struct remove_cvref +{ + using type = typename std::remove_cv< + typename std::remove_reference::type>::type; +}; + +template +using remove_cvref_t = typename remove_cvref::type; + +#endif // TOML11_HAS_STD_REMOVE_CVREF + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// C++17 and/or/not + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if defined(__cpp_lib_logical_traits) +# if __cpp_lib_logical_traits >= 201510L +# define TOML11_HAS_STD_CONJUNCTION 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_CONJUNCTION) + +using std::conjunction; +using std::disjunction; +using std::negation; + +#else + +template struct conjunction : std::true_type{}; +template struct conjunction : T{}; +template +struct conjunction : + std::conditional(T::value), conjunction, T>::type +{}; + +template struct disjunction : std::false_type{}; +template struct disjunction : T {}; +template +struct disjunction : + std::conditional(T::value), T, disjunction>::type +{}; + +template +struct negation : std::integral_constant(T::value)>{}; + +#endif // TOML11_HAS_STD_CONJUNCTION + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// C++14 index_sequence + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if defined(__cpp_lib_integer_sequence) +# if __cpp_lib_integer_sequence >= 201304L +# define TOML11_HAS_STD_INTEGER_SEQUENCE 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_INTEGER_SEQUENCE) + +using std::index_sequence; +using std::make_index_sequence; + +#else + +template struct index_sequence{}; + +template +struct double_index_sequence; + +template +struct double_index_sequence> +{ + using type = index_sequence; +}; +template +struct double_index_sequence> +{ + using type = index_sequence; +}; + +template +struct index_sequence_maker +{ + using type = typename double_index_sequence< + N % 2 == 1, N/2, typename index_sequence_maker::type + >::type; +}; +template<> +struct index_sequence_maker<0> +{ + using type = index_sequence<>; +}; + +template +using make_index_sequence = typename index_sequence_maker::type; + +#endif // TOML11_HAS_STD_INTEGER_SEQUENCE + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// C++14 enable_if_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if defined(__cpp_lib_transformation_trait_aliases) +# if __cpp_lib_transformation_trait_aliases >= 201304L +# define TOML11_HAS_STD_ENABLE_IF_T 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_ENABLE_IF_T) + +using std::enable_if_t; + +#else + +template +using enable_if_t = typename std::enable_if::type; + +#endif // TOML11_HAS_STD_ENABLE_IF_T + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// return_type_of_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if defined(__cpp_lib_is_invocable) +# if __cpp_lib_is_invocable >= 201703 +# define TOML11_HAS_STD_INVOKE_RESULT 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_INVOKE_RESULT) + +template +using return_type_of_t = std::invoke_result_t; + +#else + +// result_of is deprecated after C++17 +template +using return_type_of_t = typename std::result_of::type; + +#endif // TOML11_HAS_STD_INVOKE_RESULT + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// C++17 void_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if defined(__cpp_lib_void_t) +# if __cpp_lib_void_t >= 201411L +# define TOML11_HAS_STD_VOID_T 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_VOID_T) + +using std::void_t; + +#else + +template +using void_t = void; + +#endif // TOML11_HAS_STD_VOID_T + +} // cxx +} // toml + +// ---------------------------------------------------------------------------- +// (subset of) source_location + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 202002L +# if __has_include() +# define TOML11_HAS_STD_SOURCE_LOCATION +# endif // has_include +#endif // c++20 + +#if ! defined(TOML11_HAS_STD_SOURCE_LOCATION) +# if defined(__GNUC__) && ! defined(__clang__) +# if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if __has_include() +# define TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION +# endif +# endif +# endif // GNU g++ +#endif // not TOML11_HAS_STD_SOURCE_LOCATION + +#if ! defined(TOML11_HAS_STD_SOURCE_LOCATION) && ! defined(TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION) +# if defined(__GNUC__) && ! defined(__clang__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) +# define TOML11_HAS_BUILTIN_FILE_LINE 1 +# define TOML11_BUILTIN_LINE_TYPE int +# endif +# elif defined(__clang__) // clang 9.0.0 implements builtin_FILE/LINE +# if __has_builtin(__builtin_FILE) && __has_builtin(__builtin_LINE) +# define TOML11_HAS_BUILTIN_FILE_LINE 1 +# define TOML11_BUILTIN_LINE_TYPE unsigned int +# endif +# elif defined(_MSVC_LANG) && defined(_MSC_VER) +# if _MSC_VER > 1926 +# define TOML11_HAS_BUILTIN_FILE_LINE 1 +# define TOML11_BUILTIN_LINE_TYPE int +# endif +# endif +#endif + +#if defined(TOML11_HAS_STD_SOURCE_LOCATION) +#include +namespace toml +{ +namespace cxx +{ +using source_location = std::source_location; + +inline std::string to_string(const source_location& loc) +{ + const char* fname = loc.file_name(); + if(fname) + { + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in file ") + std::string(fname); + } + else + { + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in unknown file"); + } +} + +} // cxx +} // toml +#elif defined(TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION) +#include +namespace toml +{ +namespace cxx +{ +using source_location = std::experimental::source_location; + +inline std::string to_string(const source_location& loc) +{ + const char* fname = loc.file_name(); + if(fname) + { + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in file ") + std::string(fname); + } + else + { + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in unknown file"); + } +} + +} // cxx +} // toml +#elif defined(TOML11_HAS_BUILTIN_FILE_LINE) +namespace toml +{ +namespace cxx +{ +struct source_location +{ + using line_type = TOML11_BUILTIN_LINE_TYPE; + static source_location current(const line_type line = __builtin_LINE(), + const char* file = __builtin_FILE()) + { + return source_location(line, file); + } + + source_location(const line_type line, const char* file) + : line_(line), file_name_(file) + {} + + line_type line() const noexcept {return line_;} + const char* file_name() const noexcept {return file_name_;} + + private: + + line_type line_; + const char* file_name_; +}; + +inline std::string to_string(const source_location& loc) +{ + const char* fname = loc.file_name(); + if(fname) + { + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in file ") + std::string(fname); + } + else + { + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in unknown file"); + } +} + +} // cxx +} // toml +#else // no builtin +namespace toml +{ +namespace cxx +{ +struct source_location +{ + static source_location current() { return source_location{}; } +}; + +inline std::string to_string(const source_location&) +{ + return std::string(""); +} +} // cxx +} // toml +#endif // TOML11_HAS_STD_SOURCE_LOCATION + +// ---------------------------------------------------------------------------- +// (subset of) optional + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if __has_include() +# include +# endif // has_include(optional) +#endif // C++17 + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if defined(__cpp_lib_optional) +# if __cpp_lib_optional >= 201606L +# define TOML11_HAS_STD_OPTIONAL 1 +# endif +# endif +#endif + +#if defined(TOML11_HAS_STD_OPTIONAL) + +namespace toml +{ +namespace cxx +{ +using std::optional; + +inline std::nullopt_t make_nullopt() {return std::nullopt;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const std::nullopt_t&) +{ + os << "nullopt"; + return os; +} + +} // cxx +} // toml + +#else // TOML11_HAS_STD_OPTIONAL + +namespace toml +{ +namespace cxx +{ + +struct nullopt_t{}; +inline nullopt_t make_nullopt() {return nullopt_t{};} + +inline bool operator==(const nullopt_t&, const nullopt_t&) noexcept {return true;} +inline bool operator!=(const nullopt_t&, const nullopt_t&) noexcept {return false;} +inline bool operator< (const nullopt_t&, const nullopt_t&) noexcept {return false;} +inline bool operator<=(const nullopt_t&, const nullopt_t&) noexcept {return true;} +inline bool operator> (const nullopt_t&, const nullopt_t&) noexcept {return false;} +inline bool operator>=(const nullopt_t&, const nullopt_t&) noexcept {return true;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const nullopt_t&) +{ + os << "nullopt"; + return os; +} + +template +class optional +{ + public: + + using value_type = T; + + public: + + optional() noexcept : has_value_(false), null_('\0') {} + optional(nullopt_t) noexcept : has_value_(false), null_('\0') {} + + optional(const T& x): has_value_(true), value_(x) {} + optional(T&& x): has_value_(true), value_(std::move(x)) {} + + template::value, std::nullptr_t> = nullptr> + explicit optional(U&& x): has_value_(true), value_(std::forward(x)) {} + + optional(const optional& rhs): has_value_(rhs.has_value_) + { + if(rhs.has_value_) + { + this->assigner(rhs.value_); + } + } + optional(optional&& rhs): has_value_(rhs.has_value_) + { + if(this->has_value_) + { + this->assigner(std::move(rhs.value_)); + } + } + + optional& operator=(const optional& rhs) + { + if(this == std::addressof(rhs)) {return *this;} + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if(this->has_value_) + { + this->assigner(rhs.value_); + } + return *this; + } + optional& operator=(optional&& rhs) + { + if(this == std::addressof(rhs)) {return *this;} + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if(this->has_value_) + { + this->assigner(std::move(rhs.value_)); + } + return *this; + } + + template>, std::is_constructible + >::value, std::nullptr_t> = nullptr> + explicit optional(const optional& rhs): has_value_(rhs.has_value_), null_('\0') + { + if(rhs.has_value_) + { + this->assigner(rhs.value_); + } + } + template>, std::is_constructible + >::value, std::nullptr_t> = nullptr> + explicit optional(optional&& rhs): has_value_(rhs.has_value_), null_('\0') + { + if(this->has_value_) + { + this->assigner(std::move(rhs.value_)); + } + } + + template>, std::is_constructible + >::value, std::nullptr_t> = nullptr> + optional& operator=(const optional& rhs) + { + if(this == std::addressof(rhs)) {return *this;} + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if(this->has_value_) + { + this->assigner(rhs.value_); + } + return *this; + } + + template>, std::is_constructible + >::value, std::nullptr_t> = nullptr> + optional& operator=(optional&& rhs) + { + if(this == std::addressof(rhs)) {return *this;} + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if(this->has_value_) + { + this->assigner(std::move(rhs.value_)); + } + return *this; + } + ~optional() noexcept + { + this->cleanup(); + } + + explicit operator bool() const noexcept + { + return has_value_; + } + + bool has_value() const noexcept {return has_value_;} + + value_type const& value(source_location loc = source_location::current()) const + { + if( ! this->has_value_) + { + throw std::runtime_error("optional::value(): bad_unwrap" + to_string(loc)); + } + return this->value_; + } + value_type& value(source_location loc = source_location::current()) + { + if( ! this->has_value_) + { + throw std::runtime_error("optional::value(): bad_unwrap" + to_string(loc)); + } + return this->value_; + } + + value_type const& value_or(const value_type& opt) const + { + if(this->has_value_) {return this->value_;} else {return opt;} + } + value_type& value_or(value_type& opt) + { + if(this->has_value_) {return this->value_;} else {return opt;} + } + + private: + + void cleanup() noexcept + { + if(this->has_value_) + { + value_.~T(); + } + } + + template + void assigner(U&& x) + { + const auto tmp = ::new(std::addressof(this->value_)) value_type(std::forward(x)); + assert(tmp == std::addressof(this->value_)); + (void)tmp; + } + + private: + + bool has_value_; + union + { + char null_; + T value_; + }; +}; +} // cxx +} // toml +#endif // TOML11_HAS_STD_OPTIONAL + +#endif // TOML11_COMPAT_HPP +#ifndef TOML11_VALUE_T_HPP +#define TOML11_VALUE_T_HPP + +#ifndef TOML11_VALUE_T_FWD_HPP +#define TOML11_VALUE_T_FWD_HPP + + +#include +#include +#include + +#include + +namespace toml +{ + +// forward decl +template +class basic_value; + +// ---------------------------------------------------------------------------- +// enum representing toml types + +enum class value_t : std::uint8_t +{ + empty = 0, + boolean = 1, + integer = 2, + floating = 3, + string = 4, + offset_datetime = 5, + local_datetime = 6, + local_date = 7, + local_time = 8, + array = 9, + table = 10 +}; + +std::ostream& operator<<(std::ostream& os, value_t t); +std::string to_string(value_t t); + + +// ---------------------------------------------------------------------------- +// meta functions for internal use + +namespace detail +{ + +template +using value_t_constant = std::integral_constant; + +template +struct type_to_enum : value_t_constant {}; + +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; + +template +struct enum_to_type { using type = void; }; + +template struct enum_to_type { using type = typename V::boolean_type ; }; +template struct enum_to_type { using type = typename V::integer_type ; }; +template struct enum_to_type { using type = typename V::floating_type ; }; +template struct enum_to_type { using type = typename V::string_type ; }; +template struct enum_to_type { using type = typename V::offset_datetime_type; }; +template struct enum_to_type { using type = typename V::local_datetime_type ; }; +template struct enum_to_type { using type = typename V::local_date_type ; }; +template struct enum_to_type { using type = typename V::local_time_type ; }; +template struct enum_to_type { using type = typename V::array_type ; }; +template struct enum_to_type { using type = typename V::table_type ; }; + +template +using enum_to_type_t = typename enum_to_type::type; + +template +struct enum_to_fmt_type { using type = void; }; + +template<> struct enum_to_fmt_type { using type = boolean_format_info ; }; +template<> struct enum_to_fmt_type { using type = integer_format_info ; }; +template<> struct enum_to_fmt_type { using type = floating_format_info ; }; +template<> struct enum_to_fmt_type { using type = string_format_info ; }; +template<> struct enum_to_fmt_type { using type = offset_datetime_format_info; }; +template<> struct enum_to_fmt_type { using type = local_datetime_format_info ; }; +template<> struct enum_to_fmt_type { using type = local_date_format_info ; }; +template<> struct enum_to_fmt_type { using type = local_time_format_info ; }; +template<> struct enum_to_fmt_type { using type = array_format_info ; }; +template<> struct enum_to_fmt_type { using type = table_format_info ; }; + +template +using enum_to_fmt_type_t = typename enum_to_fmt_type::type; + +template +struct is_exact_toml_type0 : cxx::disjunction< + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same + >{}; +template struct is_exact_toml_type: is_exact_toml_type0, V> {}; +template struct is_not_toml_type : cxx::negation> {}; + +} // namespace detail +} // namespace toml +#endif // TOML11_VALUE_T_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_VALUE_T_IMPL_HPP +#define TOML11_VALUE_T_IMPL_HPP + + +#include +#include +#include + +namespace toml +{ + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, value_t t) +{ + switch(t) + { + case value_t::boolean : os << "boolean"; return os; + case value_t::integer : os << "integer"; return os; + case value_t::floating : os << "floating"; return os; + case value_t::string : os << "string"; return os; + case value_t::offset_datetime : os << "offset_datetime"; return os; + case value_t::local_datetime : os << "local_datetime"; return os; + case value_t::local_date : os << "local_date"; return os; + case value_t::local_time : os << "local_time"; return os; + case value_t::array : os << "array"; return os; + case value_t::table : os << "table"; return os; + case value_t::empty : os << "empty"; return os; + default : os << "unknown"; return os; + } +} + +TOML11_INLINE std::string to_string(value_t t) +{ + std::ostringstream oss; + oss << t; + return oss.str(); +} + +} // namespace toml +#endif // TOML11_VALUE_T_IMPL_HPP +#endif + +#endif // TOML11_VALUE_T_HPP +#ifndef TOML11_STORAGE_HPP +#define TOML11_STORAGE_HPP + + +namespace toml +{ +namespace detail +{ + +// It owns a pointer to T. It does deep-copy when copied. +// This struct is introduced to implement a recursive type. +// +// `toml::value` contains `std::vector` to represent a toml array. +// But, in the definition of `toml::value`, `toml::value` is still incomplete. +// `std::vector` of an incomplete type is not allowed in C++11 (it is allowed +// after C++17). To avoid this, we need to use a pointer to `toml::value`, like +// `std::vector>`. Although `std::unique_ptr` is +// noncopyable, we want to make `toml::value` copyable. `storage` is introduced +// to resolve those problems. +template +struct storage +{ + using value_type = T; + + explicit storage(value_type v): ptr_(cxx::make_unique(std::move(v))) {} + ~storage() = default; + + storage(const storage& rhs): ptr_(cxx::make_unique(*rhs.ptr_)) {} + storage& operator=(const storage& rhs) + { + this->ptr_ = cxx::make_unique(*rhs.ptr_); + return *this; + } + + storage(storage&&) = default; + storage& operator=(storage&&) = default; + + bool is_ok() const noexcept {return static_cast(ptr_);} + + value_type& get() const noexcept {return *ptr_;} + + private: + std::unique_ptr ptr_; +}; + +} // detail +} // toml +#endif // TOML11_STORAGE_HPP +#ifndef TOML11_COMMENTS_HPP +#define TOML11_COMMENTS_HPP + +#ifndef TOML11_COMMENTS_FWD_HPP +#define TOML11_COMMENTS_FWD_HPP + +// to use __has_builtin + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// This file provides mainly two classes, `preserve_comments` and `discard_comments`. +// Those two are a container that have the same interface as `std::vector` +// but bahaves in the opposite way. `preserve_comments` is just the same as +// `std::vector` and each `std::string` corresponds to a comment line. +// Conversely, `discard_comments` discards all the strings and ignores everything +// assigned in it. `discard_comments` is always empty and you will encounter an +// error whenever you access to the element. +namespace toml +{ +class discard_comments; // forward decl + +class preserve_comments +{ + public: + // `container_type` is not provided in discard_comments. + // do not use this inner-type in a generic code. + using container_type = std::vector; + + using size_type = container_type::size_type; + using difference_type = container_type::difference_type; + using value_type = container_type::value_type; + using reference = container_type::reference; + using const_reference = container_type::const_reference; + using pointer = container_type::pointer; + using const_pointer = container_type::const_pointer; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + using reverse_iterator = container_type::reverse_iterator; + using const_reverse_iterator = container_type::const_reverse_iterator; + + public: + + preserve_comments() = default; + ~preserve_comments() = default; + preserve_comments(preserve_comments const&) = default; + preserve_comments(preserve_comments &&) = default; + preserve_comments& operator=(preserve_comments const&) = default; + preserve_comments& operator=(preserve_comments &&) = default; + + explicit preserve_comments(const std::vector& c): comments(c){} + explicit preserve_comments(std::vector&& c) + : comments(std::move(c)) + {} + preserve_comments& operator=(const std::vector& c) + { + comments = c; + return *this; + } + preserve_comments& operator=(std::vector&& c) + { + comments = std::move(c); + return *this; + } + + explicit preserve_comments(const discard_comments&) {} + + explicit preserve_comments(size_type n): comments(n) {} + preserve_comments(size_type n, const std::string& x): comments(n, x) {} + preserve_comments(std::initializer_list x): comments(x) {} + template + preserve_comments(InputIterator first, InputIterator last) + : comments(first, last) + {} + + template + void assign(InputIterator first, InputIterator last) {comments.assign(first, last);} + void assign(std::initializer_list ini) {comments.assign(ini);} + void assign(size_type n, const std::string& val) {comments.assign(n, val);} + + // Related to the issue #97. + // + // `std::vector::insert` and `std::vector::erase` in the STL implementation + // included in GCC 4.8.5 takes `std::vector::iterator` instead of + // `std::vector::const_iterator`. It causes compilation error in GCC 4.8.5. +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && !defined(__clang__) +# if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) <= 40805 +# define TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION +# endif +#endif + +#ifdef TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION + iterator insert(iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + void insert(iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + void insert(iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + void insert(iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(iterator pos) {return comments.erase(pos);} + iterator erase(iterator first, iterator last) + { + return comments.erase(first, last); + } +#else + iterator insert(const_iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(const_iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + iterator insert(const_iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + iterator insert(const_iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + iterator insert(const_iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(const_iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(const_iterator pos) {return comments.erase(pos);} + iterator erase(const_iterator first, const_iterator last) + { + return comments.erase(first, last); + } +#endif + + void swap(preserve_comments& other) {comments.swap(other.comments);} + + void push_back(const std::string& v) {comments.push_back(v);} + void push_back(std::string&& v) {comments.push_back(std::move(v));} + void pop_back() {comments.pop_back();} + + template + void emplace_back(Ts&& ... args) {comments.emplace_back(std::forward(args)...);} + + void clear() {comments.clear();} + + size_type size() const noexcept {return comments.size();} + size_type max_size() const noexcept {return comments.max_size();} + size_type capacity() const noexcept {return comments.capacity();} + bool empty() const noexcept {return comments.empty();} + + void reserve(size_type n) {comments.reserve(n);} + void resize(size_type n) {comments.resize(n);} + void resize(size_type n, const std::string& c) {comments.resize(n, c);} + void shrink_to_fit() {comments.shrink_to_fit();} + + reference operator[](const size_type n) noexcept {return comments[n];} + const_reference operator[](const size_type n) const noexcept {return comments[n];} + reference at(const size_type n) {return comments.at(n);} + const_reference at(const size_type n) const {return comments.at(n);} + reference front() noexcept {return comments.front();} + const_reference front() const noexcept {return comments.front();} + reference back() noexcept {return comments.back();} + const_reference back() const noexcept {return comments.back();} + + pointer data() noexcept {return comments.data();} + const_pointer data() const noexcept {return comments.data();} + + iterator begin() noexcept {return comments.begin();} + iterator end() noexcept {return comments.end();} + const_iterator begin() const noexcept {return comments.begin();} + const_iterator end() const noexcept {return comments.end();} + const_iterator cbegin() const noexcept {return comments.cbegin();} + const_iterator cend() const noexcept {return comments.cend();} + + reverse_iterator rbegin() noexcept {return comments.rbegin();} + reverse_iterator rend() noexcept {return comments.rend();} + const_reverse_iterator rbegin() const noexcept {return comments.rbegin();} + const_reverse_iterator rend() const noexcept {return comments.rend();} + const_reverse_iterator crbegin() const noexcept {return comments.crbegin();} + const_reverse_iterator crend() const noexcept {return comments.crend();} + + friend bool operator==(const preserve_comments&, const preserve_comments&); + friend bool operator!=(const preserve_comments&, const preserve_comments&); + friend bool operator< (const preserve_comments&, const preserve_comments&); + friend bool operator<=(const preserve_comments&, const preserve_comments&); + friend bool operator> (const preserve_comments&, const preserve_comments&); + friend bool operator>=(const preserve_comments&, const preserve_comments&); + + friend void swap(preserve_comments&, std::vector&); + friend void swap(std::vector&, preserve_comments&); + + private: + + container_type comments; +}; + +bool operator==(const preserve_comments& lhs, const preserve_comments& rhs); +bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs); +bool operator< (const preserve_comments& lhs, const preserve_comments& rhs); +bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs); +bool operator> (const preserve_comments& lhs, const preserve_comments& rhs); +bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs); + +void swap(preserve_comments& lhs, preserve_comments& rhs); +void swap(preserve_comments& lhs, std::vector& rhs); +void swap(std::vector& lhs, preserve_comments& rhs); + +std::ostream& operator<<(std::ostream& os, const preserve_comments& com); + +namespace detail +{ + +// To provide the same interface with `preserve_comments`, `discard_comments` +// should have an iterator. But it does not contain anything, so we need to +// add an iterator that points nothing. +// +// It always points null, so DO NOT unwrap this iterator. It always crashes +// your program. +template +struct empty_iterator +{ + using value_type = T; + using reference_type = typename std::conditional::type; + using pointer_type = typename std::conditional::type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + + empty_iterator() = default; + ~empty_iterator() = default; + empty_iterator(empty_iterator const&) = default; + empty_iterator(empty_iterator &&) = default; + empty_iterator& operator=(empty_iterator const&) = default; + empty_iterator& operator=(empty_iterator &&) = default; + + // DO NOT call these operators. + reference_type operator*() const noexcept {std::terminate();} + pointer_type operator->() const noexcept {return nullptr;} + reference_type operator[](difference_type) const noexcept {return this->operator*();} + + // These operators do nothing. + empty_iterator& operator++() noexcept {return *this;} + empty_iterator operator++(int) noexcept {return *this;} + empty_iterator& operator--() noexcept {return *this;} + empty_iterator operator--(int) noexcept {return *this;} + + empty_iterator& operator+=(difference_type) noexcept {return *this;} + empty_iterator& operator-=(difference_type) noexcept {return *this;} + + empty_iterator operator+(difference_type) const noexcept {return *this;} + empty_iterator operator-(difference_type) const noexcept {return *this;} +}; + +template +bool operator==(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator!=(const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator< (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator<=(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator> (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator>=(const empty_iterator&, const empty_iterator&) noexcept {return true;} + +template +typename empty_iterator::difference_type +operator-(const empty_iterator&, const empty_iterator&) noexcept {return 0;} + +template +empty_iterator +operator+(typename empty_iterator::difference_type, const empty_iterator& rhs) noexcept {return rhs;} +template +empty_iterator +operator+(const empty_iterator& lhs, typename empty_iterator::difference_type) noexcept {return lhs;} + +} // detail + +// The default comment type. It discards all the comments. It requires only one +// byte to contain, so the memory footprint is smaller than preserve_comments. +// +// It just ignores `push_back`, `insert`, `erase`, and any other modifications. +// IT always returns size() == 0, the iterator taken by `begin()` is always the +// same as that of `end()`, and accessing through `operator[]` or iterators +// always causes a segmentation fault. DO NOT access to the element of this. +// +// Why this is chose as the default type is because the last version (2.x.y) +// does not contain any comments in a value. To minimize the impact on the +// efficiency, this is chosen as a default. +// +// To reduce the memory footprint, later we can try empty base optimization (EBO). +class discard_comments +{ + public: + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using reference = std::string&; + using const_reference = std::string const&; + using pointer = std::string*; + using const_pointer = std::string const*; + using iterator = detail::empty_iterator; + using const_iterator = detail::empty_iterator; + using reverse_iterator = detail::empty_iterator; + using const_reverse_iterator = detail::empty_iterator; + + public: + discard_comments() = default; + ~discard_comments() = default; + discard_comments(discard_comments const&) = default; + discard_comments(discard_comments &&) = default; + discard_comments& operator=(discard_comments const&) = default; + discard_comments& operator=(discard_comments &&) = default; + + explicit discard_comments(const std::vector&) noexcept {} + explicit discard_comments(std::vector&&) noexcept {} + discard_comments& operator=(const std::vector&) noexcept {return *this;} + discard_comments& operator=(std::vector&&) noexcept {return *this;} + + explicit discard_comments(const preserve_comments&) noexcept {} + + explicit discard_comments(size_type) noexcept {} + discard_comments(size_type, const std::string&) noexcept {} + discard_comments(std::initializer_list) noexcept {} + template + discard_comments(InputIterator, InputIterator) noexcept {} + + template + void assign(InputIterator, InputIterator) noexcept {} + void assign(std::initializer_list) noexcept {} + void assign(size_type, const std::string&) noexcept {} + + iterator insert(const_iterator, const std::string&) {return iterator{};} + iterator insert(const_iterator, std::string&&) {return iterator{};} + iterator insert(const_iterator, size_type, const std::string&) {return iterator{};} + template + iterator insert(const_iterator, InputIterator, InputIterator) {return iterator{};} + iterator insert(const_iterator, std::initializer_list) {return iterator{};} + + template + iterator emplace(const_iterator, Ts&& ...) {return iterator{};} + iterator erase(const_iterator) {return iterator{};} + iterator erase(const_iterator, const_iterator) {return iterator{};} + + void swap(discard_comments&) {return;} + + void push_back(const std::string&) {return;} + void push_back(std::string&& ) {return;} + void pop_back() {return;} + + template + void emplace_back(Ts&& ...) {return;} + + void clear() {return;} + + size_type size() const noexcept {return 0;} + size_type max_size() const noexcept {return 0;} + size_type capacity() const noexcept {return 0;} + bool empty() const noexcept {return true;} + + void reserve(size_type) {return;} + void resize(size_type) {return;} + void resize(size_type, const std::string&) {return;} + void shrink_to_fit() {return;} + + // DO NOT access to the element of this container. This container is always + // empty, so accessing through operator[], front/back, data causes address + // error. + + reference operator[](const size_type) noexcept {never_call("toml::discard_comment::operator[]");} + const_reference operator[](const size_type) const noexcept {never_call("toml::discard_comment::operator[]");} + reference at(const size_type) {throw std::out_of_range("toml::discard_comment is always empty.");} + const_reference at(const size_type) const {throw std::out_of_range("toml::discard_comment is always empty.");} + reference front() noexcept {never_call("toml::discard_comment::front");} + const_reference front() const noexcept {never_call("toml::discard_comment::front");} + reference back() noexcept {never_call("toml::discard_comment::back");} + const_reference back() const noexcept {never_call("toml::discard_comment::back");} + + pointer data() noexcept {return nullptr;} + const_pointer data() const noexcept {return nullptr;} + + iterator begin() noexcept {return iterator{};} + iterator end() noexcept {return iterator{};} + const_iterator begin() const noexcept {return const_iterator{};} + const_iterator end() const noexcept {return const_iterator{};} + const_iterator cbegin() const noexcept {return const_iterator{};} + const_iterator cend() const noexcept {return const_iterator{};} + + reverse_iterator rbegin() noexcept {return iterator{};} + reverse_iterator rend() noexcept {return iterator{};} + const_reverse_iterator rbegin() const noexcept {return const_iterator{};} + const_reverse_iterator rend() const noexcept {return const_iterator{};} + const_reverse_iterator crbegin() const noexcept {return const_iterator{};} + const_reverse_iterator crend() const noexcept {return const_iterator{};} + + private: + + [[noreturn]] static void never_call(const char *const this_function) + { +#if __has_builtin(__builtin_unreachable) + __builtin_unreachable(); +#endif + throw std::logic_error{this_function}; + } +}; + +inline bool operator==(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator!=(const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator< (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator<=(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator> (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator>=(const discard_comments&, const discard_comments&) noexcept {return true;} + +inline void swap(const discard_comments&, const discard_comments&) noexcept {return;} + +inline std::ostream& operator<<(std::ostream& os, const discard_comments&) {return os;} + +} // toml11 +#endif // TOML11_COMMENTS_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_COMMENTS_IMPL_HPP +#define TOML11_COMMENTS_IMPL_HPP + + +namespace toml +{ + +TOML11_INLINE bool operator==(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments == rhs.comments;} +TOML11_INLINE bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments != rhs.comments;} +TOML11_INLINE bool operator< (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments < rhs.comments;} +TOML11_INLINE bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments <= rhs.comments;} +TOML11_INLINE bool operator> (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments > rhs.comments;} +TOML11_INLINE bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments >= rhs.comments;} + +TOML11_INLINE void swap(preserve_comments& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs); + return; +} +TOML11_INLINE void swap(preserve_comments& lhs, std::vector& rhs) +{ + lhs.comments.swap(rhs); + return; +} +TOML11_INLINE void swap(std::vector& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs.comments); + return; +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const preserve_comments& com) +{ + for(const auto& c : com) + { + if(c.front() != '#') + { + os << '#'; + } + os << c << '\n'; + } + return os; +} + +} // toml11 +#endif // TOML11_COMMENTS_IMPL_HPP +#endif + +#endif // TOML11_COMMENTS_HPP +#ifndef TOML11_COLOR_HPP +#define TOML11_COLOR_HPP + +#ifndef TOML11_COLOR_FWD_HPP +#define TOML11_COLOR_FWD_HPP + +#include + +#ifdef TOML11_COLORIZE_ERROR_MESSAGE +#define TOML11_ERROR_MESSAGE_COLORIZED true +#else +#define TOML11_ERROR_MESSAGE_COLORIZED false +#endif + +#ifdef TOML11_USE_THREAD_LOCAL_COLORIZATION +#define TOML11_THREAD_LOCAL_COLORIZATION thread_local +#else +#define TOML11_THREAD_LOCAL_COLORIZATION +#endif + +namespace toml +{ +namespace color +{ +// put ANSI escape sequence to ostream +inline namespace ansi +{ +namespace detail +{ + +// Control color mode globally +class color_mode +{ + public: + + void enable() noexcept + { + should_color_ = true; + } + void disable() noexcept + { + should_color_ = false; + } + bool should_color() const noexcept + { + return should_color_; + } + + private: + + bool should_color_ = TOML11_ERROR_MESSAGE_COLORIZED; +}; + +inline color_mode& color_status() noexcept +{ + static TOML11_THREAD_LOCAL_COLORIZATION color_mode status; + return status; +} + +} // detail + +std::ostream& reset (std::ostream& os); +std::ostream& bold (std::ostream& os); +std::ostream& grey (std::ostream& os); +std::ostream& gray (std::ostream& os); +std::ostream& red (std::ostream& os); +std::ostream& green (std::ostream& os); +std::ostream& yellow (std::ostream& os); +std::ostream& blue (std::ostream& os); +std::ostream& magenta(std::ostream& os); +std::ostream& cyan (std::ostream& os); +std::ostream& white (std::ostream& os); + +} // ansi + +inline void enable() +{ + return detail::color_status().enable(); +} +inline void disable() +{ + return detail::color_status().disable(); +} +inline bool should_color() +{ + return detail::color_status().should_color(); +} + +} // color +} // toml +#endif // TOML11_COLOR_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_COLOR_IMPL_HPP +#define TOML11_COLOR_IMPL_HPP + + +#include + +namespace toml +{ +namespace color +{ +// put ANSI escape sequence to ostream +inline namespace ansi +{ + +TOML11_INLINE std::ostream& reset(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[00m";} + return os; +} +TOML11_INLINE std::ostream& bold(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[01m";} + return os; +} +TOML11_INLINE std::ostream& grey(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[30m";} + return os; +} +TOML11_INLINE std::ostream& gray(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[30m";} + return os; +} +TOML11_INLINE std::ostream& red(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[31m";} + return os; +} +TOML11_INLINE std::ostream& green(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[32m";} + return os; +} +TOML11_INLINE std::ostream& yellow(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[33m";} + return os; +} +TOML11_INLINE std::ostream& blue(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[34m";} + return os; +} +TOML11_INLINE std::ostream& magenta(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[35m";} + return os; +} +TOML11_INLINE std::ostream& cyan (std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[36m";} + return os; +} +TOML11_INLINE std::ostream& white (std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[37m";} + return os; +} + +} // ansi +} // color +} // toml +#endif // TOML11_COLOR_IMPL_HPP +#endif + +#endif // TOML11_COLOR_HPP +#ifndef TOML11_SPEC_HPP +#define TOML11_SPEC_HPP + +#include +#include +#include +#include +#include + +#include + +namespace toml +{ + +struct semantic_version +{ + constexpr semantic_version(std::uint32_t mjr, std::uint32_t mnr, std::uint32_t p) noexcept + : major{mjr}, minor{mnr}, patch{p} + {} + + std::uint32_t major; + std::uint32_t minor; + std::uint32_t patch; +}; + +constexpr inline semantic_version +make_semver(std::uint32_t mjr, std::uint32_t mnr, std::uint32_t p) noexcept +{ + return semantic_version(mjr, mnr, p); +} + +constexpr inline bool +operator==(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return lhs.major == rhs.major && + lhs.minor == rhs.minor && + lhs.patch == rhs.patch; +} +constexpr inline bool +operator!=(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return !(lhs == rhs); +} +constexpr inline bool +operator<(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return lhs.major < rhs.major || + (lhs.major == rhs.major && lhs.minor < rhs.minor) || + (lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch < rhs.patch); +} +constexpr inline bool +operator>(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return rhs < lhs; +} +constexpr inline bool +operator<=(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return !(lhs > rhs); +} +constexpr inline bool +operator>=(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return !(lhs < rhs); +} + +inline std::ostream& operator<<(std::ostream& os, const semantic_version& v) +{ + os << v.major << '.' << v.minor << '.' << v.patch; + return os; +} + +inline std::string to_string(const semantic_version& v) +{ + std::ostringstream oss; + oss << v; + return oss.str(); +} + +struct spec +{ + constexpr static spec default_version() noexcept + { + return spec::v(1, 0, 0); + } + + constexpr static spec v(std::uint32_t mjr, std::uint32_t mnr, std::uint32_t p) noexcept + { + return spec(make_semver(mjr, mnr, p)); + } + + constexpr explicit spec(const semantic_version& semver) noexcept + : version{semver}, + v1_1_0_allow_control_characters_in_comments {semantic_version{1, 1, 0} <= semver}, + v1_1_0_allow_newlines_in_inline_tables {semantic_version{1, 1, 0} <= semver}, + v1_1_0_allow_trailing_comma_in_inline_tables{semantic_version{1, 1, 0} <= semver}, + v1_1_0_allow_non_english_in_bare_keys {semantic_version{1, 1, 0} <= semver}, + v1_1_0_add_escape_sequence_e {semantic_version{1, 1, 0} <= semver}, + v1_1_0_add_escape_sequence_x {semantic_version{1, 1, 0} <= semver}, + v1_1_0_make_seconds_optional {semantic_version{1, 1, 0} <= semver}, + ext_hex_float {false}, + ext_num_suffix{false}, + ext_null_value{false} + {} + + semantic_version version; // toml version + + // diff from v1.0.0 -> v1.1.0 + bool v1_1_0_allow_control_characters_in_comments; + bool v1_1_0_allow_newlines_in_inline_tables; + bool v1_1_0_allow_trailing_comma_in_inline_tables; + bool v1_1_0_allow_non_english_in_bare_keys; + bool v1_1_0_add_escape_sequence_e; + bool v1_1_0_add_escape_sequence_x; + bool v1_1_0_make_seconds_optional; + + // library extensions + bool ext_hex_float; // allow hex float (in C++ style) + bool ext_num_suffix; // allow number suffix (in C++ style) + bool ext_null_value; // allow `null` as a value +}; + +namespace detail +{ +inline std::pair> +to_tuple(const spec& s) noexcept +{ + return std::make_pair(std::cref(s.version), std::array{{ + s.v1_1_0_allow_control_characters_in_comments, + s.v1_1_0_allow_newlines_in_inline_tables, + s.v1_1_0_allow_trailing_comma_in_inline_tables, + s.v1_1_0_allow_non_english_in_bare_keys, + s.v1_1_0_add_escape_sequence_e, + s.v1_1_0_add_escape_sequence_x, + s.v1_1_0_make_seconds_optional, + s.ext_hex_float, + s.ext_num_suffix, + s.ext_null_value + }}); +} +} // detail + +inline bool operator==(const spec& lhs, const spec& rhs) noexcept +{ + return detail::to_tuple(lhs) == detail::to_tuple(rhs); +} +inline bool operator!=(const spec& lhs, const spec& rhs) noexcept +{ + return detail::to_tuple(lhs) != detail::to_tuple(rhs); +} +inline bool operator< (const spec& lhs, const spec& rhs) noexcept +{ + return detail::to_tuple(lhs) < detail::to_tuple(rhs); +} +inline bool operator<=(const spec& lhs, const spec& rhs) noexcept +{ + return detail::to_tuple(lhs) <= detail::to_tuple(rhs); +} +inline bool operator> (const spec& lhs, const spec& rhs) noexcept +{ + return detail::to_tuple(lhs) > detail::to_tuple(rhs); +} +inline bool operator>=(const spec& lhs, const spec& rhs) noexcept +{ + return detail::to_tuple(lhs) >= detail::to_tuple(rhs); +} + +} // namespace toml +#endif // TOML11_SPEC_HPP +#ifndef TOML11_ORDERED_MAP_HPP +#define TOML11_ORDERED_MAP_HPP + +#include +#include +#include +#include + +namespace toml +{ + +namespace detail +{ +template +struct ordered_map_ebo_container +{ + Cmp cmp_; // empty base optimization for empty Cmp type +}; +} // detail + +template, + typename Allocator = std::allocator>> +class ordered_map : detail::ordered_map_ebo_container +{ + public: + using key_type = Key; + using mapped_type = Val; + using value_type = std::pair; + + using key_compare = Cmp; + using allocator_type = Allocator; + + using container_type = std::vector; + using reference = typename container_type::reference; + using pointer = typename container_type::pointer; + using const_reference = typename container_type::const_reference; + using const_pointer = typename container_type::const_pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + + private: + + using ebo_base = detail::ordered_map_ebo_container; + + public: + + ordered_map() = default; + ~ordered_map() = default; + ordered_map(const ordered_map&) = default; + ordered_map(ordered_map&&) = default; + ordered_map& operator=(const ordered_map&) = default; + ordered_map& operator=(ordered_map&&) = default; + + ordered_map(const ordered_map& other, const Allocator& alloc) + : container_(other.container_, alloc) + {} + ordered_map(ordered_map&& other, const Allocator& alloc) + : container_(std::move(other.container_), alloc) + {} + + explicit ordered_map(const Cmp& cmp, const Allocator& alloc = Allocator()) + : ebo_base{cmp}, container_(alloc) + {} + explicit ordered_map(const Allocator& alloc) + : container_(alloc) + {} + + template + ordered_map(InputIterator first, InputIterator last, const Cmp& cmp = Cmp(), const Allocator& alloc = Allocator()) + : ebo_base{cmp}, container_(first, last, alloc) + {} + template + ordered_map(InputIterator first, InputIterator last, const Allocator& alloc) + : container_(first, last, alloc) + {} + + ordered_map(std::initializer_list v, const Cmp& cmp = Cmp(), const Allocator& alloc = Allocator()) + : ebo_base{cmp}, container_(std::move(v), alloc) + {} + ordered_map(std::initializer_list v, const Allocator& alloc) + : container_(std::move(v), alloc) + {} + ordered_map& operator=(std::initializer_list v) + { + this->container_ = std::move(v); + return *this; + } + + iterator begin() noexcept {return container_.begin();} + iterator end() noexcept {return container_.end();} + const_iterator begin() const noexcept {return container_.begin();} + const_iterator end() const noexcept {return container_.end();} + const_iterator cbegin() const noexcept {return container_.cbegin();} + const_iterator cend() const noexcept {return container_.cend();} + + bool empty() const noexcept {return container_.empty();} + std::size_t size() const noexcept {return container_.size();} + std::size_t max_size() const noexcept {return container_.max_size();} + + void clear() {container_.clear();} + + void push_back(const value_type& v) + { + if(this->contains(v.first)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.push_back(v); + } + void push_back(value_type&& v) + { + if(this->contains(v.first)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.push_back(std::move(v)); + } + void emplace_back(key_type k, mapped_type v) + { + if(this->contains(k)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.emplace_back(std::move(k), std::move(v)); + } + void pop_back() {container_.pop_back();} + + void insert(value_type kv) + { + if(this->contains(kv.first)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.push_back(std::move(kv)); + } + void emplace(key_type k, mapped_type v) + { + if(this->contains(k)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.emplace_back(std::move(k), std::move(v)); + } + + std::size_t count(const key_type& key) const + { + if(this->find(key) != this->end()) + { + return 1; + } + else + { + return 0; + } + } + bool contains(const key_type& key) const + { + return this->find(key) != this->end(); + } + iterator find(const key_type& key) noexcept + { + return std::find_if(this->begin(), this->end(), + [&key, this](const value_type& v) {return this->cmp_(v.first, key);}); + } + const_iterator find(const key_type& key) const noexcept + { + return std::find_if(this->begin(), this->end(), + [&key, this](const value_type& v) {return this->cmp_(v.first, key);}); + } + + mapped_type& at(const key_type& k) + { + const auto iter = this->find(k); + if(iter == this->end()) + { + throw std::out_of_range("ordered_map: no such element"); + } + return iter->second; + } + mapped_type const& at(const key_type& k) const + { + const auto iter = this->find(k); + if(iter == this->end()) + { + throw std::out_of_range("ordered_map: no such element"); + } + return iter->second; + } + + iterator erase(iterator pos) + { + return container_.erase(pos); + } + + iterator erase(const_iterator pos) + { + return container_.erase(pos); + } + + iterator erase(const_iterator first, const_iterator last) + { + return container_.erase(first, last); + } + + size_type erase(const key_type& key) + { + auto it = this->find(key); + if (it != this->end()) + { + container_.erase(it); + return 1; + } + return 0; + } + + mapped_type& operator[](const key_type& k) + { + const auto iter = this->find(k); + if(iter == this->end()) + { + this->container_.emplace_back(k, mapped_type{}); + return this->container_.back().second; + } + return iter->second; + } + + mapped_type const& operator[](const key_type& k) const + { + const auto iter = this->find(k); + if(iter == this->end()) + { + throw std::out_of_range("ordered_map: no such element"); + } + return iter->second; + } + + key_compare key_comp() const {return this->cmp_;} + + void swap(ordered_map& other) + { + container_.swap(other.container_); + } + + private: + + container_type container_; +}; + +template +bool operator==(const ordered_map& lhs, const ordered_map& rhs) +{ + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} +template +bool operator!=(const ordered_map& lhs, const ordered_map& rhs) +{ + return !(lhs == rhs); +} +template +bool operator<(const ordered_map& lhs, const ordered_map& rhs) +{ + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} +template +bool operator>(const ordered_map& lhs, const ordered_map& rhs) +{ + return rhs < lhs; +} +template +bool operator<=(const ordered_map& lhs, const ordered_map& rhs) +{ + return !(lhs > rhs); +} +template +bool operator>=(const ordered_map& lhs, const ordered_map& rhs) +{ + return !(lhs < rhs); +} + +template +void swap(ordered_map& lhs, ordered_map& rhs) +{ + lhs.swap(rhs); + return; +} + + +} // toml +#endif // TOML11_ORDERED_MAP_HPP +#ifndef TOML11_INTO_HPP +#define TOML11_INTO_HPP + +namespace toml +{ + +template +struct into; +// { +// static toml::value into_toml(const T& user_defined_type) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_INTO_HPP +#ifndef TOML11_FROM_HPP +#define TOML11_FROM_HPP + +namespace toml +{ + +template +struct from; +// { +// static T from_toml(const toml::value& v) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_FROM_HPP +#ifndef TOML11_TRAITS_HPP +#define TOML11_TRAITS_HPP + + +#include +#include +#include +#include +#include +#include +#include + +#if defined(TOML11_HAS_STRING_VIEW) +#include +#endif + +#if defined(TOML11_HAS_OPTIONAL) +#include +#endif + +namespace toml +{ +template +class basic_value; + +namespace detail +{ +// --------------------------------------------------------------------------- +// check whether type T is a kind of container/map class + +struct has_iterator_impl +{ + template static std::true_type check(typename T::iterator*); + template static std::false_type check(...); +}; +struct has_value_type_impl +{ + template static std::true_type check(typename T::value_type*); + template static std::false_type check(...); +}; +struct has_key_type_impl +{ + template static std::true_type check(typename T::key_type*); + template static std::false_type check(...); +}; +struct has_mapped_type_impl +{ + template static std::true_type check(typename T::mapped_type*); + template static std::false_type check(...); +}; +struct has_reserve_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().reserve(std::declval()))*); +}; +struct has_push_back_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().push_back(std::declval()))*); +}; +struct is_comparable_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval() < std::declval())*); +}; + +struct has_from_toml_method_impl +{ + template + static std::true_type check( + decltype(std::declval().from_toml(std::declval<::toml::basic_value>()))*); + + template + static std::false_type check(...); +}; +struct has_into_toml_method_impl +{ + template + static std::true_type check(decltype(std::declval().into_toml())*); + template + static std::false_type check(...); +}; + +struct has_template_into_toml_method_impl +{ + template + static std::true_type check(decltype(std::declval().template into_toml())*); + template + static std::false_type check(...); +}; + +struct has_specialized_from_impl +{ + template + static std::false_type check(...); + template)> + static std::true_type check(::toml::from*); +}; +struct has_specialized_into_impl +{ + template + static std::false_type check(...); + template)> + static std::true_type check(::toml::into*); +}; + + +/// Intel C++ compiler can not use decltype in parent class declaration, here +/// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076 +#ifdef __INTEL_COMPILER +#define decltype(...) std::enable_if::type +#endif + +template +struct has_iterator: decltype(has_iterator_impl::check(nullptr)){}; +template +struct has_value_type: decltype(has_value_type_impl::check(nullptr)){}; +template +struct has_key_type: decltype(has_key_type_impl::check(nullptr)){}; +template +struct has_mapped_type: decltype(has_mapped_type_impl::check(nullptr)){}; +template +struct has_reserve_method: decltype(has_reserve_method_impl::check(nullptr)){}; +template +struct has_push_back_method: decltype(has_push_back_method_impl::check(nullptr)){}; +template +struct is_comparable: decltype(is_comparable_impl::check(nullptr)){}; + +template +struct has_from_toml_method: decltype(has_from_toml_method_impl::check(nullptr)){}; + +template +struct has_into_toml_method: decltype(has_into_toml_method_impl::check(nullptr)){}; + +template +struct has_template_into_toml_method: decltype(has_template_into_toml_method_impl::check(nullptr)){}; + +template +struct has_specialized_from: decltype(has_specialized_from_impl::check(nullptr)){}; +template +struct has_specialized_into: decltype(has_specialized_into_impl::check(nullptr)){}; + +#ifdef __INTEL_COMPILER +#undef decltype +#endif + +// --------------------------------------------------------------------------- +// type checkers + +template struct is_std_pair_impl : std::false_type{}; +template +struct is_std_pair_impl> : std::true_type{}; +template +using is_std_pair = is_std_pair_impl>; + +template struct is_std_tuple_impl : std::false_type{}; +template +struct is_std_tuple_impl> : std::true_type{}; +template +using is_std_tuple = is_std_tuple_impl>; + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if __has_include() +template struct is_std_optional_impl : std::false_type{}; +template +struct is_std_optional_impl> : std::true_type{}; +template +using is_std_optional = is_std_optional_impl>; +# endif // +#endif // > C++17 + +template struct is_std_array_impl : std::false_type{}; +template +struct is_std_array_impl> : std::true_type{}; +template +using is_std_array = is_std_array_impl>; + +template struct is_std_forward_list_impl : std::false_type{}; +template +struct is_std_forward_list_impl> : std::true_type{}; +template +using is_std_forward_list = is_std_forward_list_impl>; + +template struct is_std_basic_string_impl : std::false_type{}; +template +struct is_std_basic_string_impl> : std::true_type{}; +template +using is_std_basic_string = is_std_basic_string_impl>; + +template struct is_1byte_std_basic_string_impl : std::false_type{}; +template +struct is_1byte_std_basic_string_impl> + : std::integral_constant {}; +template +using is_1byte_std_basic_string = is_std_basic_string_impl>; + +#if defined(TOML11_HAS_STRING_VIEW) +template struct is_std_basic_string_view_impl : std::false_type{}; +template +struct is_std_basic_string_view_impl> : std::true_type{}; +template +using is_std_basic_string_view = is_std_basic_string_view_impl>; + +template +struct is_string_view_of : std::false_type {}; +template +struct is_string_view_of, std::basic_string> : std::true_type {}; +#endif + +template struct is_chrono_duration_impl: std::false_type{}; +template +struct is_chrono_duration_impl>: std::true_type{}; +template +using is_chrono_duration = is_chrono_duration_impl>; + +template +struct is_map_impl : cxx::conjunction< // map satisfies all the following conditions + has_iterator, // has T::iterator + has_value_type, // has T::value_type + has_key_type, // has T::key_type + has_mapped_type // has T::mapped_type + >{}; +template +using is_map = is_map_impl>; + +template +struct is_container_impl : cxx::conjunction< + cxx::negation>, // not a map + cxx::negation>, // not a std::string +#ifdef TOML11_HAS_STRING_VIEW + cxx::negation>, // not a std::string_view +#endif + has_iterator, // has T::iterator + has_value_type // has T::value_type + >{}; +template +using is_container = is_container_impl>; + +template +struct is_basic_value_impl: std::false_type{}; +template +struct is_basic_value_impl<::toml::basic_value>: std::true_type{}; +template +using is_basic_value = is_basic_value_impl>; + +}// detail +}//toml +#endif // TOML11_TRAITS_HPP +#ifndef TOML11_EXCEPTION_HPP +#define TOML11_EXCEPTION_HPP + +#include + +namespace toml +{ + +struct exception : public std::exception +{ + public: + virtual ~exception() noexcept override = default; + virtual const char* what() const noexcept override {return "";} +}; + +} // toml +#endif // TOMl11_EXCEPTION_HPP +#ifndef TOML11_RESULT_HPP +#define TOML11_RESULT_HPP + + +#include +#include +#include +#include + +#include + +namespace toml +{ + +struct bad_result_access final : public ::toml::exception +{ + public: + explicit bad_result_access(std::string what_arg) + : what_(std::move(what_arg)) + {} + ~bad_result_access() noexcept override = default; + const char* what() const noexcept override {return what_.c_str();} + + private: + std::string what_; +}; + +// ----------------------------------------------------------------------------- + +template +struct success +{ + static_assert( ! std::is_void::value, ""); + + using value_type = T; + + explicit success(value_type v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template, T>::value, + std::nullptr_t> = nullptr> + explicit success(U&& v): value(std::forward(v)) {} + + template + explicit success(success v): value(std::move(v.value)) {} + + ~success() = default; + success(const success&) = default; + success(success&&) = default; + success& operator=(const success&) = default; + success& operator=(success&&) = default; + + value_type& get() noexcept {return value;} + value_type const& get() const noexcept {return value;} + + private: + + value_type value; +}; + +template +struct success> +{ + static_assert( ! std::is_void::value, ""); + + using value_type = T; + + explicit success(std::reference_wrapper v) noexcept + : value(std::move(v)) + {} + + ~success() = default; + success(const success&) = default; + success(success&&) = default; + success& operator=(const success&) = default; + success& operator=(success&&) = default; + + value_type& get() noexcept {return value.get();} + value_type const& get() const noexcept {return value.get();} + + private: + + std::reference_wrapper value; +}; + +template +success::type> ok(T&& v) +{ + return success::type>(std::forward(v)); +} +template +success ok(const char (&literal)[N]) +{ + return success(std::string(literal)); +} + +// ----------------------------------------------------------------------------- + +template +struct failure +{ + using value_type = T; + + explicit failure(value_type v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template, T>::value, + std::nullptr_t> = nullptr> + explicit failure(U&& v): value(std::forward(v)) {} + + template + explicit failure(failure v): value(std::move(v.value)) {} + + ~failure() = default; + failure(const failure&) = default; + failure(failure&&) = default; + failure& operator=(const failure&) = default; + failure& operator=(failure&&) = default; + + value_type& get() noexcept {return value;} + value_type const& get() const noexcept {return value;} + + private: + + value_type value; +}; + +template +struct failure> +{ + using value_type = T; + + explicit failure(std::reference_wrapper v) noexcept + : value(std::move(v)) + {} + + ~failure() = default; + failure(const failure&) = default; + failure(failure&&) = default; + failure& operator=(const failure&) = default; + failure& operator=(failure&&) = default; + + value_type& get() noexcept {return value.get();} + value_type const& get() const noexcept {return value.get();} + + private: + + std::reference_wrapper value; +}; + +template +failure::type> err(T&& v) +{ + return failure::type>(std::forward(v)); +} + +template +failure err(const char (&literal)[N]) +{ + return failure(std::string(literal)); +} + +/* ============================================================================ + * _ _ + * _ _ ___ ____ _| | |_ + * | '_/ -_|_-< || | | _| + * |_| \___/__/\_,_|_|\__| + */ + +template +struct result +{ + using success_type = success; + using failure_type = failure; + using value_type = typename success_type::value_type; + using error_type = typename failure_type::value_type; + + result(success_type s): is_ok_(true), succ_(std::move(s)) {} + result(failure_type f): is_ok_(false), fail_(std::move(f)) {} + + template, value_type>>, + std::is_convertible, value_type> + >::value, std::nullptr_t> = nullptr> + result(success s): is_ok_(true), succ_(std::move(s.value)) {} + + template, error_type>>, + std::is_convertible, error_type> + >::value, std::nullptr_t> = nullptr> + result(failure f): is_ok_(false), fail_(std::move(f.value)) {} + + result& operator=(success_type s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(s)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + return *this; + } + result& operator=(failure_type f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(f)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + return *this; + } + + template + result& operator=(success s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(s.value)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + return *this; + } + template + result& operator=(failure f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(f.value)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + return *this; + } + + ~result() noexcept {this->cleanup();} + + result(const result& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(other.succ_); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(other.fail_); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + } + result(result&& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(other.succ_)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(other.fail_)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + } + + result& operator=(const result& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(other.succ_); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(other.fail_); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + result& operator=(result&& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(other.succ_)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(other.fail_)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + template, value_type>>, + cxx::negation, error_type>>, + std::is_convertible, value_type>, + std::is_convertible, error_type> + >::value, std::nullptr_t> = nullptr> + result(result other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + } + + template, value_type>>, + cxx::negation, error_type>>, + std::is_convertible, value_type>, + std::is_convertible, error_type> + >::value, std::nullptr_t> = nullptr> + result& operator=(result other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + bool is_ok() const noexcept {return is_ok_;} + bool is_err() const noexcept {return !is_ok_;} + + explicit operator bool() const noexcept {return is_ok_;} + + value_type& unwrap(cxx::source_location loc = cxx::source_location::current()) + { + if(this->is_err()) + { + throw bad_result_access("toml::result: bad unwrap" + cxx::to_string(loc)); + } + return this->succ_.get(); + } + value_type const& unwrap(cxx::source_location loc = cxx::source_location::current()) const + { + if(this->is_err()) + { + throw bad_result_access("toml::result: bad unwrap" + cxx::to_string(loc)); + } + return this->succ_.get(); + } + + value_type& unwrap_or(value_type& opt) noexcept + { + if(this->is_err()) {return opt;} + return this->succ_.get(); + } + value_type const& unwrap_or(value_type const& opt) const noexcept + { + if(this->is_err()) {return opt;} + return this->succ_.get(); + } + + error_type& unwrap_err(cxx::source_location loc = cxx::source_location::current()) + { + if(this->is_ok()) + { + throw bad_result_access("toml::result: bad unwrap_err" + cxx::to_string(loc)); + } + return this->fail_.get(); + } + error_type const& unwrap_err(cxx::source_location loc = cxx::source_location::current()) const + { + if(this->is_ok()) + { + throw bad_result_access("toml::result: bad unwrap_err" + cxx::to_string(loc)); + } + return this->fail_.get(); + } + + value_type& as_ok() noexcept + { + assert(this->is_ok()); + return this->succ_.get(); + } + value_type const& as_ok() const noexcept + { + assert(this->is_ok()); + return this->succ_.get(); + } + + error_type& as_err() noexcept + { + assert(this->is_err()); + return this->fail_.get(); + } + error_type const& as_err() const noexcept + { + assert(this->is_err()); + return this->fail_.get(); + } + + private: + + void cleanup() noexcept + { +#if defined(__GNUC__) && ! defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wduplicated-branches" +#endif + + if(this->is_ok_) {this->succ_.~success_type();} + else {this->fail_.~failure_type();} + +#if defined(__GNUC__) && ! defined(__clang__) +#pragma GCC diagnostic pop +#endif + return; + } + + private: + + bool is_ok_; + union + { + success_type succ_; + failure_type fail_; + }; +}; + +// ---------------------------------------------------------------------------- + +namespace detail +{ +struct none_t {}; +inline bool operator==(const none_t&, const none_t&) noexcept {return true;} +inline bool operator!=(const none_t&, const none_t&) noexcept {return false;} +inline bool operator< (const none_t&, const none_t&) noexcept {return false;} +inline bool operator<=(const none_t&, const none_t&) noexcept {return true;} +inline bool operator> (const none_t&, const none_t&) noexcept {return false;} +inline bool operator>=(const none_t&, const none_t&) noexcept {return true;} +inline std::ostream& operator<<(std::ostream& os, const none_t&) +{ + os << "none"; + return os; +} +} // detail + +inline success ok() noexcept +{ + return success(detail::none_t{}); +} +inline failure err() noexcept +{ + return failure(detail::none_t{}); +} + +} // toml +#endif // TOML11_RESULT_HPP +#ifndef TOML11_UTILITY_HPP +#define TOML11_UTILITY_HPP + + +#include +#include + +#include +#include +#include + +namespace toml +{ +namespace detail +{ + +// to output character in an error message. +inline std::string show_char(const int c) +{ + using char_type = unsigned char; + if(std::isgraph(c)) + { + return std::string(1, static_cast(c)); + } + else + { + std::array buf; + buf.fill('\0'); + const auto r = std::snprintf(buf.data(), buf.size(), "0x%02x", c & 0xFF); + assert(r == static_cast(buf.size()) - 1); + (void) r; // Unused variable warning + auto in_hex = std::string(buf.data()); + switch(c) + { + case char_type('\0'): {in_hex += "(NUL)"; break;} + case char_type(' ') : {in_hex += "(SPACE)"; break;} + case char_type('\n'): {in_hex += "(LINE FEED)"; break;} + case char_type('\r'): {in_hex += "(CARRIAGE RETURN)"; break;} + case char_type('\t'): {in_hex += "(TAB)"; break;} + case char_type('\v'): {in_hex += "(VERTICAL TAB)"; break;} + case char_type('\f'): {in_hex += "(FORM FEED)"; break;} + case char_type('\x1B'): {in_hex += "(ESCAPE)"; break;} + default: break; + } + return in_hex; + } +} + +// --------------------------------------------------------------------------- + +template +void try_reserve_impl(Container& container, std::size_t N, std::true_type) +{ + container.reserve(N); + return; +} +template +void try_reserve_impl(Container&, std::size_t, std::false_type) noexcept +{ + return; +} + +template +void try_reserve(Container& container, std::size_t N) +{ + try_reserve_impl(container, N, has_reserve_method{}); + return; +} + +// --------------------------------------------------------------------------- + +template +result from_string(const std::string& str) +{ + T v; + std::istringstream iss(str); + iss >> v; + if(iss.fail()) + { + return err(); + } + return ok(v); +} + +// --------------------------------------------------------------------------- + +// helper function to avoid std::string(0, 'c') or std::string(iter, iter) +template +std::string make_string(Iterator first, Iterator last) +{ + if(first == last) {return "";} + return std::string(first, last); +} +inline std::string make_string(std::size_t len, char c) +{ + if(len == 0) {return "";} + return std::string(len, c); +} + +// --------------------------------------------------------------------------- + +template +struct string_conv_impl +{ + static_assert(sizeof(Char) == sizeof(char), ""); + static_assert(sizeof(Char2) == sizeof(char), ""); + + static std::basic_string invoke(std::basic_string s) + { + std::basic_string retval; + std::transform(s.begin(), s.end(), std::back_inserter(retval), + [](const Char2 c) {return static_cast(c);}); + return retval; + } + template + static std::basic_string invoke(const Char2 (&s)[N]) + { + std::basic_string retval; + // "string literal" has null-char at the end. to skip it, we use prev. + std::transform(std::begin(s), std::prev(std::end(s)), std::back_inserter(retval), + [](const Char2 c) {return static_cast(c);}); + return retval; + } +}; + +template +struct string_conv_impl +{ + static_assert(sizeof(Char) == sizeof(char), ""); + + static std::basic_string invoke(std::basic_string s) + { + return s; + } + template + static std::basic_string invoke(const Char (&s)[N]) + { + return std::basic_string(s); + } +}; + +template +cxx::enable_if_t::value, S> +string_conv(std::basic_string s) +{ + using C = typename S::value_type; + using T = typename S::traits_type; + using A = typename S::allocator_type; + return string_conv_impl::invoke(std::move(s)); +} +template +cxx::enable_if_t::value, S> +string_conv(const char (&s)[N]) +{ + using C = typename S::value_type; + using T = typename S::traits_type; + using A = typename S::allocator_type; + using C2 = char; + using T2 = std::char_traits; + using A2 = std::allocator; + + return string_conv_impl::template invoke(s); +} + +} // namespace detail +} // namespace toml +#endif // TOML11_UTILITY_HPP +#ifndef TOML11_LOCATION_HPP +#define TOML11_LOCATION_HPP + +#ifndef TOML11_LOCATION_FWD_HPP +#define TOML11_LOCATION_FWD_HPP + + +#include +#include +#include + +namespace toml +{ +namespace detail +{ + +class region; // fwd decl + +// +// To represent where we are reading in the parse functions. +// Since it "points" somewhere in the input stream, the length is always 1. +// +class location +{ + public: + + using char_type = unsigned char; // must be unsigned + using container_type = std::vector; + using difference_type = typename container_type::difference_type; // to suppress sign-conversion warning + using source_ptr = std::shared_ptr; + + public: + + location(source_ptr src, std::string src_name) + : source_(std::move(src)), source_name_(std::move(src_name)), + location_(0), line_number_(1), column_number_(1) + {} + + location(const location&) = default; + location(location&&) = default; + location& operator=(const location&) = default; + location& operator=(location&&) = default; + ~location() = default; + + void advance(std::size_t n = 1) noexcept; + void retrace() noexcept; + + bool is_ok() const noexcept { return static_cast(this->source_); } + + bool eof() const noexcept; + char_type current() const; + + char_type peek(); + + std::size_t get_location() const noexcept + { + return this->location_; + } + + std::size_t line_number() const noexcept + { + return this->line_number_; + } + std::size_t column_number() const noexcept + { + return this->column_number_; + } + std::string get_line() const; + + source_ptr const& source() const noexcept {return this->source_;} + std::string const& source_name() const noexcept {return this->source_name_;} + + private: + + void advance_impl(const std::size_t n); + void retrace_impl(); + std::size_t calc_column_number() const noexcept; + + private: + + friend region; + + private: + + source_ptr source_; + std::string source_name_; + std::size_t location_; // std::vector<>::difference_type is signed + std::size_t line_number_; + std::size_t column_number_; +}; + +bool operator==(const location& lhs, const location& rhs) noexcept; +bool operator!=(const location& lhs, const location& rhs); + +location prev(const location& loc); +location next(const location& loc); +location make_temporary_location(const std::string& str) noexcept; + +template +result +find_if(const location& first, const location& last, const F& func) noexcept +{ + if(first.source() != last.source()) { return err(); } + if(first.get_location() >= last.get_location()) { return err(); } + + auto loc = first; + while(loc.get_location() != last.get_location()) + { + if(func(loc.current())) + { + return ok(loc); + } + loc.advance(); + } + return err(); +} + +template +result +rfind_if(location first, const location& last, const F& func) +{ + if(first.source() != last.source()) { return err(); } + if(first.get_location() >= last.get_location()) { return err(); } + + auto loc = last; + while(loc.get_location() != first.get_location()) + { + if(func(loc.current())) + { + return ok(loc); + } + loc.retrace(); + } + if(func(first.current())) + { + return ok(first); + } + return err(); +} + +result find(const location& first, const location& last, + const location::char_type val); +result rfind(const location& first, const location& last, + const location::char_type val); + +std::size_t count(const location& first, const location& last, + const location::char_type& c); + +} // detail +} // toml +#endif // TOML11_LOCATION_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_LOCATION_IMPL_HPP +#define TOML11_LOCATION_IMPL_HPP + + +namespace toml +{ +namespace detail +{ + +TOML11_INLINE void location::advance(std::size_t n) noexcept +{ + assert(this->is_ok()); + if(this->location_ + n < this->source_->size()) + { + this->advance_impl(n); + } + else + { + this->advance_impl(this->source_->size() - this->location_); + + assert(this->location_ == this->source_->size()); + } +} +TOML11_INLINE void location::retrace(/*restricted to n=1*/) noexcept +{ + assert(this->is_ok()); + if(this->location_ == 0) + { + this->location_ = 0; + this->line_number_ = 1; + this->column_number_ = 1; + } + else + { + this->retrace_impl(); + } +} + +TOML11_INLINE bool location::eof() const noexcept +{ + assert(this->is_ok()); + return this->location_ >= this->source_->size(); +} +TOML11_INLINE location::char_type location::current() const +{ + assert(this->is_ok()); + if(this->eof()) {return '\0';} + + assert(this->location_ < this->source_->size()); + return this->source_->at(this->location_); +} + +TOML11_INLINE location::char_type location::peek() +{ + assert(this->is_ok()); + if(this->location_ >= this->source_->size()) + { + return '\0'; + } + else + { + return this->source_->at(this->location_ + 1); + } +} + +TOML11_INLINE std::string location::get_line() const +{ + assert(this->is_ok()); + const auto iter = std::next(this->source_->cbegin(), static_cast(this->location_)); + const auto riter = cxx::make_reverse_iterator(iter); + + const auto prev = std::find(riter, this->source_->crend(), char_type('\n')); + const auto next = std::find(iter, this->source_->cend(), char_type('\n')); + + return make_string(std::next(prev.base()), next); +} + +TOML11_INLINE std::size_t location::calc_column_number() const noexcept +{ + assert(this->is_ok()); + const auto iter = std::next(this->source_->cbegin(), static_cast(this->location_)); + const auto riter = cxx::make_reverse_iterator(iter); + const auto prev = std::find(riter, this->source_->crend(), char_type('\n')); + + assert(prev.base() <= iter); + return static_cast(std::distance(prev.base(), iter) + 1); // 1-origin +} + +TOML11_INLINE void location::advance_impl(const std::size_t n) +{ + assert(this->is_ok()); + assert(this->location_ + n <= this->source_->size()); + + auto iter = this->source_->cbegin(); + std::advance(iter, static_cast(this->location_)); + + for(std::size_t i=0; iline_number_ += 1; + this->column_number_ = 1; + } + else + { + this->column_number_ += 1; + } + iter++; + } + this->location_ += n; + return; +} +TOML11_INLINE void location::retrace_impl(/*n == 1*/) +{ + assert(this->is_ok()); + assert(this->location_ != 0); + + this->location_ -= 1; + + auto iter = this->source_->cbegin(); + std::advance(iter, static_cast(this->location_)); + if(*iter == '\n') + { + this->line_number_ -= 1; + this->column_number_ = this->calc_column_number(); + } + return; +} + +TOML11_INLINE bool operator==(const location& lhs, const location& rhs) noexcept +{ + if( ! lhs.is_ok() || ! rhs.is_ok()) + { + return (!lhs.is_ok()) && (!rhs.is_ok()); + } + return lhs.source() == rhs.source() && + lhs.source_name() == rhs.source_name() && + lhs.get_location() == rhs.get_location(); +} +TOML11_INLINE bool operator!=(const location& lhs, const location& rhs) +{ + return !(lhs == rhs); +} + +TOML11_INLINE location prev(const location& loc) +{ + location p(loc); + p.retrace(); + return p; +} +TOML11_INLINE location next(const location& loc) +{ + location p(loc); + p.advance(1); + return p; +} + +TOML11_INLINE location make_temporary_location(const std::string& str) noexcept +{ + location::container_type cont(str.size()); + std::transform(str.begin(), str.end(), cont.begin(), + [](const std::string::value_type& c) { + return cxx::bit_cast(c); + }); + return location(std::make_shared( + std::move(cont)), "internal temporary"); +} + +TOML11_INLINE result +find(const location& first, const location& last, const location::char_type val) +{ + return find_if(first, last, [val](const location::char_type c) { + return c == val; + }); +} +TOML11_INLINE result +rfind(const location& first, const location& last, const location::char_type val) +{ + return rfind_if(first, last, [val](const location::char_type c) { + return c == val; + }); +} + +TOML11_INLINE std::size_t +count(const location& first, const location& last, const location::char_type& c) +{ + if(first.source() != last.source()) { return 0; } + if(first.get_location() >= last.get_location()) { return 0; } + + auto loc = first; + std::size_t num = 0; + while(loc.get_location() != last.get_location()) + { + if(loc.current() == c) + { + num += 1; + } + loc.advance(); + } + return num; +} + +} // detail +} // toml +#endif // TOML11_LOCATION_HPP +#endif + +#endif // TOML11_LOCATION_HPP +#ifndef TOML11_REGION_HPP +#define TOML11_REGION_HPP + +#ifndef TOML11_REGION_FWD_HPP +#define TOML11_REGION_FWD_HPP + + +#include +#include + +#include + +namespace toml +{ +namespace detail +{ + +// +// To represent where is a toml::value defined, or where does an error occur. +// Stored in toml::value. source_location will be constructed based on this. +// +class region +{ + public: + + using char_type = location::char_type; + using container_type = location::container_type; + using difference_type = location::difference_type; + using source_ptr = location::source_ptr; + + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + + public: + + // a value that is constructed manually does not have input stream info + region() + : source_(nullptr), source_name_(""), length_(0), + first_(0), first_line_(0), first_column_(0), last_(0), last_line_(0), + last_column_(0) + {} + + // a value defined in [first, last). + // Those source must be the same. Instread, `region` does not make sense. + region(const location& first, const location& last); + + // shorthand of [loc, loc+1) + explicit region(const location& loc); + + ~region() = default; + region(const region&) = default; + region(region&&) = default; + region& operator=(const region&) = default; + region& operator=(region&&) = default; + + bool is_ok() const noexcept { return static_cast(this->source_); } + + operator bool() const noexcept { return this->is_ok(); } + + std::size_t length() const noexcept {return this->length_;} + + std::size_t first_line_number() const noexcept + { + return this->first_line_; + } + std::size_t first_column_number() const noexcept + { + return this->first_column_; + } + std::size_t last_line_number() const noexcept + { + return this->last_line_; + } + std::size_t last_column_number() const noexcept + { + return this->last_column_; + } + + char_type at(std::size_t i) const; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + std::string as_string() const; + std::vector> as_lines() const; + + source_ptr const& source() const noexcept {return this->source_;} + std::string const& source_name() const noexcept {return this->source_name_;} + + private: + + std::pair + take_line(const_iterator begin, const_iterator end) const; + + private: + + source_ptr source_; + std::string source_name_; + std::size_t length_; + std::size_t first_; + std::size_t first_line_; + std::size_t first_column_; + std::size_t last_; + std::size_t last_line_; + std::size_t last_column_; +}; + +} // namespace detail +} // namespace toml +#endif // TOML11_REGION_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_REGION_IMPL_HPP +#define TOML11_REGION_IMPL_HPP + + +#include +#include +#include +#include +#include +#include + +namespace toml +{ +namespace detail +{ + +// a value defined in [first, last). +// Those source must be the same. Instread, `region` does not make sense. +TOML11_INLINE region::region(const location& first, const location& last) + : source_(first.source()), source_name_(first.source_name()), + length_(last.get_location() - first.get_location()), + first_(first.get_location()), + first_line_(first.line_number()), + first_column_(first.column_number()), + last_(last.get_location()), + last_line_(last.line_number()), + last_column_(last.column_number()) +{ + assert(first.source() == last.source()); + assert(first.source_name() == last.source_name()); +} + + // shorthand of [loc, loc+1) +TOML11_INLINE region::region(const location& loc) + : source_(loc.source()), source_name_(loc.source_name()), length_(0), + first_line_(0), first_column_(0), last_line_(0), last_column_(0) +{ + // if the file ends with LF, the resulting region points no char. + if(loc.eof()) + { + if(loc.get_location() == 0) + { + this->length_ = 0; + this->first_ = 0; + this->first_line_ = 0; + this->first_column_ = 0; + this->last_ = 0; + this->last_line_ = 0; + this->last_column_ = 0; + } + else + { + const auto first = prev(loc); + this->first_ = first.get_location(); + this->first_line_ = first.line_number(); + this->first_column_ = first.column_number(); + this->last_ = loc.get_location(); + this->last_line_ = loc.line_number(); + this->last_column_ = loc.column_number(); + this->length_ = 1; + } + } + else + { + this->first_ = loc.get_location(); + this->first_line_ = loc.line_number(); + this->first_column_ = loc.column_number(); + this->last_ = loc.get_location() + 1; + this->last_line_ = loc.line_number(); + this->last_column_ = loc.column_number() + 1; + this->length_ = 1; + } +} + +TOML11_INLINE region::char_type region::at(std::size_t i) const +{ + if(this->last_ <= this->first_ + i) + { + throw std::out_of_range("range::at: index " + std::to_string(i) + + " exceeds length " + std::to_string(this->length_)); + } + const auto iter = std::next(this->source_->cbegin(), + static_cast(this->first_ + i)); + return *iter; +} + +TOML11_INLINE region::const_iterator region::begin() const noexcept +{ + return std::next(this->source_->cbegin(), + static_cast(this->first_)); +} +TOML11_INLINE region::const_iterator region::end() const noexcept +{ + return std::next(this->source_->cbegin(), + static_cast(this->last_)); +} +TOML11_INLINE region::const_iterator region::cbegin() const noexcept +{ + return std::next(this->source_->cbegin(), + static_cast(this->first_)); +} +TOML11_INLINE region::const_iterator region::cend() const noexcept +{ + return std::next(this->source_->cbegin(), + static_cast(this->last_)); +} + +TOML11_INLINE std::string region::as_string() const +{ + if(this->is_ok()) + { + const auto begin = std::next(this->source_->cbegin(), static_cast(this->first_)); + const auto end = std::next(this->source_->cbegin(), static_cast(this->last_ )); + return ::toml::detail::make_string(begin, end); + } + else + { + return std::string(""); + } +} + +TOML11_INLINE std::pair +region::take_line(const_iterator begin, const_iterator end) const +{ + // To omit long line, we cap region by before/after 30 chars + const auto dist_before = std::distance(source_->cbegin(), begin); + const auto dist_after = std::distance(end, source_->cend()); + + const const_iterator capped_begin = (dist_before <= 30) ? source_->cbegin() : std::prev(begin, 30); + const const_iterator capped_end = (dist_after <= 30) ? source_->cend() : std::next(end, 30); + + const auto lf = char_type('\n'); + const auto lf_before = std::find(cxx::make_reverse_iterator(begin), + cxx::make_reverse_iterator(capped_begin), lf); + const auto lf_after = std::find(end, capped_end, lf); + + auto offset = static_cast(std::distance(lf_before.base(), begin)); + + std::string retval = make_string(lf_before.base(), lf_after); + + if(lf_before.base() != source_->cbegin() && *lf_before != lf) + { + retval = "... " + retval; + offset += 4; + } + + if(lf_after != source_->cend() && *lf_after != lf) + { + retval = retval + " ..."; + } + + return std::make_pair(retval, offset); +} + +TOML11_INLINE std::vector> region::as_lines() const +{ + assert(this->is_ok()); + if(this->length_ == 0) + { + return std::vector>{ + std::make_pair("", std::size_t(0)) + }; + } + + // Consider the following toml file + // ``` + // array = [ + // 1, 2, 3, + // ] # comment + // ``` + // and the region represnets + // ``` + // [ + // 1, 2, 3, + // ] + // ``` + // but we want to show the following. + // ``` + // array = [ + // 1, 2, 3, + // ] # comment + // ``` + // So we need to find LFs before `begin` and after `end`. + // + // But, if region ends with LF, it should not include the next line. + // ``` + // a = 42 + // ^^^- with the last LF + // ``` + // So we start from `end-1` when looking for LF. + + const auto begin_idx = static_cast(this->first_); + const auto end_idx = static_cast(this->last_) - 1; + + // length_ != 0, so begin < end. then begin <= end-1 + assert(begin_idx <= end_idx); + + const auto begin = std::next(this->source_->cbegin(), begin_idx); + const auto end = std::next(this->source_->cbegin(), end_idx); + + assert(this->first_line_number() <= this->last_line_number()); + + if(this->first_line_number() == this->last_line_number()) + { + return std::vector>{ + this->take_line(begin, end) + }; + } + + // we have multiple lines. `begin` and `end` points different lines. + // that means that there is at least one `LF` between `begin` and `end`. + + const auto after_begin = std::distance(begin, this->source_->cend()); + const auto before_end = std::distance(this->source_->cbegin(), end); + + const_iterator capped_file_end = this->source_->cend(); + const_iterator capped_file_begin = this->source_->cbegin(); + if(60 < after_begin) {capped_file_end = std::next(begin, 50);} + if(60 < before_end) {capped_file_begin = std::prev(end, 50);} + + const auto lf = char_type('\n'); + const auto first_line_end = std::find(begin, capped_file_end, lf); + const auto last_line_begin = std::find(capped_file_begin, end, lf); + + const auto first_line = this->take_line(begin, first_line_end); + const auto last_line = this->take_line(last_line_begin, end); + + if(this->first_line_number() + 1 == this->last_line_number()) + { + return std::vector>{ + first_line, last_line + }; + } + else + { + return std::vector>{ + first_line, std::make_pair("...", 0), last_line + }; + } +} + +} // namespace detail +} // namespace toml +#endif // TOML11_REGION_IMPL_HPP +#endif + +#endif // TOML11_REGION_HPP +#ifndef TOML11_SOURCE_LOCATION_HPP +#define TOML11_SOURCE_LOCATION_HPP + +#ifndef TOML11_SOURCE_LOCATION_FWD_HPP +#define TOML11_SOURCE_LOCATION_FWD_HPP + + +#include +#include +#include + +namespace toml +{ + +// +// A struct to contain location in a toml file. +// +// To reduce memory consumption, it omits unrelated parts of long lines. like: +// +// 1. one long line, short region +// ``` +// | +// 1 | ... "foo", "bar", baz, "qux", "foobar", ... +// | ^-- unknown value +// ``` +// 2. long region +// ``` +// | +// 1 | array = [ "foo", ... "bar" ] +// | ^^^^^^^^^^^^^^^^^^^^- in this array +// ``` +// 3. many lines +// | +// 1 | array = [ "foo", +// | ^^^^^^^^ +// | ... +// | ^^^ +// | +// 10 | , "bar"] +// | ^^^^^^^^- in this array +// ``` +// +struct source_location +{ + public: + + explicit source_location(const detail::region& r); + ~source_location() = default; + source_location(source_location const&) = default; + source_location(source_location &&) = default; + source_location& operator=(source_location const&) = default; + source_location& operator=(source_location &&) = default; + + bool is_ok() const noexcept {return this->is_ok_;} + std::size_t length() const noexcept {return this->length_;} + + std::size_t first_line_number() const noexcept {return this->first_line_;} + std::size_t first_column_number() const noexcept {return this->first_column_;} + std::size_t last_line_number() const noexcept {return this->last_line_;} + std::size_t last_column_number() const noexcept {return this->last_column_;} + + std::string const& file_name() const noexcept {return this->file_name_;} + + std::size_t num_lines() const noexcept {return this->line_str_.size();} + + std::string const& first_line() const; + std::string const& last_line() const; + + std::vector const& lines() const noexcept {return line_str_;} + + // for internal use + std::size_t first_column_offset() const noexcept {return this->first_offset_;} + std::size_t last_column_offset() const noexcept {return this->last_offset_;} + + private: + + bool is_ok_; + std::size_t first_line_; + std::size_t first_column_; // column num in the actual file + std::size_t first_offset_; // column num in the shown line + std::size_t last_line_; + std::size_t last_column_; // column num in the actual file + std::size_t last_offset_; // column num in the shown line + std::size_t length_; + std::string file_name_; + std::vector line_str_; +}; + +namespace detail +{ + +std::size_t integer_width_base10(std::size_t i) noexcept; + +inline std::size_t line_width() noexcept {return 0;} + +template +std::size_t line_width(const source_location& loc, const std::string& /*msg*/, + const Ts& ... tail) noexcept +{ + return (std::max)( + integer_width_base10(loc.last_line_number()), line_width(tail...)); +} + +std::ostringstream& +format_filename(std::ostringstream& oss, const source_location& loc); + +std::ostringstream& +format_empty_line(std::ostringstream& oss, const std::size_t lnw); + +std::ostringstream& format_line(std::ostringstream& oss, + const std::size_t lnw, const std::size_t linenum, const std::string& line); + +std::ostringstream& format_underline(std::ostringstream& oss, + const std::size_t lnw, const std::size_t col, const std::size_t len, + const std::string& msg); + +std::string format_location_impl(const std::size_t lnw, + const std::string& prev_fname, + const source_location& loc, const std::string& msg); + +inline std::string format_location_rec(const std::size_t, const std::string&) +{ + return ""; +} + +template +std::string format_location_rec(const std::size_t lnw, + const std::string& prev_fname, + const source_location& loc, const std::string& msg, + const Ts& ... tail) +{ + return format_location_impl(lnw, prev_fname, loc, msg) + + format_location_rec(lnw, loc.file_name(), tail...); +} + +} // namespace detail + +// format a location info without title +template +std::string format_location( + const source_location& loc, const std::string& msg, const Ts& ... tail) +{ + const auto lnw = detail::line_width(loc, msg, tail...); + + const std::string f(""); // at the 1st iteration, no prev_filename is given + return detail::format_location_rec(lnw, f, loc, msg, tail...); +} + +} // toml +#endif // TOML11_SOURCE_LOCATION_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_SOURCE_LOCATION_IMPL_HPP +#define TOML11_SOURCE_LOCATION_IMPL_HPP + + + +#include +#include +#include +#include + +#include + +namespace toml +{ + +TOML11_INLINE source_location::source_location(const detail::region& r) + : is_ok_(false), + first_line_(1), + first_column_(1), + first_offset_(1), + last_line_(1), + last_column_(1), + last_offset_(1), + length_(0), + file_name_("unknown file") +{ + if(r.is_ok()) + { + this->is_ok_ = true; + this->file_name_ = r.source_name(); + this->first_line_ = r.first_line_number(); + this->first_column_ = r.first_column_number(); + this->last_line_ = r.last_line_number(); + this->last_column_ = r.last_column_number(); + this->length_ = r.length(); + + const auto lines = r.as_lines(); + assert( ! lines.empty()); + + for(const auto& l : lines) + { + this->line_str_.push_back(l.first); + } + + this->first_offset_ = lines.at( 0).second + 1; // to 1-origin + this->last_offset_ = lines.at(lines.size()-1).second + 1; + } +} + +TOML11_INLINE std::string const& source_location::first_line() const +{ + if(this->line_str_.size() == 0) + { + throw std::out_of_range("toml::source_location::first_line: `lines` is empty"); + } + return this->line_str_.front(); +} +TOML11_INLINE std::string const& source_location::last_line() const +{ + if(this->line_str_.size() == 0) + { + throw std::out_of_range("toml::source_location::first_line: `lines` is empty"); + } + return this->line_str_.back(); +} + +namespace detail +{ + +TOML11_INLINE std::size_t integer_width_base10(std::size_t i) noexcept +{ + std::size_t width = 0; + while(i != 0) + { + i /= 10; + width += 1; + } + return width; +} + +TOML11_INLINE std::ostringstream& +format_filename(std::ostringstream& oss, const source_location& loc) +{ + // --> example.toml + oss << color::bold << color::blue << " --> " << color::reset + << color::bold << loc.file_name() << '\n' << color::reset; + return oss; +} + +TOML11_INLINE std::ostringstream& format_empty_line(std::ostringstream& oss, + const std::size_t lnw) +{ + // | + oss << detail::make_string(lnw + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + return oss; +} + +TOML11_INLINE std::ostringstream& format_line(std::ostringstream& oss, + const std::size_t lnw, const std::size_t linenum, const std::string& line) +{ + // 10 | key = "value" + oss << ' ' << color::bold << color::blue + << std::setw(static_cast(lnw)) + << std::right << linenum << " | " << color::reset; + for(const char c : line) + { + if(std::isgraph(c) || c == ' ') + { + oss << c; + } + else + { + oss << show_char(c); + } + } + oss << '\n'; + return oss; +} +TOML11_INLINE std::ostringstream& format_underline(std::ostringstream& oss, + const std::size_t lnw, const std::size_t col, const std::size_t len, + const std::string& msg) +{ + // | ^^^^^^^-- this part + oss << make_string(lnw + 1, ' ') + << color::bold << color::blue << " | " << color::reset; + + // in case col is 0, so we don't create a string with size_t max length + const std::size_t sanitized_col = col == 0 ? 0 : col - 1 /*1-origin*/; + oss << make_string(sanitized_col, ' ') + << color::bold << color::red + << make_string(len, '^') << "-- " + << color::reset << msg << '\n'; + + return oss; +} + +TOML11_INLINE std::string format_location_impl(const std::size_t lnw, + const std::string& prev_fname, + const source_location& loc, const std::string& msg) +{ + std::ostringstream oss; + + if(loc.file_name() != prev_fname) + { + format_filename(oss, loc); + if( ! loc.lines().empty()) + { + format_empty_line(oss, lnw); + } + } + + if(loc.lines().size() == 1) + { + // when column points LF, it exceeds the size of the first line. + std::size_t underline_limit = 1; + if(loc.first_line().size() < loc.first_column_offset()) + { + underline_limit = 1; + } + else + { + underline_limit = loc.first_line().size() - loc.first_column_offset() + 1; + } + const auto underline_len = (std::min)(underline_limit, loc.length()); + + format_line(oss, lnw, loc.first_line_number(), loc.first_line()); + format_underline(oss, lnw, loc.first_column_offset(), underline_len, msg); + } + else if(loc.lines().size() == 2) + { + const auto first_underline_len = + loc.first_line().size() - loc.first_column_offset() + 1; + format_line(oss, lnw, loc.first_line_number(), loc.first_line()); + format_underline(oss, lnw, loc.first_column_offset(), + first_underline_len, ""); + + format_line(oss, lnw, loc.last_line_number(), loc.last_line()); + format_underline(oss, lnw, 1, loc.last_column_offset(), msg); + } + else if(loc.lines().size() > 2) + { + const auto first_underline_len = + loc.first_line().size() - loc.first_column_offset() + 1; + format_line(oss, lnw, loc.first_line_number(), loc.first_line()); + format_underline(oss, lnw, loc.first_column_offset(), + first_underline_len, "and"); + + if(loc.lines().size() == 3) + { + format_line(oss, lnw, loc.first_line_number()+1, loc.lines().at(1)); + format_underline(oss, lnw, 1, loc.lines().at(1).size(), "and"); + } + else + { + format_line(oss, lnw, loc.first_line_number()+1, " ..."); + format_empty_line(oss, lnw); + } + format_line(oss, lnw, loc.last_line_number(), loc.last_line()); + format_underline(oss, lnw, 1, loc.last_column_offset(), msg); + } + // if loc is empty, do nothing. + return oss.str(); +} + +} // namespace detail +} // toml +#endif // TOML11_SOURCE_LOCATION_IMPL_HPP +#endif + +#endif // TOML11_SOURCE_LOCATION_HPP +#ifndef TOML11_ERROR_INFO_HPP +#define TOML11_ERROR_INFO_HPP + +#ifndef TOML11_ERROR_INFO_FWD_HPP +#define TOML11_ERROR_INFO_FWD_HPP + + +namespace toml +{ + +// error info returned from parser. +struct error_info +{ + error_info(std::string t, source_location l, std::string m, std::string s = "") + : title_(std::move(t)), locations_{std::make_pair(std::move(l), std::move(m))}, + suffix_(std::move(s)) + {} + + error_info(std::string t, std::vector> l, + std::string s = "") + : title_(std::move(t)), locations_(std::move(l)), suffix_(std::move(s)) + {} + + std::string const& title() const noexcept {return title_;} + std::string & title() noexcept {return title_;} + + std::vector> const& + locations() const noexcept {return locations_;} + + void add_locations(source_location loc, std::string msg) noexcept + { + locations_.emplace_back(std::move(loc), std::move(msg)); + } + + std::string const& suffix() const noexcept {return suffix_;} + std::string & suffix() noexcept {return suffix_;} + + private: + + std::string title_; + std::vector> locations_; + std::string suffix_; // hint or something like that +}; + +// forward decl +template +class basic_value; + +namespace detail +{ +inline error_info make_error_info_rec(error_info e) +{ + return e; +} +inline error_info make_error_info_rec(error_info e, std::string s) +{ + e.suffix() = s; + return e; +} + +template +error_info make_error_info_rec(error_info e, + const basic_value& v, std::string msg, Ts&& ... tail); + +template +error_info make_error_info_rec(error_info e, + source_location loc, std::string msg, Ts&& ... tail) +{ + e.add_locations(std::move(loc), std::move(msg)); + return make_error_info_rec(std::move(e), std::forward(tail)...); +} + +} // detail + +template +error_info make_error_info( + std::string title, source_location loc, std::string msg, Ts&& ... tail) +{ + error_info ei(std::move(title), std::move(loc), std::move(msg)); + return detail::make_error_info_rec(ei, std::forward(tail) ... ); +} + +std::string format_error(const std::string& errkind, const error_info& err); +std::string format_error(const error_info& err); + +// for custom error message +template +std::string format_error(std::string title, + source_location loc, std::string msg, Ts&& ... tail) +{ + return format_error("", make_error_info(std::move(title), + std::move(loc), std::move(msg), std::forward(tail)...)); +} + +std::ostream& operator<<(std::ostream& os, const error_info& e); + +} // toml +#endif // TOML11_ERROR_INFO_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_ERROR_INFO_IMPL_HPP +#define TOML11_ERROR_INFO_IMPL_HPP + + +#include + +namespace toml +{ + +TOML11_INLINE std::string format_error(const std::string& errkind, const error_info& err) +{ + std::string errmsg; + if( ! errkind.empty()) + { + errmsg = errkind; + errmsg += ' '; + } + errmsg += err.title(); + errmsg += '\n'; + + const auto lnw = [&err]() { + std::size_t width = 0; + for(const auto& l : err.locations()) + { + width = (std::max)(detail::integer_width_base10(l.first.last_line_number()), width); + } + return width; + }(); + + bool first = true; + std::string prev_fname; + for(const auto& lm : err.locations()) + { + if( ! first) + { + std::ostringstream oss; + oss << detail::make_string(lnw + 1, ' ') + << color::bold << color::blue << " |" << color::reset + << color::bold << " ...\n" << color::reset; + oss << detail::make_string(lnw + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + errmsg += oss.str(); + } + + const auto& l = lm.first; + const auto& m = lm.second; + + errmsg += detail::format_location_impl(lnw, prev_fname, l, m); + + prev_fname = l.file_name(); + first = false; + } + + errmsg += err.suffix(); + + return errmsg; +} + +TOML11_INLINE std::string format_error(const error_info& err) +{ + std::ostringstream oss; + oss << color::red << color::bold << "[error]" << color::reset; + return format_error(oss.str(), err); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const error_info& e) +{ + os << format_error(e); + return os; +} + +} // toml +#endif // TOML11_ERROR_INFO_IMPL_HPP +#endif + +#endif // TOML11_ERROR_INFO_HPP +#ifndef TOML11_VALUE_HPP +#define TOML11_VALUE_HPP + + +#ifdef TOML11_HAS_STRING_VIEW +#include +#endif + +#ifdef TOML11_ENABLE_ACCESS_CHECK +#include +#endif + +#include + +namespace toml +{ +template +class basic_value; + +struct type_error final : public ::toml::exception +{ + public: + type_error(std::string what_arg, source_location loc) + : what_(std::move(what_arg)), loc_(std::move(loc)) + {} + ~type_error() noexcept override = default; + + const char* what() const noexcept override {return what_.c_str();} + + source_location const& location() const noexcept {return loc_;} + + private: + std::string what_; + source_location loc_; +}; + +// only for internal use +namespace detail +{ +template +error_info make_type_error(const basic_value&, const std::string&, const value_t); + +template +error_info make_not_found_error(const basic_value&, const std::string&, const typename basic_value::key_type&); + +template +void change_region_of_value(basic_value&, const basic_value&); + +template +struct getter; + +#ifdef TOML11_ENABLE_ACCESS_CHECK +template +void unset_access_flag(basic_value&); +#endif +} // detail + +template +class basic_value +{ + public: + + using config_type = TypeConfig; + using key_type = typename config_type::string_type; + using value_type = basic_value; + using boolean_type = typename config_type::boolean_type; + using integer_type = typename config_type::integer_type; + using floating_type = typename config_type::floating_type; + using string_type = typename config_type::string_type; + using local_time_type = ::toml::local_time; + using local_date_type = ::toml::local_date; + using local_datetime_type = ::toml::local_datetime; + using offset_datetime_type = ::toml::offset_datetime; + using array_type = typename config_type::template array_type; + using table_type = typename config_type::template table_type; + using comment_type = typename config_type::comment_type; + using char_type = typename string_type::value_type; + + private: + + using region_type = detail::region; + + public: + + basic_value() noexcept + : type_(value_t::empty), empty_('\0'), region_{}, comments_{} +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + ~basic_value() noexcept {this->cleanup();} + + // copy/move constructor/assigner ===================================== {{{ + + basic_value(const basic_value& v) + : type_(v.type_), region_(v.region_), comments_(v.comments_) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{v.accessed()} +#endif + { + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default : assigner(empty_ , '\0' ); break; + } + } + basic_value(basic_value&& v) + : type_(v.type()), region_(std::move(v.region_)), + comments_(std::move(v.comments_)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{v.accessed()} +#endif + { + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default : assigner(empty_ , '\0' ); break; + } + } + + basic_value& operator=(const basic_value& v) + { + if(this == std::addressof(v)) {return *this;} + + this->cleanup(); + this->type_ = v.type_; + this->region_ = v.region_; + this->comments_ = v.comments_; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = v.accessed(); +#endif + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default : assigner(empty_ , '\0' ); break; + } + return *this; + } + basic_value& operator=(basic_value&& v) + { + if(this == std::addressof(v)) {return *this;} + + this->cleanup(); + this->type_ = v.type_; + this->region_ = std::move(v.region_); + this->comments_ = std::move(v.comments_); +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = v.accessed(); +#endif + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default : assigner(empty_ , '\0' ); break; + } + return *this; + } + // }}} + + // constructor to overwrite commnets ================================== {{{ + + basic_value(basic_value v, std::vector com) + : type_(v.type()), region_(std::move(v.region_)), + comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{v.accessed()} +#endif + { + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default : assigner(empty_ , '\0' ); break; + } + } + // }}} + + // conversion between different basic_values ========================== {{{ + + template + basic_value(basic_value other) + : type_(other.type_), + region_(std::move(other.region_)), + comments_(std::move(other.comments_)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{other.accessed()} +#endif + { + switch(other.type_) + { + // use auto-convert in constructor + case value_t::boolean : assigner(boolean_ , std::move(other.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(other.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(other.floating_ )); break; + case value_t::string : assigner(string_ , std::move(other.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(other.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(other.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(other.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(other.local_time_ )); break; + + // may have different container type + case value_t::array : + { + array_type tmp( + std::make_move_iterator(other.array_.value.get().begin()), + std::make_move_iterator(other.array_.value.get().end())); + assigner(array_, array_storage( + detail::storage(std::move(tmp)), + other.array_.format + )); + break; + } + case value_t::table : + { + table_type tmp( + std::make_move_iterator(other.table_.value.get().begin()), + std::make_move_iterator(other.table_.value.get().end())); + assigner(table_, table_storage( + detail::storage(std::move(tmp)), + other.table_.format + )); + break; + } + default: break; + } + } + + template + basic_value(basic_value other, std::vector com) + : type_(other.type_), + region_(std::move(other.region_)), + comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{other.accessed()} +#endif + { + switch(other.type_) + { + // use auto-convert in constructor + case value_t::boolean : assigner(boolean_ , std::move(other.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(other.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(other.floating_ )); break; + case value_t::string : assigner(string_ , std::move(other.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(other.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(other.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(other.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(other.local_time_ )); break; + + // may have different container type + case value_t::array : + { + array_type tmp( + std::make_move_iterator(other.array_.value.get().begin()), + std::make_move_iterator(other.array_.value.get().end())); + assigner(array_, array_storage( + detail::storage(std::move(tmp)), + other.array_.format + )); + break; + } + case value_t::table : + { + table_type tmp( + std::make_move_iterator(other.table_.value.get().begin()), + std::make_move_iterator(other.table_.value.get().end())); + assigner(table_, table_storage( + detail::storage(std::move(tmp)), + other.table_.format + )); + break; + } + default: break; + } + } + template + basic_value& operator=(basic_value other) + { + this->cleanup(); + this->region_ = other.region_; + this->comments_ = comment_type(other.comments_); + this->type_ = other.type_; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = other.accessed(); +#endif + + switch(other.type_) + { + // use auto-convert in constructor + case value_t::boolean : assigner(boolean_ , std::move(other.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(other.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(other.floating_ )); break; + case value_t::string : assigner(string_ , std::move(other.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(other.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(other.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(other.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(other.local_time_ )); break; + + // may have different container type + case value_t::array : + { + array_type tmp( + std::make_move_iterator(other.array_.value.get().begin()), + std::make_move_iterator(other.array_.value.get().end())); + assigner(array_, array_storage( + detail::storage(std::move(tmp)), + other.array_.format + )); + break; + } + case value_t::table : + { + table_type tmp( + std::make_move_iterator(other.table_.value.get().begin()), + std::make_move_iterator(other.table_.value.get().end())); + assigner(table_, table_storage( + detail::storage(std::move(tmp)), + other.table_.format + )); + break; + } + default: break; + } + return *this; + } + // }}} + + // constructor (boolean) ============================================== {{{ + + basic_value(boolean_type x) + : basic_value(x, boolean_format_info{}, std::vector{}, region_type{}) + {} + basic_value(boolean_type x, boolean_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(boolean_type x, std::vector com) + : basic_value(x, boolean_format_info{}, std::move(com), region_type{}) + {} + basic_value(boolean_type x, boolean_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(boolean_type x, boolean_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::boolean), boolean_(boolean_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(boolean_type x) + { + boolean_format_info fmt; + if(this->is_boolean()) + { + fmt = this->as_boolean_fmt(); + } + this->cleanup(); + this->type_ = value_t::boolean; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->boolean_, boolean_storage(x, fmt)); + return *this; + } + + // }}} + + // constructor (integer) ============================================== {{{ + + basic_value(integer_type x) + : basic_value(std::move(x), integer_format_info{}, std::vector{}, region_type{}) + {} + basic_value(integer_type x, integer_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(integer_type x, std::vector com) + : basic_value(std::move(x), integer_format_info{}, std::move(com), region_type{}) + {} + basic_value(integer_type x, integer_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type{}) + {} + basic_value(integer_type x, integer_format_info fmt, std::vector com, region_type reg) + : type_(value_t::integer), integer_(integer_storage(std::move(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(integer_type x) + { + integer_format_info fmt; + if(this->is_integer()) + { + fmt = this->as_integer_fmt(); + } + this->cleanup(); + this->type_ = value_t::integer; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->integer_, integer_storage(std::move(x), std::move(fmt))); + return *this; + } + + private: + + template + using enable_if_integer_like_t = cxx::enable_if_t, boolean_type>>, + cxx::negation, integer_type>>, + std::is_integral> + >::value, std::nullptr_t>; + + public: + + template = nullptr> + basic_value(T x) + : basic_value(std::move(x), integer_format_info{}, std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, integer_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(std::move(x), integer_format_info{}, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, integer_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, integer_format_info fmt, std::vector com, region_type reg) + : type_(value_t::integer), integer_(integer_storage(std::move(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + template = nullptr> + basic_value& operator=(T x) + { + integer_format_info fmt; + if(this->is_integer()) + { + fmt = this->as_integer_fmt(); + } + this->cleanup(); + this->type_ = value_t::integer; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->integer_, integer_storage(x, std::move(fmt))); + return *this; + } + + // }}} + + // constructor (floating) ============================================= {{{ + + basic_value(floating_type x) + : basic_value(std::move(x), floating_format_info{}, std::vector{}, region_type{}) + {} + basic_value(floating_type x, floating_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(floating_type x, std::vector com) + : basic_value(std::move(x), floating_format_info{}, std::move(com), region_type{}) + {} + basic_value(floating_type x, floating_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type{}) + {} + basic_value(floating_type x, floating_format_info fmt, std::vector com, region_type reg) + : type_(value_t::floating), floating_(floating_storage(std::move(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(floating_type x) + { + floating_format_info fmt; + if(this->is_floating()) + { + fmt = this->as_floating_fmt(); + } + this->cleanup(); + this->type_ = value_t::floating; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->floating_, floating_storage(std::move(x), std::move(fmt))); + return *this; + } + + private: + + template + using enable_if_floating_like_t = cxx::enable_if_t, floating_type>>, + std::is_floating_point> + >::value, std::nullptr_t>; + + public: + + template = nullptr> + basic_value(T x) + : basic_value(x, floating_format_info{}, std::vector{}, region_type{}) + {} + + template = nullptr> + basic_value(T x, floating_format_info fmt) + : basic_value(x, std::move(fmt), std::vector{}, region_type{}) + {} + + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(x, floating_format_info{}, std::move(com), region_type{}) + {} + + template = nullptr> + basic_value(T x, floating_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type{}) + {} + + template = nullptr> + basic_value(T x, floating_format_info fmt, std::vector com, region_type reg) + : type_(value_t::floating), floating_(floating_storage(x, std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + + template = nullptr> + basic_value& operator=(T x) + { + floating_format_info fmt; + if(this->is_floating()) + { + fmt = this->as_floating_fmt(); + } + this->cleanup(); + this->type_ = value_t::floating; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->floating_, floating_storage(x, std::move(fmt))); + return *this; + } + + // }}} + + // constructor (string) =============================================== {{{ + + basic_value(string_type x) + : basic_value(std::move(x), string_format_info{}, std::vector{}, region_type{}) + {} + basic_value(string_type x, string_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(string_type x, std::vector com) + : basic_value(std::move(x), string_format_info{}, std::move(com), region_type{}) + {} + basic_value(string_type x, string_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type{}) + {} + basic_value(string_type x, string_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::string), string_(string_storage(std::move(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(string_type x) + { + string_format_info fmt; + if(this->is_string()) + { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->string_, string_storage(x, std::move(fmt))); + return *this; + } + + // "string literal" + + basic_value(const typename string_type::value_type* x) + : basic_value(x, string_format_info{}, std::vector{}, region_type{}) + {} + basic_value(const typename string_type::value_type* x, string_format_info fmt) + : basic_value(x, std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(const typename string_type::value_type* x, std::vector com) + : basic_value(x, string_format_info{}, std::move(com), region_type{}) + {} + basic_value(const typename string_type::value_type* x, string_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type{}) + {} + basic_value(const typename string_type::value_type* x, string_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::string), string_(string_storage(string_type(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(const typename string_type::value_type* x) + { + string_format_info fmt; + if(this->is_string()) + { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->string_, string_storage(string_type(x), std::move(fmt))); + return *this; + } + +#if defined(TOML11_HAS_STRING_VIEW) + using string_view_type = std::basic_string_view< + typename string_type::value_type, typename string_type::traits_type>; + + basic_value(string_view_type x) + : basic_value(x, string_format_info{}, std::vector{}, region_type{}) + {} + basic_value(string_view_type x, string_format_info fmt) + : basic_value(x, std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(string_view_type x, std::vector com) + : basic_value(x, string_format_info{}, std::move(com), region_type{}) + {} + basic_value(string_view_type x, string_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type{}) + {} + basic_value(string_view_type x, string_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::string), string_(string_storage(string_type(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(string_view_type x) + { + string_format_info fmt; + if(this->is_string()) + { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->string_, string_storage(string_type(x), std::move(fmt))); + return *this; + } + +#endif // TOML11_HAS_STRING_VIEW + + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x) + : basic_value(x, string_format_info{}, std::vector{}, region_type{}) + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x, string_format_info fmt) + : basic_value(x, std::move(fmt), std::vector{}, region_type{}) + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x, std::vector com) + : basic_value(x, string_format_info{}, std::move(com), region_type{}) + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x, string_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type{}) + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x, string_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::string), + string_(string_storage(detail::string_conv(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value& operator=(const T& x) + { + string_format_info fmt; + if(this->is_string()) + { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->string_, string_storage(detail::string_conv(x), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (local_date) =========================================== {{{ + + basic_value(local_date_type x) + : basic_value(x, local_date_format_info{}, std::vector{}, region_type{}) + {} + basic_value(local_date_type x, local_date_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(local_date_type x, std::vector com) + : basic_value(x, local_date_format_info{}, std::move(com), region_type{}) + {} + basic_value(local_date_type x, local_date_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(local_date_type x, local_date_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::local_date), local_date_(local_date_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(local_date_type x) + { + local_date_format_info fmt; + if(this->is_local_date()) + { + fmt = this->as_local_date_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_date; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->local_date_, local_date_storage(x, fmt)); + return *this; + } + + // }}} + + // constructor (local_time) =========================================== {{{ + + basic_value(local_time_type x) + : basic_value(x, local_time_format_info{}, std::vector{}, region_type{}) + {} + basic_value(local_time_type x, local_time_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(local_time_type x, std::vector com) + : basic_value(x, local_time_format_info{}, std::move(com), region_type{}) + {} + basic_value(local_time_type x, local_time_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(local_time_type x, local_time_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::local_time), local_time_(local_time_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(local_time_type x) + { + local_time_format_info fmt; + if(this->is_local_time()) + { + fmt = this->as_local_time_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_time; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->local_time_, local_time_storage(x, fmt)); + return *this; + } + + template + basic_value(const std::chrono::duration& x) + : basic_value(local_time_type(x), local_time_format_info{}, std::vector{}, region_type{}) + {} + template + basic_value(const std::chrono::duration& x, local_time_format_info fmt) + : basic_value(local_time_type(x), std::move(fmt), std::vector{}, region_type{}) + {} + template + basic_value(const std::chrono::duration& x, std::vector com) + : basic_value(local_time_type(x), local_time_format_info{}, std::move(com), region_type{}) + {} + template + basic_value(const std::chrono::duration& x, local_time_format_info fmt, std::vector com) + : basic_value(local_time_type(x), std::move(fmt), std::move(com), region_type{}) + {} + template + basic_value(const std::chrono::duration& x, + local_time_format_info fmt, + std::vector com, region_type reg) + : basic_value(local_time_type(x), std::move(fmt), std::move(com), std::move(reg)) + {} + template + basic_value& operator=(const std::chrono::duration& x) + { + local_time_format_info fmt; + if(this->is_local_time()) + { + fmt = this->as_local_time_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_time; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->local_time_, local_time_storage(local_time_type(x), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (local_datetime) =========================================== {{{ + + basic_value(local_datetime_type x) + : basic_value(x, local_datetime_format_info{}, std::vector{}, region_type{}) + {} + basic_value(local_datetime_type x, local_datetime_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(local_datetime_type x, std::vector com) + : basic_value(x, local_datetime_format_info{}, std::move(com), region_type{}) + {} + basic_value(local_datetime_type x, local_datetime_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(local_datetime_type x, local_datetime_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::local_datetime), local_datetime_(local_datetime_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(local_datetime_type x) + { + local_datetime_format_info fmt; + if(this->is_local_datetime()) + { + fmt = this->as_local_datetime_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_datetime; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->local_datetime_, local_datetime_storage(x, fmt)); + return *this; + } + + // }}} + + // constructor (offset_datetime) =========================================== {{{ + + basic_value(offset_datetime_type x) + : basic_value(x, offset_datetime_format_info{}, std::vector{}, region_type{}) + {} + basic_value(offset_datetime_type x, offset_datetime_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(offset_datetime_type x, std::vector com) + : basic_value(x, offset_datetime_format_info{}, std::move(com), region_type{}) + {} + basic_value(offset_datetime_type x, offset_datetime_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(offset_datetime_type x, offset_datetime_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::offset_datetime), offset_datetime_(offset_datetime_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(offset_datetime_type x) + { + offset_datetime_format_info fmt; + if(this->is_offset_datetime()) + { + fmt = this->as_offset_datetime_fmt(); + } + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->offset_datetime_, offset_datetime_storage(x, fmt)); + return *this; + } + + // system_clock::time_point + + basic_value(std::chrono::system_clock::time_point x) + : basic_value(offset_datetime_type(x), offset_datetime_format_info{}, std::vector{}, region_type{}) + {} + basic_value(std::chrono::system_clock::time_point x, offset_datetime_format_info fmt) + : basic_value(offset_datetime_type(x), fmt, std::vector{}, region_type{}) + {} + basic_value(std::chrono::system_clock::time_point x, std::vector com) + : basic_value(offset_datetime_type(x), offset_datetime_format_info{}, std::move(com), region_type{}) + {} + basic_value(std::chrono::system_clock::time_point x, offset_datetime_format_info fmt, std::vector com) + : basic_value(offset_datetime_type(x), fmt, std::move(com), region_type{}) + {} + basic_value(std::chrono::system_clock::time_point x, offset_datetime_format_info fmt, + std::vector com, region_type reg) + : basic_value(offset_datetime_type(x), std::move(fmt), std::move(com), std::move(reg)) + {} + basic_value& operator=(std::chrono::system_clock::time_point x) + { + offset_datetime_format_info fmt; + if(this->is_offset_datetime()) + { + fmt = this->as_offset_datetime_fmt(); + } + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->offset_datetime_, offset_datetime_storage(offset_datetime_type(x), fmt)); + return *this; + } + + // }}} + + // constructor (array) ================================================ {{{ + + basic_value(array_type x) + : basic_value(std::move(x), array_format_info{}, std::vector{}, region_type{}) + {} + basic_value(array_type x, array_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(array_type x, std::vector com) + : basic_value(std::move(x), array_format_info{}, std::move(com), region_type{}) + {} + basic_value(array_type x, array_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type{}) + {} + basic_value(array_type x, array_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::array), array_(array_storage( + detail::storage(std::move(x)), std::move(fmt) + )), region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(array_type x) + { + array_format_info fmt; + if(this->is_array()) + { + fmt = this->as_array_fmt(); + } + this->cleanup(); + this->type_ = value_t::array; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->array_, array_storage( + detail::storage(std::move(x)), std::move(fmt))); + return *this; + } + + private: + + template + using enable_if_array_like_t = cxx::enable_if_t, + cxx::negation>, + cxx::negation>, +#if defined(TOML11_HAS_STRING_VIEW) + cxx::negation>, +#endif + cxx::negation>, + cxx::negation> + >::value, std::nullptr_t>; + + public: + + template = nullptr> + basic_value(T x) + : basic_value(std::move(x), array_format_info{}, std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, array_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(std::move(x), array_format_info{}, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, array_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, array_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::array), array_(array_storage( + detail::storage(array_type( + std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end())) + ), std::move(fmt) + )), region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + template = nullptr> + basic_value& operator=(T x) + { + array_format_info fmt; + if(this->is_array()) + { + fmt = this->as_array_fmt(); + } + this->cleanup(); + this->type_ = value_t::array; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + array_type a(std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end())); + assigner(this->array_, array_storage( + detail::storage(std::move(a)), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (table) ================================================ {{{ + + basic_value(table_type x) + : basic_value(std::move(x), table_format_info{}, std::vector{}, region_type{}) + {} + basic_value(table_type x, table_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(table_type x, std::vector com) + : basic_value(std::move(x), table_format_info{}, std::move(com), region_type{}) + {} + basic_value(table_type x, table_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type{}) + {} + basic_value(table_type x, table_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::table), table_(table_storage( + detail::storage(std::move(x)), std::move(fmt) + )), region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + basic_value& operator=(table_type x) + { + table_format_info fmt; + if(this->is_table()) + { + fmt = this->as_table_fmt(); + } + this->cleanup(); + this->type_ = value_t::table; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + assigner(this->table_, table_storage( + detail::storage(std::move(x)), std::move(fmt))); + return *this; + } + + // table-like + + private: + + template + using enable_if_table_like_t = cxx::enable_if_t>, + detail::is_map, + cxx::negation>, + cxx::negation> + >::value, std::nullptr_t>; + + public: + + template = nullptr> + basic_value(T x) + : basic_value(std::move(x), table_format_info{}, std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, table_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(std::move(x), table_format_info{}, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, table_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, table_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::table), table_(table_storage( + detail::storage(table_type( + std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end()) + )), std::move(fmt) + )), region_(std::move(reg)), comments_(std::move(com)) +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + template = nullptr> + basic_value& operator=(T x) + { + table_format_info fmt; + if(this->is_table()) + { + fmt = this->as_table_fmt(); + } + this->cleanup(); + this->type_ = value_t::table; + this->region_ = region_type{}; +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + table_type t(std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end())); + assigner(this->table_, table_storage( + detail::storage(std::move(t)), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (user_defined) ========================================= {{{ + + template::value, std::nullptr_t> = nullptr> + basic_value(const T& ud) + : basic_value( + into>::template into_toml(ud)) + {} + template::value, std::nullptr_t> = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value( + into>::template into_toml(ud), + std::move(com)) + {} + template::value, std::nullptr_t> = nullptr> + basic_value& operator=(const T& ud) + { + *this = into>::template into_toml(ud); +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + return *this; + } + + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value(const T& ud): basic_value(ud.into_toml()) {} + + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value(ud.into_toml(), std::move(com)) + {} + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value& operator=(const T& ud) + { + *this = ud.into_toml(); +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + return *this; + } + + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value(const T& ud): basic_value(ud.template into_toml()) {} + + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value(ud.template into_toml(), std::move(com)) + {} + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value& operator=(const T& ud) + { + *this = ud.template into_toml(); +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + return *this; + } + // }}} + + // empty value with region info ======================================= {{{ + + // mainly for `null` extension + basic_value(detail::none_t, region_type reg) noexcept + : type_(value_t::empty), empty_('\0'), region_(std::move(reg)), comments_{} +#ifdef TOML11_ENABLE_ACCESS_CHECK + , accessed_{false} +#endif + {} + + // }}} + + // type checking ====================================================== {{{ + + template, value_type>::value, + std::nullptr_t> = nullptr> + bool is() const noexcept + { + return this->is(detail::type_to_enum::value); + } + bool is(value_t t) const noexcept + { + this->set_accessed(); + return t == this->type_; + } + + bool is_empty() const noexcept {return this->is(value_t::empty );} + bool is_boolean() const noexcept {return this->is(value_t::boolean );} + bool is_integer() const noexcept {return this->is(value_t::integer );} + bool is_floating() const noexcept {return this->is(value_t::floating );} + bool is_string() const noexcept {return this->is(value_t::string );} + bool is_offset_datetime() const noexcept {return this->is(value_t::offset_datetime);} + bool is_local_datetime() const noexcept {return this->is(value_t::local_datetime );} + bool is_local_date() const noexcept {return this->is(value_t::local_date );} + bool is_local_time() const noexcept {return this->is(value_t::local_time );} + bool is_array() const noexcept {return this->is(value_t::array );} + bool is_table() const noexcept {return this->is(value_t::table );} + + bool is_array_of_tables() const noexcept + { + this->set_accessed(); + if( ! this->is_array()) {return false;} + const auto& a = this->as_array(std::nothrow); // already checked. + + // when you define [[array.of.tables]], at least one empty table will be + // assigned. In case of array of inline tables, `array_of_tables = []`, + // there is no reason to consider this as an array of *tables*. + // So empty array is not an array-of-tables. + if(a.empty()) {return false;} + + // since toml v1.0.0 allows array of heterogeneous types, we need to + // check all the elements. if any of the elements is not a table, it + // is a heterogeneous array and cannot be expressed by `[[aot]]` form. + for(const auto& e : a) + { + if( ! e.is_table()) {return false;} + } + return true; + } + + value_t type() const noexcept + { + this->set_accessed(); + return type_; + } + + // }}} + + // as_xxx (noexcept) version ========================================== {{{ + + template + detail::enum_to_type_t> const& + as(const std::nothrow_t&) const noexcept + { + this->set_accessed(); + return detail::getter::get_nothrow(*this); + } + template + detail::enum_to_type_t>& + as(const std::nothrow_t&) noexcept + { + this->set_accessed(); + return detail::getter::get_nothrow(*this); + } + + boolean_type const& as_boolean (const std::nothrow_t&) const noexcept {this->set_accessed(); return this->boolean_.value;} + integer_type const& as_integer (const std::nothrow_t&) const noexcept {this->set_accessed(); return this->integer_.value;} + floating_type const& as_floating (const std::nothrow_t&) const noexcept {this->set_accessed(); return this->floating_.value;} + string_type const& as_string (const std::nothrow_t&) const noexcept {this->set_accessed(); return this->string_.value;} + offset_datetime_type const& as_offset_datetime(const std::nothrow_t&) const noexcept {this->set_accessed(); return this->offset_datetime_.value;} + local_datetime_type const& as_local_datetime (const std::nothrow_t&) const noexcept {this->set_accessed(); return this->local_datetime_.value;} + local_date_type const& as_local_date (const std::nothrow_t&) const noexcept {this->set_accessed(); return this->local_date_.value;} + local_time_type const& as_local_time (const std::nothrow_t&) const noexcept {this->set_accessed(); return this->local_time_.value;} + array_type const& as_array (const std::nothrow_t&) const noexcept {this->set_accessed(); return this->array_.value.get();} + table_type const& as_table (const std::nothrow_t&) const noexcept {this->set_accessed(); return this->table_.value.get();} + + boolean_type & as_boolean (const std::nothrow_t&) noexcept {this->set_accessed(); return this->boolean_.value;} + integer_type & as_integer (const std::nothrow_t&) noexcept {this->set_accessed(); return this->integer_.value;} + floating_type & as_floating (const std::nothrow_t&) noexcept {this->set_accessed(); return this->floating_.value;} + string_type & as_string (const std::nothrow_t&) noexcept {this->set_accessed(); return this->string_.value;} + offset_datetime_type& as_offset_datetime(const std::nothrow_t&) noexcept {this->set_accessed(); return this->offset_datetime_.value;} + local_datetime_type & as_local_datetime (const std::nothrow_t&) noexcept {this->set_accessed(); return this->local_datetime_.value;} + local_date_type & as_local_date (const std::nothrow_t&) noexcept {this->set_accessed(); return this->local_date_.value;} + local_time_type & as_local_time (const std::nothrow_t&) noexcept {this->set_accessed(); return this->local_time_.value;} + array_type & as_array (const std::nothrow_t&) noexcept {this->set_accessed(); return this->array_.value.get();} + table_type & as_table (const std::nothrow_t&) noexcept {this->set_accessed(); return this->table_.value.get();} + + // }}} + + // as_xxx (throw) ===================================================== {{{ + + template + detail::enum_to_type_t> const& as() const + { + return detail::getter::get(*this); + } + template + detail::enum_to_type_t>& as() + { + return detail::getter::get(*this); + } + + boolean_type const& as_boolean() const + { + if(this->type_ != value_t::boolean) + { + this->throw_bad_cast("toml::value::as_boolean()", value_t::boolean); + } + this->set_accessed(); + return this->boolean_.value; + } + integer_type const& as_integer() const + { + if(this->type_ != value_t::integer) + { + this->throw_bad_cast("toml::value::as_integer()", value_t::integer); + } + this->set_accessed(); + return this->integer_.value; + } + floating_type const& as_floating() const + { + if(this->type_ != value_t::floating) + { + this->throw_bad_cast("toml::value::as_floating()", value_t::floating); + } + this->set_accessed(); + return this->floating_.value; + } + string_type const& as_string() const + { + if(this->type_ != value_t::string) + { + this->throw_bad_cast("toml::value::as_string()", value_t::string); + } + this->set_accessed(); + return this->string_.value; + } + offset_datetime_type const& as_offset_datetime() const + { + if(this->type_ != value_t::offset_datetime) + { + this->throw_bad_cast("toml::value::as_offset_datetime()", value_t::offset_datetime); + } + this->set_accessed(); + return this->offset_datetime_.value; + } + local_datetime_type const& as_local_datetime() const + { + if(this->type_ != value_t::local_datetime) + { + this->throw_bad_cast("toml::value::as_local_datetime()", value_t::local_datetime); + } + this->set_accessed(); + return this->local_datetime_.value; + } + local_date_type const& as_local_date() const + { + if(this->type_ != value_t::local_date) + { + this->throw_bad_cast("toml::value::as_local_date()", value_t::local_date); + } + this->set_accessed(); + return this->local_date_.value; + } + local_time_type const& as_local_time() const + { + if(this->type_ != value_t::local_time) + { + this->throw_bad_cast("toml::value::as_local_time()", value_t::local_time); + } + this->set_accessed(); + return this->local_time_.value; + } + array_type const& as_array() const + { + if(this->type_ != value_t::array) + { + this->throw_bad_cast("toml::value::as_array()", value_t::array); + } + this->set_accessed(); + return this->array_.value.get(); + } + table_type const& as_table() const + { + if(this->type_ != value_t::table) + { + this->throw_bad_cast("toml::value::as_table()", value_t::table); + } + this->set_accessed(); + return this->table_.value.get(); + } + + // ------------------------------------------------------------------------ + // nonconst reference + + boolean_type& as_boolean() + { + if(this->type_ != value_t::boolean) + { + this->throw_bad_cast("toml::value::as_boolean()", value_t::boolean); + } + this->set_accessed(); + return this->boolean_.value; + } + integer_type& as_integer() + { + if(this->type_ != value_t::integer) + { + this->throw_bad_cast("toml::value::as_integer()", value_t::integer); + } + this->set_accessed(); + return this->integer_.value; + } + floating_type& as_floating() + { + if(this->type_ != value_t::floating) + { + this->throw_bad_cast("toml::value::as_floating()", value_t::floating); + } + this->set_accessed(); + return this->floating_.value; + } + string_type& as_string() + { + if(this->type_ != value_t::string) + { + this->throw_bad_cast("toml::value::as_string()", value_t::string); + } + this->set_accessed(); + return this->string_.value; + } + offset_datetime_type& as_offset_datetime() + { + if(this->type_ != value_t::offset_datetime) + { + this->throw_bad_cast("toml::value::as_offset_datetime()", value_t::offset_datetime); + } + this->set_accessed(); + return this->offset_datetime_.value; + } + local_datetime_type& as_local_datetime() + { + if(this->type_ != value_t::local_datetime) + { + this->throw_bad_cast("toml::value::as_local_datetime()", value_t::local_datetime); + } + this->set_accessed(); + return this->local_datetime_.value; + } + local_date_type& as_local_date() + { + if(this->type_ != value_t::local_date) + { + this->throw_bad_cast("toml::value::as_local_date()", value_t::local_date); + } + this->set_accessed(); + return this->local_date_.value; + } + local_time_type& as_local_time() + { + if(this->type_ != value_t::local_time) + { + this->throw_bad_cast("toml::value::as_local_time()", value_t::local_time); + } + this->set_accessed(); + return this->local_time_.value; + } + array_type& as_array() + { + if(this->type_ != value_t::array) + { + this->throw_bad_cast("toml::value::as_array()", value_t::array); + } + this->set_accessed(); + return this->array_.value.get(); + } + table_type& as_table() + { + if(this->type_ != value_t::table) + { + this->throw_bad_cast("toml::value::as_table()", value_t::table); + } + this->set_accessed(); + return this->table_.value.get(); + } + + // }}} + + // format accessors (noexcept) ======================================== {{{ + + template + detail::enum_to_fmt_type_t const& + as_fmt(const std::nothrow_t&) const noexcept + { + return detail::getter::get_fmt_nothrow(*this); + } + template + detail::enum_to_fmt_type_t& + as_fmt(const std::nothrow_t&) noexcept + { + return detail::getter::get_fmt_nothrow(*this); + } + + boolean_format_info & as_boolean_fmt (const std::nothrow_t&) noexcept {return this->boolean_.format;} + integer_format_info & as_integer_fmt (const std::nothrow_t&) noexcept {return this->integer_.format;} + floating_format_info & as_floating_fmt (const std::nothrow_t&) noexcept {return this->floating_.format;} + string_format_info & as_string_fmt (const std::nothrow_t&) noexcept {return this->string_.format;} + offset_datetime_format_info& as_offset_datetime_fmt(const std::nothrow_t&) noexcept {return this->offset_datetime_.format;} + local_datetime_format_info & as_local_datetime_fmt (const std::nothrow_t&) noexcept {return this->local_datetime_.format;} + local_date_format_info & as_local_date_fmt (const std::nothrow_t&) noexcept {return this->local_date_.format;} + local_time_format_info & as_local_time_fmt (const std::nothrow_t&) noexcept {return this->local_time_.format;} + array_format_info & as_array_fmt (const std::nothrow_t&) noexcept {return this->array_.format;} + table_format_info & as_table_fmt (const std::nothrow_t&) noexcept {return this->table_.format;} + + boolean_format_info const& as_boolean_fmt (const std::nothrow_t&) const noexcept {return this->boolean_.format;} + integer_format_info const& as_integer_fmt (const std::nothrow_t&) const noexcept {return this->integer_.format;} + floating_format_info const& as_floating_fmt (const std::nothrow_t&) const noexcept {return this->floating_.format;} + string_format_info const& as_string_fmt (const std::nothrow_t&) const noexcept {return this->string_.format;} + offset_datetime_format_info const& as_offset_datetime_fmt(const std::nothrow_t&) const noexcept {return this->offset_datetime_.format;} + local_datetime_format_info const& as_local_datetime_fmt (const std::nothrow_t&) const noexcept {return this->local_datetime_.format;} + local_date_format_info const& as_local_date_fmt (const std::nothrow_t&) const noexcept {return this->local_date_.format;} + local_time_format_info const& as_local_time_fmt (const std::nothrow_t&) const noexcept {return this->local_time_.format;} + array_format_info const& as_array_fmt (const std::nothrow_t&) const noexcept {return this->array_.format;} + table_format_info const& as_table_fmt (const std::nothrow_t&) const noexcept {return this->table_.format;} + + // }}} + + // format accessors (throw) =========================================== {{{ + + template + detail::enum_to_fmt_type_t const& as_fmt() const + { + return detail::getter::get_fmt(*this); + } + template + detail::enum_to_fmt_type_t& as_fmt() + { + return detail::getter::get_fmt(*this); + } + + boolean_format_info const& as_boolean_fmt() const + { + if(this->type_ != value_t::boolean) + { + this->throw_bad_cast("toml::value::as_boolean_fmt()", value_t::boolean); + } + return this->boolean_.format; + } + integer_format_info const& as_integer_fmt() const + { + if(this->type_ != value_t::integer) + { + this->throw_bad_cast("toml::value::as_integer_fmt()", value_t::integer); + } + return this->integer_.format; + } + floating_format_info const& as_floating_fmt() const + { + if(this->type_ != value_t::floating) + { + this->throw_bad_cast("toml::value::as_floating_fmt()", value_t::floating); + } + return this->floating_.format; + } + string_format_info const& as_string_fmt() const + { + if(this->type_ != value_t::string) + { + this->throw_bad_cast("toml::value::as_string_fmt()", value_t::string); + } + return this->string_.format; + } + offset_datetime_format_info const& as_offset_datetime_fmt() const + { + if(this->type_ != value_t::offset_datetime) + { + this->throw_bad_cast("toml::value::as_offset_datetime_fmt()", value_t::offset_datetime); + } + return this->offset_datetime_.format; + } + local_datetime_format_info const& as_local_datetime_fmt() const + { + if(this->type_ != value_t::local_datetime) + { + this->throw_bad_cast("toml::value::as_local_datetime_fmt()", value_t::local_datetime); + } + return this->local_datetime_.format; + } + local_date_format_info const& as_local_date_fmt() const + { + if(this->type_ != value_t::local_date) + { + this->throw_bad_cast("toml::value::as_local_date_fmt()", value_t::local_date); + } + return this->local_date_.format; + } + local_time_format_info const& as_local_time_fmt() const + { + if(this->type_ != value_t::local_time) + { + this->throw_bad_cast("toml::value::as_local_time_fmt()", value_t::local_time); + } + return this->local_time_.format; + } + array_format_info const& as_array_fmt() const + { + if(this->type_ != value_t::array) + { + this->throw_bad_cast("toml::value::as_array_fmt()", value_t::array); + } + return this->array_.format; + } + table_format_info const& as_table_fmt() const + { + if(this->type_ != value_t::table) + { + this->throw_bad_cast("toml::value::as_table_fmt()", value_t::table); + } + return this->table_.format; + } + + // ------------------------------------------------------------------------ + // nonconst reference + + boolean_format_info& as_boolean_fmt() + { + if(this->type_ != value_t::boolean) + { + this->throw_bad_cast("toml::value::as_boolean_fmt()", value_t::boolean); + } + return this->boolean_.format; + } + integer_format_info& as_integer_fmt() + { + if(this->type_ != value_t::integer) + { + this->throw_bad_cast("toml::value::as_integer_fmt()", value_t::integer); + } + return this->integer_.format; + } + floating_format_info& as_floating_fmt() + { + if(this->type_ != value_t::floating) + { + this->throw_bad_cast("toml::value::as_floating_fmt()", value_t::floating); + } + return this->floating_.format; + } + string_format_info& as_string_fmt() + { + if(this->type_ != value_t::string) + { + this->throw_bad_cast("toml::value::as_string_fmt()", value_t::string); + } + return this->string_.format; + } + offset_datetime_format_info& as_offset_datetime_fmt() + { + if(this->type_ != value_t::offset_datetime) + { + this->throw_bad_cast("toml::value::as_offset_datetime_fmt()", value_t::offset_datetime); + } + return this->offset_datetime_.format; + } + local_datetime_format_info& as_local_datetime_fmt() + { + if(this->type_ != value_t::local_datetime) + { + this->throw_bad_cast("toml::value::as_local_datetime_fmt()", value_t::local_datetime); + } + return this->local_datetime_.format; + } + local_date_format_info& as_local_date_fmt() + { + if(this->type_ != value_t::local_date) + { + this->throw_bad_cast("toml::value::as_local_date_fmt()", value_t::local_date); + } + return this->local_date_.format; + } + local_time_format_info& as_local_time_fmt() + { + if(this->type_ != value_t::local_time) + { + this->throw_bad_cast("toml::value::as_local_time_fmt()", value_t::local_time); + } + return this->local_time_.format; + } + array_format_info& as_array_fmt() + { + if(this->type_ != value_t::array) + { + this->throw_bad_cast("toml::value::as_array_fmt()", value_t::array); + } + return this->array_.format; + } + table_format_info& as_table_fmt() + { + if(this->type_ != value_t::table) + { + this->throw_bad_cast("toml::value::as_table_fmt()", value_t::table); + } + return this->table_.format; + } + // }}} + + // table accessors ==================================================== {{{ + + value_type& at(const key_type& k) + { + if(!this->is_table()) + { + this->throw_bad_cast("toml::value::at(key_type)", value_t::table); + } + auto& table = this->as_table(std::nothrow); + const auto found = table.find(k); + if(found == table.end()) + { + this->throw_key_not_found_error("toml::value::at", k); + } + assert(found->first == k); + return found->second; + } + value_type const& at(const key_type& k) const + { + if(!this->is_table()) + { + this->throw_bad_cast("toml::value::at(key_type)", value_t::table); + } + const auto& table = this->as_table(std::nothrow); + const auto found = table.find(k); + if(found == table.end()) + { + this->throw_key_not_found_error("toml::value::at", k); + } + assert(found->first == k); + return found->second; + } + value_type& operator[](const key_type& k) + { + if(this->is_empty()) + { + (*this) = table_type{}; + } + else if( ! this->is_table()) // initialized, but not a table + { + this->throw_bad_cast("toml::value::operator[](key_type)", value_t::table); + } + return (this->as_table(std::nothrow))[k]; + } + std::size_t count(const key_type& k) const + { + if(!this->is_table()) + { + this->throw_bad_cast("toml::value::count(key_type)", value_t::table); + } + return this->as_table(std::nothrow).count(k); + } + bool contains(const key_type& k) const + { + if(!this->is_table()) + { + this->throw_bad_cast("toml::value::contains(key_type)", value_t::table); + } + const auto& table = this->as_table(std::nothrow); + return table.find(k) != table.end(); + } + // }}} + + // array accessors ==================================================== {{{ + + value_type& at(const std::size_t idx) + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::at(idx)", value_t::array); + } + auto& ar = this->as_array(std::nothrow); + + if(ar.size() <= idx) + { + std::ostringstream oss; + oss << "actual length (" << ar.size() + << ") is shorter than the specified index (" << idx << ")."; + throw std::out_of_range(format_error( + "toml::value::at(idx): no element corresponding to the index", + this->location(), oss.str() + )); + } + return ar.at(idx); + } + value_type const& at(const std::size_t idx) const + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::at(idx)", value_t::array); + } + const auto& ar = this->as_array(std::nothrow); + + if(ar.size() <= idx) + { + std::ostringstream oss; + oss << "actual length (" << ar.size() + << ") is shorter than the specified index (" << idx << ")."; + + throw std::out_of_range(format_error( + "toml::value::at(idx): no element corresponding to the index", + this->location(), oss.str() + )); + } + return ar.at(idx); + } + + value_type& operator[](const std::size_t idx) noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + value_type const& operator[](const std::size_t idx) const noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + + void push_back(const value_type& x) + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::push_back(idx)", value_t::array); + } + this->as_array(std::nothrow).push_back(x); + return; + } + void push_back(value_type&& x) + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::push_back(idx)", value_t::array); + } + this->as_array(std::nothrow).push_back(std::move(x)); + return; + } + + template + value_type& emplace_back(Ts&& ... args) + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::emplace_back(idx)", value_t::array); + } + auto& ar = this->as_array(std::nothrow); + ar.emplace_back(std::forward(args) ...); + return ar.back(); + } + + std::size_t size() const + { + switch(this->type_) + { + case value_t::array: + { + return this->as_array(std::nothrow).size(); + } + case value_t::table: + { + return this->as_table(std::nothrow).size(); + } + case value_t::string: + { + return this->as_string(std::nothrow).size(); + } + default: + { + throw type_error(format_error( + "toml::value::size(): bad_cast to container types", + this->location(), + "the actual type is " + to_string(this->type_) + ), this->location()); + } + } + } + + // }}} + + source_location location() const + { + return source_location(this->region_); + } + + comment_type const& comments() const noexcept {return this->comments_;} + comment_type& comments() noexcept {return this->comments_;} + +#ifdef TOML11_ENABLE_ACCESS_CHECK + bool accessed() const {return this->accessed_.load();} +#endif + + private: + + // private helper functions =========================================== {{{ + + void set_accessed() const noexcept + { +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_.store(true); +#endif + return; + } + + void cleanup() noexcept + { + switch(this->type_) + { + case value_t::boolean : { boolean_ .~boolean_storage (); break; } + case value_t::integer : { integer_ .~integer_storage (); break; } + case value_t::floating : { floating_ .~floating_storage (); break; } + case value_t::string : { string_ .~string_storage (); break; } + case value_t::offset_datetime : { offset_datetime_.~offset_datetime_storage (); break; } + case value_t::local_datetime : { local_datetime_ .~local_datetime_storage (); break; } + case value_t::local_date : { local_date_ .~local_date_storage (); break; } + case value_t::local_time : { local_time_ .~local_time_storage (); break; } + case value_t::array : { array_ .~array_storage (); break; } + case value_t::table : { table_ .~table_storage (); break; } + default : { break; } + } +#ifdef TOML11_ENABLE_ACCESS_CHECK + this->accessed_ = false; +#endif + this->type_ = value_t::empty; + return; + } + + template + static void assigner(T& dst, U&& v) + { + const auto tmp = ::new(std::addressof(dst)) T(std::forward(v)); + assert(tmp == std::addressof(dst)); + (void)tmp; + } + + [[noreturn]] + void throw_bad_cast(const std::string& funcname, const value_t ty) const + { + throw type_error(format_error(detail::make_type_error(*this, funcname, ty)), + this->location()); + } + + [[noreturn]] + void throw_key_not_found_error(const std::string& funcname, const key_type& key) const + { + throw std::out_of_range(format_error( + detail::make_not_found_error(*this, funcname, key))); + } + + template + friend void detail::change_region_of_value(basic_value&, const basic_value&); + + template + friend class basic_value; + + +#ifdef TOML11_ENABLE_ACCESS_CHECK + template + friend void detail::unset_access_flag(basic_value&); +#endif + + // }}} + + private: + + using boolean_storage = detail::value_with_format; + using integer_storage = detail::value_with_format; + using floating_storage = detail::value_with_format; + using string_storage = detail::value_with_format; + using offset_datetime_storage = detail::value_with_format; + using local_datetime_storage = detail::value_with_format; + using local_date_storage = detail::value_with_format; + using local_time_storage = detail::value_with_format; + using array_storage = detail::value_with_format, array_format_info >; + using table_storage = detail::value_with_format, table_format_info >; + + private: + + value_t type_; + union + { + char empty_; // the smallest type + boolean_storage boolean_; + integer_storage integer_; + floating_storage floating_; + string_storage string_; + offset_datetime_storage offset_datetime_; + local_datetime_storage local_datetime_; + local_date_storage local_date_; + local_time_storage local_time_; + array_storage array_; + table_storage table_; + }; + region_type region_; + comment_type comments_; + +#ifdef TOML11_ENABLE_ACCESS_CHECK + mutable std::atomic accessed_; +#endif +}; + +template +bool operator==(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()) {return false;} + if(lhs.comments() != rhs.comments()) {return false;} + + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() == rhs.as_boolean(); + } + case value_t::integer : + { + return lhs.as_integer() == rhs.as_integer(); + } + case value_t::floating : + { + return lhs.as_floating() == rhs.as_floating(); + } + case value_t::string : + { + return lhs.as_string() == rhs.as_string(); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() == rhs.as_offset_datetime(); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() == rhs.as_local_datetime(); + } + case value_t::local_date: + { + return lhs.as_local_date() == rhs.as_local_date(); + } + case value_t::local_time: + { + return lhs.as_local_time() == rhs.as_local_time(); + } + case value_t::array : + { + return lhs.as_array() == rhs.as_array(); + } + case value_t::table : + { + return lhs.as_table() == rhs.as_table(); + } + case value_t::empty : {return true; } + default: {return false;} + } +} + +template +bool operator!=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs == rhs); +} + +template +cxx::enable_if_t::array_type>, + detail::is_comparable::table_type> + >::value, bool> +operator<(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()) + { + return (lhs.type() < rhs.type()); + } + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() < rhs.as_boolean() || + (lhs.as_boolean() == rhs.as_boolean() && + lhs.comments() < rhs.comments()); + } + case value_t::integer : + { + return lhs.as_integer() < rhs.as_integer() || + (lhs.as_integer() == rhs.as_integer() && + lhs.comments() < rhs.comments()); + } + case value_t::floating : + { + return lhs.as_floating() < rhs.as_floating() || + (lhs.as_floating() == rhs.as_floating() && + lhs.comments() < rhs.comments()); + } + case value_t::string : + { + return lhs.as_string() < rhs.as_string() || + (lhs.as_string() == rhs.as_string() && + lhs.comments() < rhs.comments()); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() < rhs.as_offset_datetime() || + (lhs.as_offset_datetime() == rhs.as_offset_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() < rhs.as_local_datetime() || + (lhs.as_local_datetime() == rhs.as_local_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_date: + { + return lhs.as_local_date() < rhs.as_local_date() || + (lhs.as_local_date() == rhs.as_local_date() && + lhs.comments() < rhs.comments()); + } + case value_t::local_time: + { + return lhs.as_local_time() < rhs.as_local_time() || + (lhs.as_local_time() == rhs.as_local_time() && + lhs.comments() < rhs.comments()); + } + case value_t::array : + { + return lhs.as_array() < rhs.as_array() || + (lhs.as_array() == rhs.as_array() && + lhs.comments() < rhs.comments()); + } + case value_t::table : + { + return lhs.as_table() < rhs.as_table() || + (lhs.as_table() == rhs.as_table() && + lhs.comments() < rhs.comments()); + } + case value_t::empty : + { + return lhs.comments() < rhs.comments(); + } + default: + { + return lhs.comments() < rhs.comments(); + } + } +} + +template +cxx::enable_if_t::array_type>, + detail::is_comparable::table_type> + >::value, bool> +operator<=(const basic_value& lhs, const basic_value& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +template +cxx::enable_if_t::array_type>, + detail::is_comparable::table_type> + >::value, bool> +operator>(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs <= rhs); +} +template +cxx::enable_if_t::array_type>, + detail::is_comparable::table_type> + >::value, bool> +operator>=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs < rhs); +} + +// error_info helper +namespace detail +{ +template +error_info make_error_info_rec(error_info e, + const basic_value& v, std::string msg, Ts&& ... tail) +{ + return make_error_info_rec(std::move(e), v.location(), std::move(msg), std::forward(tail)...); +} +} // detail + +template +error_info make_error_info( + std::string title, const basic_value& v, std::string msg, Ts&& ... tail) +{ + return make_error_info(std::move(title), + v.location(), std::move(msg), std::forward(tail)...); +} +template +std::string format_error(std::string title, + const basic_value& v, std::string msg, Ts&& ... tail) +{ + return format_error(std::move(title), + v.location(), std::move(msg), std::forward(tail)...); +} + +namespace detail +{ + +template +error_info make_type_error(const basic_value& v, const std::string& fname, const value_t ty) +{ + return make_error_info(fname + ": bad_cast to " + to_string(ty), + v.location(), "the actual type is " + to_string(v.type())); +} +template +error_info make_not_found_error(const basic_value& v, const std::string& fname, const typename basic_value::key_type& key) +{ + const auto loc = v.location(); + const std::string title = fname + ": key \"" + string_conv(key) + "\" not found"; + + std::vector> locs; + if( ! loc.is_ok()) + { + return error_info(title, locs); + } + + if(loc.first_line_number() == 1 && loc.first_column_number() == 1 && loc.length() == 1) + { + // The top-level table has its region at the 0th character of the file. + // That means that, in the case when a key is not found in the top-level + // table, the error message points to the first character. If the file has + // the first table at the first line, the error message would be like this. + // ```console + // [error] key "a" not found + // --> example.toml + // | + // 1 | [table] + // | ^------ in this table + // ``` + // It actually points to the top-level table at the first character, not + // `[table]`. But it is too confusing. To avoid the confusion, the error + // message should explicitly say "key not found in the top-level table". + locs.emplace_back(v.location(), "at the top-level table"); + } + else + { + locs.emplace_back(v.location(), "in this table"); + } + return error_info(title, locs); +} + +#define TOML11_DETAIL_GENERATE_COMPTIME_GETTER(ty) \ + template \ + struct getter \ + { \ + using value_type = basic_value; \ + using result_type = enum_to_type_t; \ + using format_type = enum_to_fmt_type_t; \ + \ + static result_type& get(value_type& v) \ + { \ + return v.as_ ## ty(); \ + } \ + static result_type const& get(const value_type& v) \ + { \ + return v.as_ ## ty(); \ + } \ + \ + static result_type& get_nothrow(value_type& v) noexcept \ + { \ + return v.as_ ## ty(std::nothrow); \ + } \ + static result_type const& get_nothrow(const value_type& v) noexcept \ + { \ + return v.as_ ## ty(std::nothrow); \ + } \ + \ + static format_type& get_fmt(value_type& v) \ + { \ + return v.as_ ## ty ## _fmt(); \ + } \ + static format_type const& get_fmt(const value_type& v) \ + { \ + return v.as_ ## ty ## _fmt(); \ + } \ + \ + static format_type& get_fmt_nothrow(value_type& v) noexcept \ + { \ + return v.as_ ## ty ## _fmt(std::nothrow); \ + } \ + static format_type const& get_fmt_nothrow(const value_type& v) noexcept \ + { \ + return v.as_ ## ty ## _fmt(std::nothrow); \ + } \ + }; + +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(boolean ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(integer ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(floating ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(string ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(offset_datetime) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(local_datetime ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(local_date ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(local_time ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(array ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(table ) + +#undef TOML11_DETAIL_GENERATE_COMPTIME_GETTER + +template +void change_region_of_value(basic_value& dst, const basic_value& src) +{ + dst.region_ = std::move(src.region_); + return; +} + +#ifdef TOML11_ENABLE_ACCESS_CHECK +template +void unset_access_flag(basic_value& v) +{ + v.accessed_.store(false); +} + +template +void unset_access_flag_recursively(basic_value& v) +{ + switch(v.type()) + { + case value_t::empty : { return unset_access_flag(v); } + case value_t::boolean : { return unset_access_flag(v); } + case value_t::integer : { return unset_access_flag(v); } + case value_t::floating : { return unset_access_flag(v); } + case value_t::string : { return unset_access_flag(v); } + case value_t::offset_datetime : { return unset_access_flag(v); } + case value_t::local_datetime : { return unset_access_flag(v); } + case value_t::local_date : { return unset_access_flag(v); } + case value_t::local_time : { return unset_access_flag(v); } + case value_t::array: + { + for(auto& elem : v.as_array()) + { + unset_access_flag_recursively(elem); + } + return unset_access_flag(v); + } + case value_t::table: + { + for(auto& kv : v.as_table()) + { + unset_access_flag_recursively(kv.second); + } + return unset_access_flag(v); + } + default: { return unset_access_flag(v); } + } +} +#endif + +} // namespace detail +} // namespace toml +#endif // TOML11_VALUE_HPP +#ifndef TOML11_VISIT_HPP +#define TOML11_VISIT_HPP + + +namespace toml +{ + +namespace detail +{ + +template +using visit_result_t = decltype(std::declval()(std::declval().as_boolean() ...)); + +template +struct front_binder +{ + template + auto operator()(Args&& ... args) -> decltype(std::declval()(std::declval(), std::forward(args)...)) + { + return func(std::move(front), std::forward(args)...); + } + F func; + T front; +}; + +template +front_binder, cxx::remove_cvref_t> +bind_front(F&& f, T&& t) +{ + return front_binder, cxx::remove_cvref_t>{ + std::forward(f), std::forward(t) + }; +} + +template +visit_result_t&, Args...> +visit_impl(Visitor&& visitor, const basic_value& v, Args&& ... args); + +template +visit_result_t&, Args...> +visit_impl(Visitor&& visitor, basic_value& v, Args&& ... args); + +template +visit_result_t, Args...> +visit_impl(Visitor&& visitor, basic_value&& v, Args&& ... args); + + +template +visit_result_t visit_impl(Visitor&& visitor) +{ + return visitor(); +} + +template +visit_result_t&, Args...> +visit_impl(Visitor&& visitor, basic_value& v, Args&& ... args) +{ + switch(v.type()) + { + case value_t::boolean : {return visit_impl(bind_front(visitor, std::ref(v.as_boolean ())), std::forward(args)...);} + case value_t::integer : {return visit_impl(bind_front(visitor, std::ref(v.as_integer ())), std::forward(args)...);} + case value_t::floating : {return visit_impl(bind_front(visitor, std::ref(v.as_floating ())), std::forward(args)...);} + case value_t::string : {return visit_impl(bind_front(visitor, std::ref(v.as_string ())), std::forward(args)...);} + case value_t::offset_datetime: {return visit_impl(bind_front(visitor, std::ref(v.as_offset_datetime())), std::forward(args)...);} + case value_t::local_datetime : {return visit_impl(bind_front(visitor, std::ref(v.as_local_datetime ())), std::forward(args)...);} + case value_t::local_date : {return visit_impl(bind_front(visitor, std::ref(v.as_local_date ())), std::forward(args)...);} + case value_t::local_time : {return visit_impl(bind_front(visitor, std::ref(v.as_local_time ())), std::forward(args)...);} + case value_t::array : {return visit_impl(bind_front(visitor, std::ref(v.as_array ())), std::forward(args)...);} + case value_t::table : {return visit_impl(bind_front(visitor, std::ref(v.as_table ())), std::forward(args)...);} + case value_t::empty : break; + default: break; + } + throw type_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid type.", v.location(), "here"), v.location()); +} + +template +visit_result_t&, Args...> +visit_impl(Visitor&& visitor, const basic_value& v, Args&& ... args) +{ + switch(v.type()) + { + case value_t::boolean : {return visit_impl(bind_front(visitor, std::cref(v.as_boolean ())), std::forward(args)...);} + case value_t::integer : {return visit_impl(bind_front(visitor, std::cref(v.as_integer ())), std::forward(args)...);} + case value_t::floating : {return visit_impl(bind_front(visitor, std::cref(v.as_floating ())), std::forward(args)...);} + case value_t::string : {return visit_impl(bind_front(visitor, std::cref(v.as_string ())), std::forward(args)...);} + case value_t::offset_datetime: {return visit_impl(bind_front(visitor, std::cref(v.as_offset_datetime())), std::forward(args)...);} + case value_t::local_datetime : {return visit_impl(bind_front(visitor, std::cref(v.as_local_datetime ())), std::forward(args)...);} + case value_t::local_date : {return visit_impl(bind_front(visitor, std::cref(v.as_local_date ())), std::forward(args)...);} + case value_t::local_time : {return visit_impl(bind_front(visitor, std::cref(v.as_local_time ())), std::forward(args)...);} + case value_t::array : {return visit_impl(bind_front(visitor, std::cref(v.as_array ())), std::forward(args)...);} + case value_t::table : {return visit_impl(bind_front(visitor, std::cref(v.as_table ())), std::forward(args)...);} + case value_t::empty : break; + default: break; + } + throw type_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid type.", v.location(), "here"), v.location()); +} + +template +visit_result_t, Args...> +visit_impl(Visitor&& visitor, basic_value&& v, Args&& ... args) +{ + switch(v.type()) + { + case value_t::boolean : {return visit_impl(bind_front(visitor, std::move(v.as_boolean ())), std::forward(args)...);} + case value_t::integer : {return visit_impl(bind_front(visitor, std::move(v.as_integer ())), std::forward(args)...);} + case value_t::floating : {return visit_impl(bind_front(visitor, std::move(v.as_floating ())), std::forward(args)...);} + case value_t::string : {return visit_impl(bind_front(visitor, std::move(v.as_string ())), std::forward(args)...);} + case value_t::offset_datetime: {return visit_impl(bind_front(visitor, std::move(v.as_offset_datetime())), std::forward(args)...);} + case value_t::local_datetime : {return visit_impl(bind_front(visitor, std::move(v.as_local_datetime ())), std::forward(args)...);} + case value_t::local_date : {return visit_impl(bind_front(visitor, std::move(v.as_local_date ())), std::forward(args)...);} + case value_t::local_time : {return visit_impl(bind_front(visitor, std::move(v.as_local_time ())), std::forward(args)...);} + case value_t::array : {return visit_impl(bind_front(visitor, std::move(v.as_array ())), std::forward(args)...);} + case value_t::table : {return visit_impl(bind_front(visitor, std::move(v.as_table ())), std::forward(args)...);} + case value_t::empty : break; + default: break; + } + throw type_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid type.", v.location(), "here"), v.location()); +} + +} // detail + +template +detail::visit_result_t +visit(Visitor&& visitor, Args&& ... args) +{ + return detail::visit_impl(std::forward(visitor), std::forward(args)...); +} + +} // toml +#endif // TOML11_VISIT_HPP +#ifndef TOML11_TYPES_HPP +#define TOML11_TYPES_HPP + + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace toml +{ + +// forward decl +template +class basic_value; + +// when you use a special integer type as toml::value::integer_type, parse must +// be able to read it. So, type_config has static member functions that read the +// integer_type as {dec, hex, oct, bin}-integer. But, in most cases, operator<< +// is enough. To make config easy, we provide the default read functions. +// +// Before this functions is called, syntax is checked and prefix(`0x` etc) and +// spacer(`_`) are removed. + +template +result +read_dec_int(const std::string& str, const source_location src) +{ + constexpr auto max_digits = std::numeric_limits::digits; + assert( ! str.empty()); + + T val{0}; + std::istringstream iss(str); + iss >> val; + if(iss.fail()) + { + return err(make_error_info("toml::parse_dec_integer: " + "too large integer: current max digits = 2^" + std::to_string(max_digits), + std::move(src), "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); +} + +template +result +read_hex_int(const std::string& str, const source_location src) +{ + constexpr auto max_digits = std::numeric_limits::digits; + assert( ! str.empty()); + + T val{0}; + std::istringstream iss(str); + iss >> std::hex >> val; + if(iss.fail()) + { + return err(make_error_info("toml::parse_hex_integer: " + "too large integer: current max value = 2^" + std::to_string(max_digits), + std::move(src), "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); +} + +template +result +read_oct_int(const std::string& str, const source_location src) +{ + constexpr auto max_digits = std::numeric_limits::digits; + assert( ! str.empty()); + + T val{0}; + std::istringstream iss(str); + iss >> std::oct >> val; + if(iss.fail()) + { + return err(make_error_info("toml::parse_oct_integer: " + "too large integer: current max value = 2^" + std::to_string(max_digits), + std::move(src), "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); +} + +template +result +read_bin_int(const std::string& str, const source_location src) +{ + constexpr auto is_bounded = std::numeric_limits::is_bounded; + constexpr auto max_digits = std::numeric_limits::digits; + const auto max_value = (std::numeric_limits::max)(); + + T val{0}; + T base{1}; + for(auto i = str.rbegin(); i != str.rend(); ++i) + { + const auto c = *i; + if(c == '1') + { + val += base; + // prevent `base` from overflow + if(is_bounded && max_value / 2 < base && std::next(i) != str.rend()) + { + base = 0; + } + else + { + base *= 2; + } + } + else + { + assert(c == '0'); + + if(is_bounded && max_value / 2 < base && std::next(i) != str.rend()) + { + base = 0; + } + else + { + base *= 2; + } + } + } + if(base == 0) + { + return err(make_error_info("toml::parse_bin_integer: " + "too large integer: current max value = 2^" + std::to_string(max_digits), + std::move(src), "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); +} + +template +result +read_int(const std::string& str, const source_location src, const std::uint8_t base) +{ + assert(base == 10 || base == 16 || base == 8 || base == 2); + switch(base) + { + case 2: { return read_bin_int(str, src); } + case 8: { return read_oct_int(str, src); } + case 16: { return read_hex_int(str, src); } + default: + { + assert(base == 10); + return read_dec_int(str, src); + } + } +} + +inline result +read_hex_float(const std::string& str, const source_location src, float val) +{ +#if defined(_MSC_VER) && ! defined(__clang__) + const auto res = ::sscanf_s(str.c_str(), "%a", std::addressof(val)); +#else + const auto res = std::sscanf(str.c_str(), "%a", std::addressof(val)); +#endif + if(res != 1) + { + return err(make_error_info("toml::parse_floating: " + "failed to read hexadecimal floating point value ", + std::move(src), "here")); + } + return ok(val); +} +inline result +read_hex_float(const std::string& str, const source_location src, double val) +{ +#if defined(_MSC_VER) && ! defined(__clang__) + const auto res = ::sscanf_s(str.c_str(), "%la", std::addressof(val)); +#else + const auto res = std::sscanf(str.c_str(), "%la", std::addressof(val)); +#endif + if(res != 1) + { + return err(make_error_info("toml::parse_floating: " + "failed to read hexadecimal floating point value ", + std::move(src), "here")); + } + return ok(val); +} +template +cxx::enable_if_t, double>>, + cxx::negation, float>> + >::value, result> +read_hex_float(const std::string&, const source_location src, T) +{ + return err(make_error_info("toml::parse_floating: failed to read " + "floating point value because of unknown type in type_config", + std::move(src), "here")); +} + +template +result +read_dec_float(const std::string& str, const source_location src) +{ + T val; + std::istringstream iss(str); + iss >> val; + if(iss.fail()) + { + return err(make_error_info("toml::parse_floating: " + "failed to read floating point value from stream", + std::move(src), "here")); + } + return ok(val); +} + +template +result +read_float(const std::string& str, const source_location src, const bool is_hex) +{ + if(is_hex) + { + return read_hex_float(str, src, T{}); + } + else + { + return read_dec_float(str, src); + } +} + +struct type_config +{ + using comment_type = preserve_comments; + + using boolean_type = bool; + using integer_type = std::int64_t; + using floating_type = double; + using string_type = std::string; + + template + using array_type = std::vector; + template + using table_type = std::unordered_map; + + static result + parse_int(const std::string& str, const source_location src, const std::uint8_t base) + { + return read_int(str, src, base); + } + static result + parse_float(const std::string& str, const source_location src, const bool is_hex) + { + return read_float(str, src, is_hex); + } +}; + +using value = basic_value; +using table = typename value::table_type; +using array = typename value::array_type; + +struct ordered_type_config +{ + using comment_type = preserve_comments; + + using boolean_type = bool; + using integer_type = std::int64_t; + using floating_type = double; + using string_type = std::string; + + template + using array_type = std::vector; + template + using table_type = ordered_map; + + static result + parse_int(const std::string& str, const source_location src, const std::uint8_t base) + { + return read_int(str, src, base); + } + static result + parse_float(const std::string& str, const source_location src, const bool is_hex) + { + return read_float(str, src, is_hex); + } +}; + +using ordered_value = basic_value; +using ordered_table = typename ordered_value::table_type; +using ordered_array = typename ordered_value::array_type; + +// ---------------------------------------------------------------------------- +// meta functions for internal use + +namespace detail +{ + +// ---------------------------------------------------------------------------- +// check if type T has all the needed member types + +template +struct has_comment_type: std::false_type{}; +template +struct has_comment_type>: std::true_type{}; + +template +struct has_integer_type: std::false_type{}; +template +struct has_integer_type>: std::true_type{}; + +template +struct has_floating_type: std::false_type{}; +template +struct has_floating_type>: std::true_type{}; + +template +struct has_string_type: std::false_type{}; +template +struct has_string_type>: std::true_type{}; + +template +struct has_array_type: std::false_type{}; +template +struct has_array_type>>: std::true_type{}; + +template +struct has_table_type: std::false_type{}; +template +struct has_table_type>>: std::true_type{}; + +template +struct has_parse_int: std::false_type{}; +template +struct has_parse_int().parse_int( + std::declval(), + std::declval<::toml::source_location const&>(), + std::declval() + ))>>: std::true_type{}; + +template +struct has_parse_float: std::false_type{}; +template +struct has_parse_float().parse_float( + std::declval(), + std::declval<::toml::source_location const&>(), + std::declval() + ))>>: std::true_type{}; + +template +using is_type_config = cxx::conjunction< + has_comment_type, + has_integer_type, + has_floating_type, + has_string_type, + has_array_type, + has_table_type, + has_parse_int, + has_parse_float + >; + +} // namespace detail +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +extern template class basic_value; +extern template class basic_value; +} // toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_TYPES_HPP +#ifndef TOML11_GET_HPP +#define TOML11_GET_HPP + +#include + + +#if defined(TOML11_HAS_STRING_VIEW) +#include +#endif // string_view + +namespace toml +{ + +// ============================================================================ +// T is toml::value; identity transformation. + +template +cxx::enable_if_t>::value, T>& +get(basic_value& v) +{ + return v; +} + +template +cxx::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + return v; +} + +template +cxx::enable_if_t>::value, T> +get(basic_value&& v) +{ + return basic_value(std::move(v)); +} + +// ============================================================================ +// exact toml::* type + +template +cxx::enable_if_t>::value, T> & +get(basic_value& v) +{ + constexpr auto ty = detail::type_to_enum>::value; + return detail::getter::get(v); +} + +template +cxx::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + constexpr auto ty = detail::type_to_enum>::value; + return detail::getter::get(v); +} + +template +cxx::enable_if_t>::value, T> +get(basic_value&& v) +{ + constexpr auto ty = detail::type_to_enum>::value; + return detail::getter::get(std::move(v)); +} + +// ============================================================================ +// T is toml::basic_value + +template +cxx::enable_if_t, + cxx::negation>> + >::value, T> +get(basic_value v) +{ + return T(std::move(v)); +} + +// ============================================================================ +// integer convertible from toml::value::integer_type + +template +cxx::enable_if_t, + cxx::negation>, + detail::is_not_toml_type>, + cxx::negation>, + cxx::negation> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_integer()); +} + +// ============================================================================ +// floating point convertible from toml::value::floating_type + +template +cxx::enable_if_t, + detail::is_not_toml_type>, + cxx::negation>, + cxx::negation> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_floating()); +} + +// ============================================================================ +// std::string with different char/trait/allocator + +template +cxx::enable_if_t>, + detail::is_1byte_std_basic_string + >::value, T> +get(const basic_value& v) +{ + return detail::string_conv>(v.as_string()); +} + +// ============================================================================ +// std::string_view + +#if defined(TOML11_HAS_STRING_VIEW) + +template +cxx::enable_if_t::string_type>::value, T> +get(const basic_value& v) +{ + return T(v.as_string()); +} + +#endif // string_view + +// ============================================================================ +// std::chrono::duration from toml::local_time + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + return std::chrono::duration_cast( + std::chrono::nanoseconds(v.as_local_time())); +} + +// ============================================================================ +// std::chrono::system_clock::time_point from toml::datetime variants + +template +cxx::enable_if_t< + std::is_same::value, T> +get(const basic_value& v) +{ + switch(v.type()) + { + case value_t::local_date: + { + return std::chrono::system_clock::time_point(v.as_local_date()); + } + case value_t::local_datetime: + { + return std::chrono::system_clock::time_point(v.as_local_datetime()); + } + case value_t::offset_datetime: + { + return std::chrono::system_clock::time_point(v.as_offset_datetime()); + } + default: + { + const auto loc = v.location(); + throw type_error(format_error("toml::get: " + "bad_cast to std::chrono::system_clock::time_point", loc, + "the actual type is " + to_string(v.type())), loc); + } + } +} + +// ============================================================================ +// forward declaration to use this recursively. ignore this and go ahead. + +// array-like (w/ push_back) +template +cxx::enable_if_t, // T is a container + detail::has_push_back_method, // .push_back() works + detail::is_not_toml_type>, // but not toml::array + cxx::negation>, // but not std::basic_string +#if defined(TOML11_HAS_STRING_VIEW) + cxx::negation>, // but not std::basic_string_view +#endif + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value&); + +// std::array +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// std::forward_list +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// std::pair +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// std::tuple +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// std::map (key is convertible from toml::value::key_type) +template +cxx::enable_if_t, // T is map + detail::is_not_toml_type>, // but not toml::table + std::is_convertible::key_type, + typename T::key_type>, // keys are convertible + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v); + +// std::map (key is not convertible from toml::value::key_type, but +// is a std::basic_string) +template +cxx::enable_if_t, // T is map + detail::is_not_toml_type>, // but not toml::table + cxx::negation::key_type, + typename T::key_type>>, // keys are NOT convertible + detail::is_1byte_std_basic_string, // is std::basic_string + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v); + +// toml::from::from_toml(v) +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// has T.from_toml(v) but no from +template +cxx::enable_if_t, // has T.from_toml() + cxx::negation>, // no toml::from + std::is_default_constructible // T{} works + >::value, T> +get(const basic_value&); + +// T(const toml::value&) and T is not toml::basic_value, +// and it does not have `from` nor `from_toml`. +template +cxx::enable_if_t&>, // has T(const basic_value&) + cxx::negation>, // but not basic_value itself + cxx::negation>, // no .from_toml() + cxx::negation> // no toml::from + >::value, T> +get(const basic_value&); + +// ============================================================================ +// array-like types; most likely STL container, like std::vector, etc. + +template +cxx::enable_if_t, // T is a container + detail::has_push_back_method, // .push_back() works + detail::is_not_toml_type>, // but not toml::array + cxx::negation>, // but not std::basic_string +#if defined(TOML11_HAS_STRING_VIEW) + cxx::negation>, // but not std::basic_string_view +#endif + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& a = v.as_array(); + + T container; + detail::try_reserve(container, a.size()); // if T has .reserve(), call it + + for(const auto& elem : a) + { + container.push_back(get(elem)); + } + return container; +} + +// ============================================================================ +// std::array + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& a = v.as_array(); + + T container; + if(a.size() != container.size()) + { + const auto loc = v.location(); + throw std::out_of_range(format_error("toml::get: while converting to an array: " + " array size is " + std::to_string(container.size()) + + " but there are " + std::to_string(a.size()) + " elements in toml array.", + loc, "here")); + } + for(std::size_t i=0; i(a.at(i)); + } + return container; +} + +// ============================================================================ +// std::forward_list + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + + T container; + for(const auto& elem : v.as_array()) + { + container.push_front(get(elem)); + } + container.reverse(); + return container; +} + +// ============================================================================ +// std::pair + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + using first_type = typename T::first_type; + using second_type = typename T::second_type; + + const auto& ar = v.as_array(); + if(ar.size() != 2) + { + const auto loc = v.location(); + throw std::out_of_range(format_error("toml::get: while converting std::pair: " + " but there are " + std::to_string(ar.size()) + " > 2 elements in toml array.", + loc, "here")); + } + return std::make_pair(::toml::get(ar.at(0)), + ::toml::get(ar.at(1))); +} + +// ============================================================================ +// std::tuple. + +namespace detail +{ +template +T get_tuple_impl(const Array& a, cxx::index_sequence) +{ + return std::make_tuple( + ::toml::get::type>(a.at(I))...); +} +} // detail + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + const auto& ar = v.as_array(); + if(ar.size() != std::tuple_size::value) + { + const auto loc = v.location(); + throw std::out_of_range(format_error("toml::get: while converting std::tuple: " + " there are " + std::to_string(ar.size()) + " > " + + std::to_string(std::tuple_size::value) + " elements in toml array.", + loc, "here")); + } + return detail::get_tuple_impl(ar, + cxx::make_index_sequence::value>{}); +} + +// ============================================================================ +// map-like types; most likely STL map, like std::map or std::unordered_map. + +// key is convertible from toml::value::key_type +template +cxx::enable_if_t, // T is map + detail::is_not_toml_type>, // but not toml::table + std::is_convertible::key_type, + typename T::key_type>, // keys are convertible + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v) +{ + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + static_assert( + std::is_convertible::key_type, key_type>::value, + "toml::get only supports map type of which key_type is " + "convertible from toml::basic_value::key_type."); + + T m; + for(const auto& kv : v.as_table()) + { + m.emplace(key_type(kv.first), get(kv.second)); + } + return m; +} + +// key is NOT convertible from toml::value::key_type but std::basic_string +template +cxx::enable_if_t, // T is map + detail::is_not_toml_type>, // but not toml::table + cxx::negation::key_type, + typename T::key_type>>, // keys are NOT convertible + detail::is_1byte_std_basic_string, // is std::basic_string + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v) +{ + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + + T m; + for(const auto& kv : v.as_table()) + { + m.emplace(detail::string_conv(kv.first), get(kv.second)); + } + return m; +} + +// ============================================================================ +// user-defined, but convertible types. + +// toml::from +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + return ::toml::from::from_toml(v); +} + +// has T.from_toml(v) but no from +template +cxx::enable_if_t, // has T.from_toml() + cxx::negation>, // no toml::from + std::is_default_constructible // T{} works + >::value, T> +get(const basic_value& v) +{ + T ud; + ud.from_toml(v); + return ud; +} + +// T(const toml::value&) and T is not toml::basic_value, +// and it does not have `from` nor `from_toml`. +template +cxx::enable_if_t&>, // has T(const basic_value&) + cxx::negation>, // but not basic_value itself + cxx::negation>, // no .from_toml() + cxx::negation> // no toml::from + >::value, T> +get(const basic_value& v) +{ + return T(v); +} + +// ============================================================================ +// get_or(value, fallback) + +template +cxx::enable_if_t::value, basic_value> const& +get_or(const basic_value& v, const basic_value&) +{ + return v; +} + +template +cxx::enable_if_t::value, basic_value>& +get_or(basic_value& v, basic_value&) +{ + return v; +} + +template +cxx::enable_if_t::value, basic_value> +get_or(basic_value&& v, basic_value&&) +{ + return v; +} + +// ---------------------------------------------------------------------------- +// specialization for the exact toml types (return type becomes lvalue ref) + +template +cxx::enable_if_t< + detail::is_exact_toml_type>::value, T> const& +get_or(const basic_value& v, const T& opt) noexcept +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template +cxx::enable_if_t>, + detail::is_exact_toml_type> + >::value, T>& +get_or(basic_value& v, T& opt) noexcept +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template +cxx::enable_if_t, + basic_value>::value, cxx::remove_cvref_t> +get_or(basic_value&& v, T&& opt) noexcept +{ + try + { + return get>(std::move(v)); + } + catch(...) + { + return cxx::remove_cvref_t(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// specialization for string literal + +// template +// typename basic_value::string_type +// get_or(const basic_value& v, +// const typename basic_value::string_type::value_type (&opt)[N]) +// { +// try +// { +// return v.as_string(); +// } +// catch(...) +// { +// return typename basic_value::string_type(opt); +// } +// } +// +// The above only matches to the literal, like `get_or(v, "foo");` but not +// ```cpp +// const auto opt = "foo"; +// const auto str = get_or(v, opt); +// ``` +// . And the latter causes an error. +// To match to both `"foo"` and `const auto opt = "foo"`, we take a pointer to +// a character here. + +template +typename basic_value::string_type +get_or(const basic_value& v, + const typename basic_value::string_type::value_type* opt) +{ + try + { + return v.as_string(); + } + catch(...) + { + return typename basic_value::string_type(opt); + } +} + +// ---------------------------------------------------------------------------- +// others (require type conversion and return type cannot be lvalue reference) + +template +cxx::enable_if_t>, + cxx::negation>>, + cxx::negation, typename basic_value::string_type::value_type const*>> + >::value, cxx::remove_cvref_t> +get_or(const basic_value& v, T&& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return cxx::remove_cvref_t(std::forward(opt)); + } +} + +} // toml +#endif // TOML11_GET_HPP +#ifndef TOML11_FIND_HPP +#define TOML11_FIND_HPP + +#include + + +#if defined(TOML11_HAS_STRING_VIEW) +#include +#endif + +namespace toml +{ + +// ---------------------------------------------------------------------------- +// find(value, key); + +template +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const typename basic_value::key_type& ky) +{ + return ::toml::get(v.at(ky)); +} + +template +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const typename basic_value::key_type& ky) +{ + return ::toml::get(v.at(ky)); +} + +template +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const typename basic_value::key_type& ky) +{ + return ::toml::get(std::move(v.at(ky))); +} + +// ---------------------------------------------------------------------------- +// find(value, idx) + +template +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const std::size_t idx) +{ + return ::toml::get(v.at(idx)); +} +template +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const std::size_t idx) +{ + return ::toml::get(v.at(idx)); +} +template +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const std::size_t idx) +{ + return ::toml::get(std::move(v.at(idx))); +} + +// ---------------------------------------------------------------------------- +// find(value, key/idx), w/o conversion + +template +cxx::enable_if_t::value, basic_value>& +find(basic_value& v, const typename basic_value::key_type& ky) +{ + return v.at(ky); +} +template +cxx::enable_if_t::value, basic_value> const& +find(basic_value const& v, const typename basic_value::key_type& ky) +{ + return v.at(ky); +} +template +cxx::enable_if_t::value, basic_value> +find(basic_value&& v, const typename basic_value::key_type& ky) +{ + return basic_value(std::move(v.at(ky))); +} + +template +cxx::enable_if_t::value, basic_value>& +find(basic_value& v, const std::size_t idx) +{ + return v.at(idx); +} +template +cxx::enable_if_t::value, basic_value> const& +find(basic_value const& v, const std::size_t idx) +{ + return v.at(idx); +} +template +cxx::enable_if_t::value, basic_value> +find(basic_value&& v, const std::size_t idx) +{ + return basic_value(std::move(v.at(idx))); +} + +// -------------------------------------------------------------------------- +// find> + +#if defined(TOML11_HAS_OPTIONAL) +template +cxx::enable_if_t::value, T> +find(const basic_value& v, const typename basic_value::key_type& ky) +{ + if(v.contains(ky)) + { + return ::toml::get(v.at(ky)); + } + else + { + return std::nullopt; + } +} + +template +cxx::enable_if_t::value, T> +find(basic_value& v, const typename basic_value::key_type& ky) +{ + if(v.contains(ky)) + { + return ::toml::get(v.at(ky)); + } + else + { + return std::nullopt; + } +} + +template +cxx::enable_if_t::value, T> +find(basic_value&& v, const typename basic_value::key_type& ky) +{ + if(v.contains(ky)) + { + return ::toml::get(std::move(v.at(ky))); + } + else + { + return std::nullopt; + } +} + +template +cxx::enable_if_t::value && std::is_integral::value, T> +find(const basic_value& v, const K& k) +{ + if(static_cast(k) < v.size()) + { + return ::toml::get(v.at(static_cast(k))); + } + else + { + return std::nullopt; + } +} + +template +cxx::enable_if_t::value && std::is_integral::value, T> +find(basic_value& v, const K& k) +{ + if(static_cast(k) < v.size()) + { + return ::toml::get(v.at(static_cast(k))); + } + else + { + return std::nullopt; + } +} + +template +cxx::enable_if_t::value && std::is_integral::value, T> +find(basic_value&& v, const K& k) +{ + if(static_cast(k) < v.size()) + { + return ::toml::get(std::move(v.at(static_cast(k)))); + } + else + { + return std::nullopt; + } +} +#endif // optional + +// -------------------------------------------------------------------------- +// toml::find(toml::value, toml::key, Ts&& ... keys) + +namespace detail +{ + +// It suppresses warnings by -Wsign-conversion when we pass integer literal +// to toml::find. integer literal `0` is deduced as an int, and will be +// converted to std::size_t. This causes sign-conversion. + +template +std::size_t key_cast(const std::size_t& v) noexcept +{ + return v; +} +template +cxx::enable_if_t>::value, std::size_t> +key_cast(const T& v) noexcept +{ + return static_cast(v); +} + +// for string-like (string, string literal, string_view) + +template +typename basic_value::key_type const& +key_cast(const typename basic_value::key_type& v) noexcept +{ + return v; +} +template +typename basic_value::key_type +key_cast(const typename basic_value::key_type::value_type* v) +{ + return typename basic_value::key_type(v); +} +#if defined(TOML11_HAS_STRING_VIEW) +template +typename basic_value::key_type +key_cast(const std::string_view v) +{ + return typename basic_value::key_type(v); +} +#endif // string_view + +} // detail + +// ---------------------------------------------------------------------------- +// find(v, keys...) + +template +cxx::enable_if_t::value, basic_value> const& +find(const basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); +} +template +cxx::enable_if_t::value, basic_value>& +find(basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); +} +template +cxx::enable_if_t::value, basic_value> +find(basic_value&& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(std::move(v.at(detail::key_cast(k1))), detail::key_cast(k2), ks...); +} + +// ---------------------------------------------------------------------------- +// find(v, keys...) + +template +decltype(::toml::get(std::declval&>())) +find(const basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); +} +template +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); +} +template +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(std::move(v.at(detail::key_cast(k1))), detail::key_cast(k2), ks...); +} + +#if defined(TOML11_HAS_OPTIONAL) +template +cxx::enable_if_t::value, T> +find(const basic_value& v, const typename basic_value::key_type& k1, const K2& k2, const Ks& ... ks) +{ + if(v.contains(k1)) + { + return find(v.at(k1), detail::key_cast(k2), ks...); + } + else + { + return std::nullopt; + } +} +template +cxx::enable_if_t::value, T> +find(basic_value& v, const typename basic_value::key_type& k1, const K2& k2, const Ks& ... ks) +{ + if(v.contains(k1)) + { + return find(v.at(k1), detail::key_cast(k2), ks...); + } + else + { + return std::nullopt; + } +} +template +cxx::enable_if_t::value, T> +find(basic_value&& v, const typename basic_value::key_type& k1, const K2& k2, const Ks& ... ks) +{ + if(v.contains(k1)) + { + return find(v.at(k1), detail::key_cast(k2), ks...); + } + else + { + return std::nullopt; + } +} + +template +cxx::enable_if_t::value && std::is_integral::value, T> +find(const basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + if(static_cast(k1) < v.size()) + { + return find(v.at(static_cast(k1)), detail::key_cast(k2), ks...); + } + else + { + return std::nullopt; + } +} +template +cxx::enable_if_t::value && std::is_integral::value, T> +find(basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + if(static_cast(k1) < v.size()) + { + return find(v.at(static_cast(k1)), detail::key_cast(k2), ks...); + } + else + { + return std::nullopt; + } +} +template +cxx::enable_if_t::value && std::is_integral::value, T> +find(basic_value&& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + if(static_cast(k1) < v.size()) + { + return find(v.at(static_cast(k1)), detail::key_cast(k2), ks...); + } + else + { + return std::nullopt; + } +} +#endif // optional + +// =========================================================================== +// find_or(value, key, fallback) + +// --------------------------------------------------------------------------- +// find_or(v, key, other_v) + +template +cxx::enable_if_t::value, basic_value>& +find_or(basic_value& v, const K& k, basic_value& opt) noexcept +{ + try + { + return ::toml::find(v, detail::key_cast(k)); + } + catch(...) + { + return opt; + } +} +template +cxx::enable_if_t::value, basic_value> const& +find_or(const basic_value& v, const K& k, const basic_value& opt) noexcept +{ + try + { + return ::toml::find(v, detail::key_cast(k)); + } + catch(...) + { + return opt; + } +} +template +cxx::enable_if_t::value, basic_value> +find_or(basic_value&& v, const K& k, basic_value&& opt) noexcept +{ + try + { + return ::toml::find(v, detail::key_cast(k)); + } + catch(...) + { + return opt; + } +} + +// --------------------------------------------------------------------------- +// toml types (return type can be a reference) + +template +cxx::enable_if_t>::value, + cxx::remove_cvref_t const&> +find_or(const basic_value& v, const K& k, const T& opt) +{ + try + { + return ::toml::get(v.at(detail::key_cast(k))); + } + catch(...) + { + return opt; + } +} + +template +cxx::enable_if_t>, + detail::is_exact_toml_type> + >::value, cxx::remove_cvref_t&> +find_or(basic_value& v, const K& k, T& opt) +{ + try + { + return ::toml::get(v.at(detail::key_cast(k))); + } + catch(...) + { + return opt; + } +} + +template +cxx::enable_if_t>::value, + cxx::remove_cvref_t> +find_or(basic_value&& v, const K& k, T opt) +{ + try + { + return ::toml::get(std::move(v.at(detail::key_cast(k)))); + } + catch(...) + { + return T(std::move(opt)); + } +} + +// --------------------------------------------------------------------------- +// string literal (deduced as std::string) + +// XXX to avoid confusion when T is explicitly specified in find_or(), +// we restrict the string type as std::string. +template +cxx::enable_if_t::value, std::string> +find_or(const basic_value& v, const K& k, const char* opt) +{ + try + { + return ::toml::get(v.at(detail::key_cast(k))); + } + catch(...) + { + return std::string(opt); + } +} + +// --------------------------------------------------------------------------- +// other types (requires type conversion and return type cannot be a reference) + +template +cxx::enable_if_t>>, + detail::is_not_toml_type, basic_value>, + cxx::negation, + const typename basic_value::string_type::value_type*>> + >::value, cxx::remove_cvref_t> +find_or(const basic_value& v, const K& ky, T opt) +{ + try + { + return ::toml::get>(v.at(detail::key_cast(ky))); + } + catch(...) + { + return cxx::remove_cvref_t(std::move(opt)); + } +} + +// ---------------------------------------------------------------------------- +// recursive + +namespace detail +{ + +template +auto last_one(Ts&&... args) + -> decltype(std::get(std::forward_as_tuple(std::forward(args)...))) +{ + return std::get(std::forward_as_tuple(std::forward(args)...)); +} + +} // detail + +template +auto find_or(Value&& v, const K1& k1, const K2& k2, K3&& k3, Ks&& ... keys) noexcept + -> cxx::enable_if_t< + detail::is_basic_value>::value, + decltype(find_or(v, k2, std::forward(k3), std::forward(keys)...)) + > +{ + try + { + return find_or(v.at(k1), k2, std::forward(k3), std::forward(keys)...); + } + catch(...) + { + return detail::last_one(k3, keys...); + } +} + +template +T find_or(const basic_value& v, const K1& k1, const K2& k2, const K3& k3, const Ks& ... keys) noexcept +{ + try + { + return find_or(v.at(k1), k2, k3, keys...); + } + catch(...) + { + return static_cast(detail::last_one(k3, keys...)); + } +} + +// =========================================================================== +// find_or_default(value, key) + +template +cxx::enable_if_t::value, T> +find_or_default(const basic_value& v, K&& k) noexcept(std::is_nothrow_default_constructible::value) +{ + try + { + return ::toml::get(v.at(detail::key_cast(std::forward(k)))); + } + catch(...) + { + return T(); + } +} + +template +cxx::enable_if_t::value, T> +find_or_default(const basic_value& v, K1&& k1, Ks&& ... keys) noexcept(std::is_nothrow_default_constructible::value) +{ + try + { + return find_or_default(v.at(std::forward(k1)), std::forward(keys)...); + } + catch(...) + { + return T(); + } +} + +} // toml +#endif // TOML11_FIND_HPP +#ifndef TOML11_CONVERSION_HPP +#define TOML11_CONVERSION_HPP + + +#if defined(TOML11_HAS_OPTIONAL) + +#include + +namespace toml +{ +namespace detail +{ + +template +inline constexpr bool is_optional_v = false; + +template +inline constexpr bool is_optional_v> = true; + +template +void find_member_variable_from_value(T& obj, const basic_value& v, const char* var_name) +{ + if constexpr(is_optional_v) + { + if(v.contains(var_name)) + { + obj = toml::find(v, var_name); + } + else + { + obj = std::nullopt; + } + } + else + { + obj = toml::find(v, var_name); + } +} + +template +void assign_member_variable_to_value(const T& obj, basic_value& v, const char* var_name) +{ + if constexpr(is_optional_v) + { + if(obj.has_value()) + { + v[var_name] = obj.value(); + } + } + else + { + v[var_name] = obj; + } +} + +} // detail +} // toml + +#else + +namespace toml +{ +namespace detail +{ + +template +void find_member_variable_from_value(T& obj, const basic_value& v, const char* var_name) +{ + obj = toml::find(v, var_name); +} + +template +void assign_member_variable_to_value(const T& obj, basic_value& v, const char* var_name) +{ + v[var_name] = obj; +} + +} // detail +} // toml + +#endif // optional + +// use it in the following way. +// ```cpp +// namespace foo +// { +// struct Foo +// { +// std::string s; +// double d; +// int i; +// }; +// } // foo +// +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i) +// ``` +// +// And then you can use `toml::get(v)` and `toml::find(file, "foo");` +// + +#define TOML11_STRINGIZE_AUX(x) #x +#define TOML11_STRINGIZE(x) TOML11_STRINGIZE_AUX(x) + +#define TOML11_CONCATENATE_AUX(x, y) x##y +#define TOML11_CONCATENATE(x, y) TOML11_CONCATENATE_AUX(x, y) + +// ============================================================================ +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE + +#ifndef TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +// ---------------------------------------------------------------------------- +// TOML11_ARGS_SIZE + +#define TOML11_INDEX_RSEQ() \ + 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, \ + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +#define TOML11_ARGS_SIZE_IMPL(\ + ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, ARG9, ARG10, \ + ARG11, ARG12, ARG13, ARG14, ARG15, ARG16, ARG17, ARG18, ARG19, ARG20, \ + ARG21, ARG22, ARG23, ARG24, ARG25, ARG26, ARG27, ARG28, ARG29, ARG30, \ + ARG31, ARG32, N, ...) N +#define TOML11_ARGS_SIZE_AUX(...) TOML11_ARGS_SIZE_IMPL(__VA_ARGS__) +#define TOML11_ARGS_SIZE(...) TOML11_ARGS_SIZE_AUX(__VA_ARGS__, TOML11_INDEX_RSEQ()) + +// ---------------------------------------------------------------------------- +// TOML11_FOR_EACH_VA_ARGS + +#define TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, ARG1 ) FUNCTOR(ARG1) +#define TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_32(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, __VA_ARGS__) + +#define TOML11_FOR_EACH_VA_ARGS(FUNCTOR, ...)\ + TOML11_CONCATENATE(TOML11_FOR_EACH_VA_ARGS_AUX_, TOML11_ARGS_SIZE(__VA_ARGS__))(FUNCTOR, __VA_ARGS__) + + +#define TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE(VAR_NAME)\ + toml::detail::find_member_variable_from_value(obj.VAR_NAME, v, TOML11_STRINGIZE(VAR_NAME)); + +#define TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE(VAR_NAME)\ + toml::detail::assign_member_variable_to_value(obj.VAR_NAME, v, TOML11_STRINGIZE(VAR_NAME)); + +#define TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(NAME, ...)\ + namespace toml { \ + template<> \ + struct from \ + { \ + template \ + static NAME from_toml(const basic_value& v) \ + { \ + NAME obj; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE, __VA_ARGS__) \ + return obj; \ + } \ + }; \ + template<> \ + struct into \ + { \ + template \ + static basic_value into_toml(const NAME& obj) \ + { \ + ::toml::basic_value v = typename ::toml::basic_value::table_type{}; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE, __VA_ARGS__) \ + return v; \ + } \ + }; \ + } /* toml */ + +#endif// TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +#endif // TOML11_CONVERSION_HPP +#ifndef TOML11_CONTEXT_HPP +#define TOML11_CONTEXT_HPP + + +#include + +namespace toml +{ +namespace detail +{ + +template +class context +{ + public: + + explicit context(const spec& toml_spec) + : toml_spec_(toml_spec), errors_{} + {} + + bool has_error() const noexcept {return !errors_.empty();} + + std::vector const& errors() const noexcept {return errors_;} + + semantic_version& toml_version() noexcept {return toml_spec_.version;} + semantic_version const& toml_version() const noexcept {return toml_spec_.version;} + + spec& toml_spec() noexcept {return toml_spec_;} + spec const& toml_spec() const noexcept {return toml_spec_;} + + void report_error(error_info err) + { + this->errors_.push_back(std::move(err)); + } + + error_info pop_last_error() + { + assert( ! errors_.empty()); + auto e = std::move(errors_.back()); + errors_.pop_back(); + return e; + } + + private: + + spec toml_spec_; + std::vector errors_; +}; + +} // detail +} // toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +struct type_config; +struct ordered_type_config; +namespace detail +{ +extern template class context<::toml::type_config>; +extern template class context<::toml::ordered_type_config>; +} // detail +} // toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_CONTEXT_HPP +#ifndef TOML11_SCANNER_HPP +#define TOML11_SCANNER_HPP + +#ifndef TOML11_SCANNER_FWD_HPP +#define TOML11_SCANNER_FWD_HPP + + +#include +#include +#include +#include + +#include +#include +#include + +namespace toml +{ +namespace detail +{ + +class scanner_base +{ + public: + virtual ~scanner_base() = default; + virtual region scan(location& loc) const = 0; + virtual scanner_base* clone() const = 0; + + // returns expected character or set of characters or literal. + // to show the error location, it changes loc (in `sequence`, especially). + virtual std::string expected_chars(location& loc) const = 0; + virtual std::string name() const = 0; +}; + +// make `scanner*` copyable +struct scanner_storage +{ + template>::value, + std::nullptr_t> = nullptr> + explicit scanner_storage(Scanner&& s) + : scanner_(cxx::make_unique>(std::forward(s))) + {} + ~scanner_storage() = default; + + scanner_storage(const scanner_storage& other); + scanner_storage& operator=(const scanner_storage& other); + scanner_storage(scanner_storage&&) = default; + scanner_storage& operator=(scanner_storage&&) = default; + + bool is_ok() const noexcept {return static_cast(scanner_);} + + region scan(location& loc) const; + + std::string expected_chars(location& loc) const; + + scanner_base& get() const noexcept; + + std::string name() const; + + private: + + std::unique_ptr scanner_; +}; + +// ---------------------------------------------------------------------------- + +class character final : public scanner_base +{ + public: + + using char_type = location::char_type; + + public: + + explicit character(const char_type c) noexcept + : value_(c) + {} + ~character() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + char_type value_; +}; + +// ---------------------------------------------------------------------------- + +class character_either final : public scanner_base +{ + public: + + using char_type = location::char_type; + + public: + + template + explicit character_either(const char (&cs)[N]) noexcept + : value_(cs), size_(N-1) // remove null character at the end + {} + ~character_either() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + const char* value_; + std::size_t size_; +}; + +// ---------------------------------------------------------------------------- + +class character_in_range final : public scanner_base +{ + public: + + using char_type = location::char_type; + + public: + + explicit character_in_range(const char_type from, const char_type to) noexcept + : from_(from), to_(to) + {} + ~character_in_range() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + char_type from_; + char_type to_; +}; + +// ---------------------------------------------------------------------------- + +class literal final : public scanner_base +{ + public: + + using char_type = location::char_type; + + public: + + template + explicit literal(const char (&cs)[N]) noexcept + : value_(cs), size_(N-1) // remove null character at the end + {} + ~literal() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + const char* value_; + std::size_t size_; +}; + +// ---------------------------------------------------------------------------- + +class sequence final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + explicit sequence(Ts&& ... args) + { + push_back_all(std::forward(args)...); + } + sequence(const sequence&) = default; + sequence(sequence&&) = default; + sequence& operator=(const sequence&) = default; + sequence& operator=(sequence&&) = default; + ~sequence() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + + void push_back_all() + { + return; + } + template + void push_back_all(T&& head, Ts&& ... args) + { + others_.emplace_back(std::forward(head)); + push_back_all(std::forward(args)...); + return; + } + + private: + std::vector others_; +}; + +// ---------------------------------------------------------------------------- + +class either final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + explicit either(Ts&& ... args) + { + push_back_all(std::forward(args)...); + } + either(const either&) = default; + either(either&&) = default; + either& operator=(const either&) = default; + either& operator=(either&&) = default; + ~either() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + + void push_back_all() + { + return; + } + template + void push_back_all(T&& head, Ts&& ... args) + { + others_.emplace_back(std::forward(head)); + push_back_all(std::forward(args)...); + return; + } + + private: + std::vector others_; +}; + +// ---------------------------------------------------------------------------- + +class repeat_exact final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + repeat_exact(const std::size_t length, Scanner&& other) + : length_(length), other_(std::forward(other)) + {} + repeat_exact(const repeat_exact&) = default; + repeat_exact(repeat_exact&&) = default; + repeat_exact& operator=(const repeat_exact&) = default; + repeat_exact& operator=(repeat_exact&&) = default; + ~repeat_exact() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + std::size_t length_; + scanner_storage other_; +}; + +// ---------------------------------------------------------------------------- + +class repeat_at_least final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + repeat_at_least(const std::size_t length, Scanner&& s) + : length_(length), other_(std::forward(s)) + {} + repeat_at_least(const repeat_at_least&) = default; + repeat_at_least(repeat_at_least&&) = default; + repeat_at_least& operator=(const repeat_at_least&) = default; + repeat_at_least& operator=(repeat_at_least&&) = default; + ~repeat_at_least() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + std::size_t length_; + scanner_storage other_; +}; + +// ---------------------------------------------------------------------------- + +class maybe final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + explicit maybe(Scanner&& s) + : other_(std::forward(s)) + {} + maybe(const maybe&) = default; + maybe(maybe&&) = default; + maybe& operator=(const maybe&) = default; + maybe& operator=(maybe&&) = default; + ~maybe() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + scanner_storage other_; +}; + +} // detail +} // toml +#endif // TOML11_SCANNER_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_SCANNER_IMPL_HPP +#define TOML11_SCANNER_IMPL_HPP + + +namespace toml +{ +namespace detail +{ + +TOML11_INLINE scanner_storage::scanner_storage(const scanner_storage& other) + : scanner_(nullptr) +{ + if(other.is_ok()) + { + scanner_.reset(other.get().clone()); + } +} +TOML11_INLINE scanner_storage& scanner_storage::operator=(const scanner_storage& other) +{ + if(this == std::addressof(other)) {return *this;} + if(other.is_ok()) + { + scanner_.reset(other.get().clone()); + } + return *this; +} + +TOML11_INLINE region scanner_storage::scan(location& loc) const +{ + assert(this->is_ok()); + return this->scanner_->scan(loc); +} + +TOML11_INLINE std::string scanner_storage::expected_chars(location& loc) const +{ + assert(this->is_ok()); + return this->scanner_->expected_chars(loc); +} + +TOML11_INLINE scanner_base& scanner_storage::get() const noexcept +{ + assert(this->is_ok()); + return *scanner_; +} + +TOML11_INLINE std::string scanner_storage::name() const +{ + assert(this->is_ok()); + return this->scanner_->name(); +} + +// ---------------------------------------------------------------------------- + +TOML11_INLINE region character::scan(location& loc) const +{ + if(loc.eof()) {return region{};} + + if(loc.current() == this->value_) + { + const auto first = loc; + loc.advance(1); + return region(first, loc); + } + return region{}; +} + +TOML11_INLINE std::string character::expected_chars(location&) const +{ + return show_char(value_); +} + +TOML11_INLINE scanner_base* character::clone() const +{ + return new character(*this); +} + +TOML11_INLINE std::string character::name() const +{ + return "character{" + show_char(value_) + "}"; +} + +// ---------------------------------------------------------------------------- + +TOML11_INLINE region character_either::scan(location& loc) const +{ + if(loc.eof()) {return region{};} + + for(std::size_t i=0; isize_; ++i) + { + const auto c = char_type(this->value_[i]); + if(loc.current() == c) + { + const auto first = loc; + loc.advance(1); + return region(first, loc); + } + } + return region{}; +} + +TOML11_INLINE std::string character_either::expected_chars(location&) const +{ + assert( this->value_ ); + assert( this->size_ != 0 ); + + std::string expected; + if(this->size_ == 1) + { + expected += show_char(char_type(value_[0])); + } + else if(this->size_ == 2) + { + expected += show_char(char_type(value_[0])) + " or " + + show_char(char_type(value_[1])); + } + else + { + for(std::size_t i=0; isize_; ++i) + { + if(i != 0) + { + expected += ", "; + } + if(i + 1 == this->size_) + { + expected += "or "; + } + expected += show_char(char_type(value_[i])); + } + } + return expected; +} + +TOML11_INLINE scanner_base* character_either::clone() const +{ + return new character_either(*this); +} + +TOML11_INLINE std::string character_either::name() const +{ + std::string n("character_either{"); + for(std::size_t i=0; isize_; ++i) + { + const auto c = char_type(this->value_[i]); + n += show_char(c); + n += ", "; + } + if(this->size_ != 0) + { + n.pop_back(); + n.pop_back(); + } + n += "}"; + return n; +} + +// ---------------------------------------------------------------------------- +// character_in_range + +TOML11_INLINE region character_in_range::scan(location& loc) const +{ + if(loc.eof()) {return region{};} + + const auto curr = loc.current(); + if(this->from_ <= curr && curr <= this->to_) + { + const auto first = loc; + loc.advance(1); + return region(first, loc); + } + return region{}; +} + +TOML11_INLINE std::string character_in_range::expected_chars(location&) const +{ + std::string expected("from `"); + expected += show_char(from_); + expected += "` to `"; + expected += show_char(to_); + expected += "`"; + return expected; +} + +TOML11_INLINE scanner_base* character_in_range::clone() const +{ + return new character_in_range(*this); +} + +TOML11_INLINE std::string character_in_range::name() const +{ + return "character_in_range{" + show_char(from_) + "," + show_char(to_) + "}"; +} + +// ---------------------------------------------------------------------------- +// literal + +TOML11_INLINE region literal::scan(location& loc) const +{ + const auto first = loc; + for(std::size_t i=0; iothers_.empty()) + { + n.pop_back(); + n.pop_back(); + } + n += "}"; + return n; +} + +// ---------------------------------------------------------------------------- +// either + +TOML11_INLINE region either::scan(location& loc) const +{ + for(const auto& other : others_) + { + const auto reg = other.scan(loc); + if(reg.is_ok()) + { + return reg; + } + } + return region{}; +} + +TOML11_INLINE std::string either::expected_chars(location& loc) const +{ + assert( ! others_.empty()); + + std::string expected = others_.at(0).expected_chars(loc); + if(others_.size() == 2) + { + expected += " or "; + expected += others_.at(1).expected_chars(loc); + } + else + { + for(std::size_t i=1; iothers_.empty()) + { + n.pop_back(); + n.pop_back(); + } + n += "}"; + return n; +} + +// ---------------------------------------------------------------------------- +// repeat_exact + +TOML11_INLINE region repeat_exact::scan(location& loc) const +{ + const auto first = loc; + for(std::size_t i=0; i +struct syntax_cache +{ + using value_type = cxx::return_type_of_t; + static_assert(std::is_base_of::value, ""); + + explicit syntax_cache(F f) + : func_(std::move(f)), cache_(cxx::make_nullopt()) + {} + + value_type const& at(const spec& s) + { + if( ! this->cache_.has_value() || this->cache_.value().first != s) + { + this->cache_ = std::make_pair(s, func_(s)); + } + return this->cache_.value().second; + } + + private: + F func_; + cxx::optional> cache_; +}; + +template +syntax_cache> make_cache(F&& f) +{ + return syntax_cache>(std::forward(f)); +} + +// =========================================================================== +// UTF-8 + +// avoid redundant representation and out-of-unicode sequence + +TOML11_INLINE character_in_range const& utf8_1byte(const spec&) +{ + static thread_local character_in_range cache(0x00, 0x7F); + return cache; +} + +TOML11_INLINE sequence const& utf8_2bytes(const spec&) +{ + static thread_local sequence cache( + character_in_range(0xC2, 0xDF), + character_in_range(0x80, 0xBF)); + return cache; +} + +TOML11_INLINE sequence const& utf8_3bytes(const spec&) +{ + static thread_local sequence cache(/*1~2 bytes = */either( + sequence(character (0xE0), character_in_range(0xA0, 0xBF)), + sequence(character_in_range(0xE1, 0xEC), character_in_range(0x80, 0xBF)), + sequence(character (0xED), character_in_range(0x80, 0x9F)), + sequence(character_in_range(0xEE, 0xEF), character_in_range(0x80, 0xBF)) + ), /*3rd byte = */ character_in_range(0x80, 0xBF)); + + return cache; +} + +TOML11_INLINE sequence const& utf8_4bytes(const spec&) +{ + static thread_local sequence cache(/*1~2 bytes = */either( + sequence(character (0xF0), character_in_range(0x90, 0xBF)), + sequence(character_in_range(0xF1, 0xF3), character_in_range(0x80, 0xBF)), + sequence(character (0xF4), character_in_range(0x80, 0x8F)) + ), character_in_range(0x80, 0xBF), character_in_range(0x80, 0xBF)); + + return cache; +} + +// =========================================================================== +// Whitespace + +TOML11_INLINE character_either const& wschar(const spec&) +{ + static thread_local character_either cache(" \t"); + return cache; +} + +TOML11_INLINE repeat_at_least const& ws(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s){ + return repeat_at_least(0, wschar(s)); + }); + return cache.at(sp); +} + +// =========================================================================== +// Newline + +TOML11_INLINE either const& newline(const spec&) +{ + static thread_local either cache(character(char_type('\n')), literal("\r\n")); + return cache; +} + +// =========================================================================== +// Comments + +TOML11_INLINE either const& allowed_comment_char(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s){ + if(s.v1_1_0_allow_control_characters_in_comments) + { + return either( + character_in_range(0x01, 0x09), + character_in_range(0x0E, 0x7F), + non_ascii(s) + ); + } + else + { + return either( + character(0x09), + character_in_range(0x20, 0x7E), + non_ascii(s) + ); + } + }); + return cache.at(sp); +} + +// XXX Note that it does not take newline +TOML11_INLINE sequence const& comment(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s){ + return sequence(character(char_type('#')), + repeat_at_least(0, allowed_comment_char(s))); + }); + return cache.at(sp); +} + +// =========================================================================== +// Boolean + +TOML11_INLINE either const& boolean(const spec&) +{ + static thread_local either cache(literal("true"), literal("false")); + return cache; +} + +// =========================================================================== +// Integer + +// non-digit-graph = ([a-zA-Z]|unicode mb char) +// graph = ([a-zA-Z0-9]|unicode mb char) +// suffix = _ non-digit-graph (graph | _graph) +TOML11_INLINE sequence const& num_suffix(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + const auto non_digit_graph = [&s]() { + return either( + alpha(s), + non_ascii(s) + ); + }; + const auto graph = [&s]() { + return either( + alpha(s), + digit(s), + non_ascii(s) + ); + }; + + return sequence( + character(char_type('_')), + non_digit_graph(), + repeat_at_least(0, + either( + sequence(character(char_type('_')), graph()), + graph() + ) + ) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& dec_int(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + const auto digit19 = []() { + return character_in_range(char_type('1'), char_type('9')); + }; + return sequence( + maybe(character_either("+-")), + either( + sequence( + digit19(), + repeat_at_least(1, + either( + digit(s), + sequence(character(char_type('_')), digit(s)) + ) + ) + ), + digit(s) + ) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& hex_int(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence( + literal("0x"), + hexdig(s), + repeat_at_least(0, + either( + hexdig(s), + sequence(character(char_type('_')), hexdig(s)) + ) + ) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& oct_int(const spec& s) +{ + static thread_local auto cache = make_cache([](const spec&) { + const auto digit07 = []() { + return character_in_range(char_type('0'), char_type('7')); + }; + return sequence( + literal("0o"), + digit07(), + repeat_at_least(0, + either( + digit07(), + sequence(character(char_type('_')), digit07()) + ) + ) + ); + }); + return cache.at(s); +} + +TOML11_INLINE sequence const& bin_int(const spec& s) +{ + static thread_local auto cache = make_cache([](const spec&) { + const auto digit01 = []() { + return character_either("01"); + }; + return sequence( + literal("0b"), + digit01(), + repeat_at_least(0, + either( + digit01(), + sequence(character(char_type('_')), digit01()) + ) + ) + ); + }); + return cache.at(s); +} + +TOML11_INLINE either const& integer(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return either( + hex_int(s), + oct_int(s), + bin_int(s), + dec_int(s) + ); + }); + return cache.at(sp); +} + + +// =========================================================================== +// Floating + +TOML11_INLINE sequence const& zero_prefixable_int(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence( + digit(s), + repeat_at_least(0, + either( + digit(s), + sequence(character('_'), digit(s)) + ) + ) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& fractional_part(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence( + character('.'), + zero_prefixable_int(s) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& exponent_part(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence( + character_either("eE"), + maybe(character_either("+-")), + zero_prefixable_int(s) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& hex_floating(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + // C99 hexfloat (%a) + // [+-]? 0x ( [0-9a-fA-F]*\.[0-9a-fA-F]+ | [0-9a-fA-F]+\.? ) [pP] [+-]? [0-9]+ + + // - 0x(int).(frac)p[+-](int) + // - 0x(int).p[+-](int) + // - 0x.(frac)p[+-](int) + // - 0x(int)p[+-](int) + + return sequence( + maybe(character_either("+-")), + character('0'), + character_either("xX"), + either( + sequence( + repeat_at_least(0, hexdig(s)), + character('.'), + repeat_at_least(1, hexdig(s)) + ), + sequence( + repeat_at_least(1, hexdig(s)), + maybe(character('.')) + ) + ), + character_either("pP"), + maybe(character_either("+-")), + repeat_at_least(1, character_in_range('0', '9')) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE either const& floating(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return either( + sequence( + dec_int(s), + either( + exponent_part(s), + sequence(fractional_part(s), maybe(exponent_part(s))) + ) + ), + sequence( + maybe(character_either("+-")), + either(literal("inf"), literal("nan")) + ) + ); + }); + return cache.at(sp); +} + +// =========================================================================== +// Datetime + +TOML11_INLINE sequence const& local_date(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence( + repeat_exact(4, digit(s)), + character('-'), + repeat_exact(2, digit(s)), + character('-'), + repeat_exact(2, digit(s)) + ); + }); + return cache.at(sp); +} +TOML11_INLINE sequence const& local_time(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + if(s.v1_1_0_make_seconds_optional) + { + return sequence( + repeat_exact(2, digit(s)), + character(':'), + repeat_exact(2, digit(s)), + maybe(sequence( + character(':'), + repeat_exact(2, digit(s)), + maybe(sequence(character('.'), repeat_at_least(1, digit(s)))) + ))); + } + else + { + return sequence( + repeat_exact(2, digit(s)), + character(':'), + repeat_exact(2, digit(s)), + character(':'), + repeat_exact(2, digit(s)), + maybe(sequence(character('.'), repeat_at_least(1, digit(s)))) + ); + } + }); + return cache.at(sp); +} +TOML11_INLINE either const& time_offset(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return either( + character_either("zZ"), + sequence(character_either("+-"), + repeat_exact(2, digit(s)), + character(':'), + repeat_exact(2, digit(s)) + ) + ); + }); + return cache.at(sp); +} +TOML11_INLINE sequence const& full_time(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(local_time(s), time_offset(s)); + }); + return cache.at(sp); +} +TOML11_INLINE character_either const& time_delim(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec&) { + return character_either("Tt "); + }); + return cache.at(sp); +} +TOML11_INLINE sequence const& local_datetime(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(local_date(s), time_delim(s), local_time(s)); + }); + return cache.at(sp); +} +TOML11_INLINE sequence const& offset_datetime(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(local_date(s), time_delim(s), full_time(s)); + }); + return cache.at(sp); +} + +// =========================================================================== +// String + +TOML11_INLINE sequence const& escaped_x2(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(character('x'), repeat_exact(2, hexdig(s))); + }); + return cache.at(sp); +} +TOML11_INLINE sequence const& escaped_u4(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(character('u'), repeat_exact(4, hexdig(s))); + }); + return cache.at(sp); +} +TOML11_INLINE sequence const& escaped_U8(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(character('U'), repeat_exact(8, hexdig(s))); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& escaped(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + const auto escape_char = [&s] { + if(s.v1_1_0_add_escape_sequence_e) + { + return character_either("\"\\bfnrte"); + } + else + { + return character_either("\"\\bfnrt"); + } + }; + + const auto escape_seq = [&s, &escape_char] { + if(s.v1_1_0_add_escape_sequence_x) + { + return either( + escape_char(), + escaped_u4(s), + escaped_U8(s), + escaped_x2(s) + ); + } + else + { + return either( + escape_char(), + escaped_u4(s), + escaped_U8(s) + ); + } + }; + + return sequence(character('\\'), escape_seq()); + }); + return cache.at(sp); +} + +TOML11_INLINE either const& basic_char(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + const auto basic_unescaped = [&s]() { + return either( + wschar(s), + character(0x21), // 22 is " + character_in_range(0x23, 0x5B), // 5C is backslash + character_in_range(0x5D, 0x7E), // 7F is DEL + non_ascii(s) + ); + }; + return either(basic_unescaped(), escaped(s)); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& basic_string(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence( + character('"'), + repeat_at_least(0, basic_char(s)), + character('"') + ); + }); + return cache.at(sp); +} + +// --------------------------------------------------------------------------- +// multiline string + +TOML11_INLINE sequence const& escaped_newline(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence( + character('\\'), ws(s), newline(s), + repeat_at_least(0, either(wschar(s), newline(s))) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& ml_basic_string(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + const auto mlb_content = [&s]() { + return either(basic_char(s), newline(s), escaped_newline(s)); + }; + const auto mlb_quotes = []() { + return either(literal("\"\""), character('\"')); + }; + + return sequence( + literal("\"\"\""), + maybe(newline(s)), + repeat_at_least(0, mlb_content()), + repeat_at_least(0, + sequence( + mlb_quotes(), + repeat_at_least(1, mlb_content()) + ) + ), + // XXX """ and mlb_quotes are intentionally reordered to avoid + // unexpected match of mlb_quotes + literal("\"\"\""), + maybe(mlb_quotes()) + ); + }); + return cache.at(sp); +} + +// --------------------------------------------------------------------------- +// literal string + +TOML11_INLINE either const& literal_char(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return either( + character (0x09), + character_in_range(0x20, 0x26), + character_in_range(0x28, 0x7E), + non_ascii(s) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& literal_string(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence( + character('\''), + repeat_at_least(0, literal_char(s)), + character('\'') + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& ml_literal_string(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + const auto mll_quotes = []() { + return either(literal("''"), character('\'')); + }; + const auto mll_content = [&s]() { + return either(literal_char(s), newline(s)); + }; + + return sequence( + literal("'''"), + maybe(newline(s)), + repeat_at_least(0, mll_content()), + repeat_at_least(0, sequence( + mll_quotes(), + repeat_at_least(1, mll_content()) + ) + ), + literal("'''"), + maybe(mll_quotes()) + // XXX ''' and mll_quotes are intentionally reordered to avoid + // unexpected match of mll_quotes + ); + }); + return cache.at(sp); +} + +TOML11_INLINE either const& string(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return either( + ml_basic_string(s), + ml_literal_string(s), + basic_string(s), + literal_string(s) + ); + }); + return cache.at(sp); +} + +// =========================================================================== +// Keys + +// to keep `expected_chars` simple +TOML11_INLINE non_ascii_key_char::non_ascii_key_char(const spec& s) noexcept +{ + assert(s.v1_1_0_allow_non_english_in_bare_keys); + (void)s; // for NDEBUG +} + +TOML11_INLINE std::uint32_t non_ascii_key_char::read_utf8(location& loc) const +{ + // U+0000 ... U+0079 ; 0xxx_xxxx + // U+0080 ... U+07FF ; 110y_yyyx 10xx_xxxx; + // U+0800 ... U+FFFF ; 1110_yyyy 10yx_xxxx 10xx_xxxx + // U+010000 ... U+10FFFF; 1111_0yyy 10yy_xxxx 10xx_xxxx 10xx_xxxx + + const unsigned char b1 = loc.current(); loc.advance(1); + if(b1 < 0x80) + { + return static_cast(b1); + } + else if((b1 >> 5) == 6) // 0b110 == 6 + { + const auto b2 = loc.current(); loc.advance(1); + + const std::uint32_t c1 = b1 & ((1 << 5) - 1); + const std::uint32_t c2 = b2 & ((1 << 6) - 1); + const std::uint32_t codep = (c1 << 6) + c2; + + if(codep < 0x80) + { + return 0xFFFFFFFF; + } + return codep; + } + else if((b1 >> 4) == 14) // 0b1110 == 14 + { + const auto b2 = loc.current(); loc.advance(1); if(loc.eof()) {return 0xFFFFFFFF;} + const auto b3 = loc.current(); loc.advance(1); + + const std::uint32_t c1 = b1 & ((1 << 4) - 1); + const std::uint32_t c2 = b2 & ((1 << 6) - 1); + const std::uint32_t c3 = b3 & ((1 << 6) - 1); + + const std::uint32_t codep = (c1 << 12) + (c2 << 6) + c3; + if(codep < 0x800) + { + return 0xFFFFFFFF; + } + return codep; + } + else if((b1 >> 3) == 30) // 0b11110 == 30 + { + const auto b2 = loc.current(); loc.advance(1); if(loc.eof()) {return 0xFFFFFFFF;} + const auto b3 = loc.current(); loc.advance(1); if(loc.eof()) {return 0xFFFFFFFF;} + const auto b4 = loc.current(); loc.advance(1); + + const std::uint32_t c1 = b1 & ((1 << 3) - 1); + const std::uint32_t c2 = b2 & ((1 << 6) - 1); + const std::uint32_t c3 = b3 & ((1 << 6) - 1); + const std::uint32_t c4 = b4 & ((1 << 6) - 1); + const std::uint32_t codep = (c1 << 18) + (c2 << 12) + (c3 << 6) + c4; + + if(codep < 0x10000) + { + return 0xFFFFFFFF; + } + return codep; + } + else // not a Unicode codepoint in UTF-8 + { + return 0xFFFFFFFF; + } +} + +TOML11_INLINE region non_ascii_key_char::scan(location& loc) const +{ + if(loc.eof()) {return region{};} + + const auto first = loc; + + const auto cp = read_utf8(loc); + + if(cp == 0xFFFFFFFF) + { + return region{}; + } + + // ALPHA / DIGIT / %x2D / %x5F ; a-z A-Z 0-9 - _ + // / %xB2 / %xB3 / %xB9 / %xBC-BE ; superscript digits, fractions + // / %xC0-D6 / %xD8-F6 / %xF8-37D ; non-symbol chars in Latin block + // / %x37F-1FFF ; exclude GREEK QUESTION MARK, which is basically a semi-colon + // / %x200C-200D / %x203F-2040 ; from General Punctuation Block, include the two tie symbols and ZWNJ, ZWJ + // / %x2070-218F / %x2460-24FF ; include super-/subscripts, letterlike/numberlike forms, enclosed alphanumerics + // / %x2C00-2FEF / %x3001-D7FF ; skip arrows, math, box drawing etc, skip 2FF0-3000 ideographic up/down markers and spaces + // / %xF900-FDCF / %xFDF0-FFFD ; skip D800-DFFF surrogate block, E000-F8FF Private Use area, FDD0-FDEF intended for process-internal use (unicode) + // / %x10000-EFFFF ; all chars outside BMP range, excluding Private Use planes (F0000-10FFFF) + + if(cp == 0xB2 || cp == 0xB3 || cp == 0xB9 || (0xBC <= cp && cp <= 0xBE) || + (0xC0 <= cp && cp <= 0xD6 ) || (0xD8 <= cp && cp <= 0xF6) || (0xF8 <= cp && cp <= 0x37D) || + (0x37F <= cp && cp <= 0x1FFF) || + (0x200C <= cp && cp <= 0x200D) || (0x203F <= cp && cp <= 0x2040) || + (0x2070 <= cp && cp <= 0x218F) || (0x2460 <= cp && cp <= 0x24FF) || + (0x2C00 <= cp && cp <= 0x2FEF) || (0x3001 <= cp && cp <= 0xD7FF) || + (0xF900 <= cp && cp <= 0xFDCF) || (0xFDF0 <= cp && cp <= 0xFFFD) || + (0x10000 <= cp && cp <= 0xEFFFF) ) + { + return region(first, loc); + } + loc = first; + return region{}; +} + +TOML11_INLINE repeat_at_least const& unquoted_key(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + const auto keychar = [&s] { + if(s.v1_1_0_allow_non_english_in_bare_keys) + { + return either(alpha(s), digit(s), character{0x2D}, character{0x5F}, + non_ascii_key_char(s)); + } + else + { + return either(alpha(s), digit(s), character{0x2D}, character{0x5F}); + } + }; + return repeat_at_least(1, keychar()); + }); + return cache.at(sp); +} + +TOML11_INLINE either const& quoted_key(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return either(basic_string(s), literal_string(s)); + }); + return cache.at(sp); +} + +TOML11_INLINE either const& simple_key(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return either(unquoted_key(s), quoted_key(s)); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& dot_sep(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(ws(s), character('.'), ws(s)); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& dotted_key(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence( + simple_key(s), + repeat_at_least(1, sequence(dot_sep(s), simple_key(s))) + ); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& keyval_sep(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(ws(s), character('='), ws(s)); + }); + return cache.at(sp); +} + +// =========================================================================== +// Table key + +TOML11_INLINE sequence const& std_table(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(character('['), ws(s), key(s), ws(s), character(']')); + }); + return cache.at(sp); +} + +TOML11_INLINE sequence const& array_table(const spec& sp) +{ + static thread_local auto cache = make_cache([](const spec& s) { + return sequence(literal("[["), ws(s), key(s), ws(s), literal("]]")); + }); + return cache.at(sp); +} + +// =========================================================================== +// extension: null + +TOML11_INLINE literal const& null_value(const spec&) +{ + static thread_local literal cache("null"); + return cache; +} + +} // namespace syntax +} // namespace detail +} // namespace toml +#endif // TOML11_SYNTAX_IMPL_HPP +#endif + +#endif// TOML11_SYNTAX_HPP +#ifndef TOML11_SKIP_HPP +#define TOML11_SKIP_HPP + + +#include + +namespace toml +{ +namespace detail +{ + +template +bool skip_whitespace(location& loc, const context& ctx) +{ + return syntax::ws(ctx.toml_spec()).scan(loc).is_ok(); +} + +template +bool skip_empty_lines(location& loc, const context& ctx) +{ + return repeat_at_least(1, sequence( + syntax::ws(ctx.toml_spec()), + syntax::newline(ctx.toml_spec()) + )).scan(loc).is_ok(); +} + +// For error recovery. +// +// In case if a comment line contains an invalid character, we need to skip it +// to advance parsing. +template +void skip_comment_block(location& loc, const context& ctx) +{ + while( ! loc.eof()) + { + skip_whitespace(loc, ctx); + if(loc.current() == '#') + { + while( ! loc.eof()) + { + // both CRLF and LF ends with LF. + if(loc.current() == '\n') + { + loc.advance(); + break; + } + } + } + else if(syntax::newline(ctx.toml_spec()).scan(loc).is_ok()) + { + ; // an empty line. skip this also + } + else + { + // the next token is neither a comment nor empty line. + return ; + } + } + return ; +} + +template +void skip_empty_or_comment_lines(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + repeat_at_least(0, sequence( + syntax::ws(spec), + maybe(syntax::comment(spec)), + syntax::newline(spec)) + ).scan(loc); + return ; +} + +// For error recovery. +// +// Sometimes we need to skip a value and find a delimiter, like `,`, `]`, or `}`. +// To find delimiter, we need to skip delimiters in a string. +// Since we are skipping invalid value while error recovery, we don't need +// to check the syntax. Here we just skip string-like region until closing quote +// is found. +template +void skip_string_like(location& loc, const context&) +{ + // if """ is found, skip until the closing """ is found. + if(literal("\"\"\"").scan(loc).is_ok()) + { + while( ! loc.eof()) + { + if(literal("\"\"\"").scan(loc).is_ok()) + { + return; + } + loc.advance(); + } + } + else if(literal("'''").scan(loc).is_ok()) + { + while( ! loc.eof()) + { + if(literal("'''").scan(loc).is_ok()) + { + return; + } + loc.advance(); + } + } + // if " is found, skip until the closing " or newline is found. + else if(loc.current() == '"') + { + while( ! loc.eof()) + { + loc.advance(); + if(loc.current() == '"' || loc.current() == '\n') + { + loc.advance(); + return; + } + } + } + else if(loc.current() == '\'') + { + while( ! loc.eof()) + { + loc.advance(); + if(loc.current() == '\'' || loc.current() == '\n') + { + loc.advance(); + return ; + } + } + } + return; +} + +template +void skip_value(location& loc, const context& ctx); +template +void skip_array_like(location& loc, const context& ctx); +template +void skip_inline_table_like(location& loc, const context& ctx); +template +void skip_key_value_pair(location& loc, const context& ctx); + +template +result +guess_value_type(const location& loc, const context& ctx); + +template +void skip_array_like(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + assert(loc.current() == '['); + loc.advance(); + + while( ! loc.eof()) + { + if(loc.current() == '\"' || loc.current() == '\'') + { + skip_string_like(loc, ctx); + } + else if(loc.current() == '#') + { + skip_comment_block(loc, ctx); + } + else if(loc.current() == '{') + { + skip_inline_table_like(loc, ctx); + } + else if(loc.current() == '[') + { + const auto checkpoint = loc; + if(syntax::std_table(spec).scan(loc).is_ok() || + syntax::array_table(spec).scan(loc).is_ok()) + { + loc = checkpoint; + break; + } + // if it is not a table-definition, then it is an array. + skip_array_like(loc, ctx); + } + else if(loc.current() == '=') + { + // key-value pair cannot be inside the array. + // guessing the error is "missing closing bracket `]`". + // find the previous key just before `=`. + while(loc.get_location() != 0) + { + loc.retrace(); + if(loc.current() == '\n') + { + loc.advance(); + break; + } + } + break; + } + else if(loc.current() == ']') + { + break; // found closing bracket + } + else + { + loc.advance(); + } + } + return ; +} + +template +void skip_inline_table_like(location& loc, const context& ctx) +{ + assert(loc.current() == '{'); + loc.advance(); + + const auto& spec = ctx.toml_spec(); + + while( ! loc.eof()) + { + if(loc.current() == '\n' && ! spec.v1_1_0_allow_newlines_in_inline_tables) + { + break; // missing closing `}`. + } + else if(loc.current() == '\"' || loc.current() == '\'') + { + skip_string_like(loc, ctx); + } + else if(loc.current() == '#') + { + skip_comment_block(loc, ctx); + if( ! spec.v1_1_0_allow_newlines_in_inline_tables) + { + // comment must end with newline. + break; // missing closing `}`. + } + } + else if(loc.current() == '[') + { + const auto checkpoint = loc; + if(syntax::std_table(spec).scan(loc).is_ok() || + syntax::array_table(spec).scan(loc).is_ok()) + { + loc = checkpoint; + break; // missing closing `}`. + } + // if it is not a table-definition, then it is an array. + skip_array_like(loc, ctx); + } + else if(loc.current() == '{') + { + skip_inline_table_like(loc, ctx); + } + else if(loc.current() == '}') + { + // closing brace found. guessing the error is inside the table. + break; + } + else + { + // skip otherwise. + loc.advance(); + } + } + return ; +} + +template +void skip_value(location& loc, const context& ctx) +{ + value_t ty = guess_value_type(loc, ctx).unwrap_or(value_t::empty); + if(ty == value_t::string) + { + skip_string_like(loc, ctx); + } + else if(ty == value_t::array) + { + skip_array_like(loc, ctx); + } + else if(ty == value_t::table) + { + // In case of multiline tables, it may skip key-value pair but not the + // whole table. + skip_inline_table_like(loc, ctx); + } + else // others are an "in-line" values. skip until the next line + { + while( ! loc.eof()) + { + if(loc.current() == '\n') + { + break; + } + else if(loc.current() == ',' || loc.current() == ']' || loc.current() == '}') + { + break; + } + loc.advance(); + } + } + return; +} + +template +void skip_key_value_pair(location& loc, const context& ctx) +{ + while( ! loc.eof()) + { + if(loc.current() == '=') + { + skip_whitespace(loc, ctx); + skip_value(loc, ctx); + return; + } + else if(loc.current() == '\n') + { + // newline is found before finding `=`. assuming "missing `=`". + return; + } + loc.advance(); + } + return ; +} + +template +void skip_until_next_table(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + while( ! loc.eof()) + { + if(loc.current() == '\n') + { + loc.advance(); + const auto line_begin = loc; + + skip_whitespace(loc, ctx); + if(syntax::std_table(spec).scan(loc).is_ok()) + { + loc = line_begin; + return ; + } + if(syntax::array_table(spec).scan(loc).is_ok()) + { + loc = line_begin; + return ; + } + } + loc.advance(); + } +} + +} // namespace detail +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +struct type_config; +struct ordered_type_config; + +namespace detail +{ +extern template bool skip_whitespace (location& loc, const context&); +extern template bool skip_empty_lines (location& loc, const context&); +extern template void skip_comment_block (location& loc, const context&); +extern template void skip_empty_or_comment_lines(location& loc, const context&); +extern template void skip_string_like (location& loc, const context&); +extern template void skip_array_like (location& loc, const context&); +extern template void skip_inline_table_like (location& loc, const context&); +extern template void skip_value (location& loc, const context&); +extern template void skip_key_value_pair (location& loc, const context&); +extern template void skip_until_next_table (location& loc, const context&); + +extern template bool skip_whitespace (location& loc, const context&); +extern template bool skip_empty_lines (location& loc, const context&); +extern template void skip_comment_block (location& loc, const context&); +extern template void skip_empty_or_comment_lines(location& loc, const context&); +extern template void skip_string_like (location& loc, const context&); +extern template void skip_array_like (location& loc, const context&); +extern template void skip_inline_table_like (location& loc, const context&); +extern template void skip_value (location& loc, const context&); +extern template void skip_key_value_pair (location& loc, const context&); +extern template void skip_until_next_table (location& loc, const context&); + +} // detail +} // toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_SKIP_HPP +#ifndef TOML11_PARSER_HPP +#define TOML11_PARSER_HPP + + +#include +#include + +#include +#include + +#if defined(TOML11_HAS_FILESYSTEM) && TOML11_HAS_FILESYSTEM +#include +#endif + +namespace toml +{ + +struct syntax_error final : public ::toml::exception +{ + public: + syntax_error(std::string what_arg, std::vector err) + : what_(std::move(what_arg)), err_(std::move(err)) + {} + ~syntax_error() noexcept override = default; + + const char* what() const noexcept override {return what_.c_str();} + + std::vector const& errors() const noexcept + { + return err_; + } + + private: + std::string what_; + std::vector err_; +}; + +struct file_io_error final : public ::toml::exception +{ + public: + + file_io_error(const std::string& msg, const std::string& fname) + : errno_(cxx::make_nullopt()), + what_(msg + " \"" + fname + "\"") + {} + file_io_error(int errnum, const std::string& msg, const std::string& fname) + : errno_(errnum), + what_(msg + " \"" + fname + "\": errno=" + std::to_string(errnum)) + {} + ~file_io_error() noexcept override = default; + + const char* what() const noexcept override {return what_.c_str();} + + bool has_errno() const noexcept {return errno_.has_value();} + int get_errno() const noexcept {return errno_.value_or(0);} + + private: + + cxx::optional errno_; + std::string what_; +}; + +namespace detail +{ + +/* ============================================================================ + * __ ___ _ __ _ __ ___ _ _ + * / _/ _ \ ' \| ' \/ _ \ ' \ + * \__\___/_|_|_|_|_|_\___/_||_| + */ + +template +error_info make_syntax_error(std::string title, + const S& scanner, location loc, std::string suffix = "") +{ + auto msg = std::string("expected ") + scanner.expected_chars(loc); + auto src = source_location(region(loc)); + return make_error_info( + std::move(title), std::move(src), std::move(msg), std::move(suffix)); +} + + +/* ============================================================================ + * _ + * __ ___ _ __ _ __ ___ _ _| |_ + * / _/ _ \ ' \| ' \/ -_) ' \ _| + * \__\___/_|_|_|_|_|_\___|_||_\__| + */ + +template +result, error_info> +parse_comment_line(location& loc, context& ctx) +{ + const auto& spec = ctx.toml_spec(); + const auto first = loc; + + skip_whitespace(loc, ctx); + + const auto com_reg = syntax::comment(spec).scan(loc); + if(com_reg.is_ok()) + { + // once comment started, newline must follow (or reach EOF). + if( ! loc.eof() && ! syntax::newline(spec).scan(loc).is_ok()) + { + while( ! loc.eof()) // skip until newline to continue parsing + { + loc.advance(); + if(loc.current() == '\n') { /*skip LF*/ loc.advance(); break; } + } + return err(make_error_info("toml::parse_comment_line: " + "newline (LF / CRLF) or EOF is expected", + source_location(region(loc)), "but got this", + "Hint: most of the control characters are not allowed in comments")); + } + return ok(cxx::optional(com_reg.as_string())); + } + else + { + loc = first; // rollback whitespace to parse indent + return ok(cxx::optional(cxx::make_nullopt())); + } +} + +/* ============================================================================ + * ___ _ + * | _ ) ___ ___| |___ __ _ _ _ + * | _ \/ _ \/ _ \ / -_) _` | ' \ + * |___/\___/\___/_\___\__,_|_||_| + */ + +template +result, error_info> +parse_boolean(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::boolean(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_boolean: " + "invalid boolean: boolean must be `true` or `false`, in lowercase. " + "string must be surrounded by `\"`", syntax::boolean(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + const auto str = reg.as_string(); + const auto val = [&str]() { + if(str == "true") + { + return true; + } + else + { + assert(str == "false"); + return false; + } + }(); + + // ---------------------------------------------------------------------- + // no format info for boolean + boolean_format_info fmt; + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); +} + +/* ============================================================================ + * ___ _ + * |_ _|_ _| |_ ___ __ _ ___ _ _ + * | || ' \ _/ -_) _` / -_) '_| + * |___|_||_\__\___\__, \___|_| + * |___/ + */ + +template +result, error_info> +parse_bin_integer(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + auto reg = syntax::bin_int(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_bin_integer: " + "invalid integer: bin_int must be like: 0b0101, 0b1111_0000", + syntax::bin_int(spec), loc)); + } + + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::bin; + fmt.width = str.size() - 2 - static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if(first_underscore != str.rend()) + { + fmt.spacer = static_cast(std::distance(str.rbegin(), first_underscore)); + } + + // skip prefix `0b` and zeros and underscores at the MSB + str.erase(str.begin(), std::find(std::next(str.begin(), 2), str.end(), '1')); + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + // 0b0000_0000 becomes empty. + if(str.empty()) { str = "0"; } + + const auto val = TC::parse_int(str, source_location(region(loc)), 2); + if(val.is_ok()) + { + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } + else + { + loc = first; + return err(val.as_err()); + } +} + +// ---------------------------------------------------------------------------- + +template +result, error_info> +parse_oct_integer(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + auto reg = syntax::oct_int(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_oct_integer: " + "invalid integer: oct_int must be like: 0o775, 0o04_44", + syntax::oct_int(spec), loc)); + } + + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::oct; + fmt.width = str.size() - 2 - static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if(first_underscore != str.rend()) + { + fmt.spacer = static_cast(std::distance(str.rbegin(), first_underscore)); + } + + // skip prefix `0o` and zeros and underscores at the MSB + str.erase(str.begin(), std::find_if( + std::next(str.begin(), 2), str.end(), [](const char c) { + return c != '0' && c != '_'; + })); + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + // 0o0000_0000 becomes empty. + if(str.empty()) { str = "0"; } + + const auto val = TC::parse_int(str, source_location(region(loc)), 8); + if(val.is_ok()) + { + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } + else + { + loc = first; + return err(val.as_err()); + } +} + +template +result, error_info> +parse_hex_integer(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + auto reg = syntax::hex_int(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_hex_integer: " + "invalid integer: hex_int must be like: 0xC0FFEE, 0xdead_beef", + syntax::hex_int(spec), loc)); + } + + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::hex; + fmt.width = str.size() - 2 - static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if(first_underscore != str.rend()) + { + fmt.spacer = static_cast(std::distance(str.rbegin(), first_underscore)); + } + + // skip prefix `0x` and zeros and underscores at the MSB + str.erase(str.begin(), std::find_if( + std::next(str.begin(), 2), str.end(), [](const char c) { + return c != '0' && c != '_'; + })); + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + // 0x0000_0000 becomes empty. + if(str.empty()) { str = "0"; } + + // prefix zero and _ is removed. check if it uses upper/lower case. + // if both upper and lower case letters are found, set upper=true. + const auto lower_not_found = std::find_if(str.begin(), str.end(), + [](const char c) { return std::islower(static_cast(c)) != 0; }) == str.end(); + const auto upper_found = std::find_if(str.begin(), str.end(), + [](const char c) { return std::isupper(static_cast(c)) != 0; }) != str.end(); + fmt.uppercase = lower_not_found || upper_found; + + const auto val = TC::parse_int(str, source_location(region(loc)), 16); + if(val.is_ok()) + { + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } + else + { + loc = first; + return err(val.as_err()); + } +} + +template +result, error_info> +parse_dec_integer(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::dec_int(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_dec_integer: " + "invalid integer: dec_int must be like: 42, 123_456_789", + syntax::dec_int(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::dec; + fmt.width = str.size() - static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if(first_underscore != str.rend()) + { + fmt.spacer = static_cast(std::distance(str.rbegin(), first_underscore)); + } + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + auto src = source_location(region(loc)); + const auto val = TC::parse_int(str, src, 10); + if(val.is_err()) + { + loc = first; + return err(val.as_err()); + } + + // ---------------------------------------------------------------------- + // parse suffix (extension) + + if(spec.ext_num_suffix && loc.current() == '_') + { + const auto sfx_reg = syntax::num_suffix(spec).scan(loc); + if( ! sfx_reg.is_ok()) + { + loc = first; + return err(make_error_info("toml::parse_dec_integer: " + "invalid suffix: should be `_ non-digit-graph (graph | _graph)`", + source_location(region(loc)), "here")); + } + auto sfx = sfx_reg.as_string(); + assert( ! sfx.empty() && sfx.front() == '_'); + sfx.erase(sfx.begin()); // remove the first `_` + + fmt.suffix = sfx; + } + + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); +} + +template +result, error_info> +parse_integer(location& loc, const context& ctx) +{ + const auto first = loc; + + if( ! loc.eof() && (loc.current() == '+' || loc.current() == '-')) + { + // skip +/- to diagnose +0xDEADBEEF or -0b0011 (invalid). + // without this, +0xDEAD_BEEF will be parsed as a decimal int and + // unexpected "xDEAD_BEEF" will appear after integer "+0". + loc.advance(); + } + + if( ! loc.eof() && loc.current() == '0') + { + loc.advance(); + if(loc.eof()) + { + // `[+-]?0`. parse as an decimal integer. + loc = first; + return parse_dec_integer(loc, ctx); + } + + const auto prefix = loc.current(); + auto prefix_src = source_location(region(loc)); + + loc = first; + + if(prefix == 'b') {return parse_bin_integer(loc, ctx);} + if(prefix == 'o') {return parse_oct_integer(loc, ctx);} + if(prefix == 'x') {return parse_hex_integer(loc, ctx);} + + if(std::isdigit(prefix)) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_integer: " + "leading zero in an decimal integer is not allowed", + std::move(src), "leading zero")); + } + } + + loc = first; + return parse_dec_integer(loc, ctx); +} + +/* ============================================================================ + * ___ _ _ _ + * | __| |___ __ _| |_(_)_ _ __ _ + * | _|| / _ \/ _` | _| | ' \/ _` | + * |_| |_\___/\__,_|\__|_|_||_\__, | + * |___/ + */ + +template +result, error_info> +parse_floating(location& loc, const context& ctx) +{ + using floating_type = typename basic_value::floating_type; + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + // ---------------------------------------------------------------------- + // check syntax + bool is_hex = false; + std::string str; + region reg; + if(spec.ext_hex_float && literal("0x").scan(loc).is_ok()) + { + loc = first; + is_hex = true; + + reg = syntax::hex_floating(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_floating: " + "invalid hex floating: float must be like: 0xABCp-3f", + syntax::floating(spec), loc)); + } + str = reg.as_string(); + } + else + { + reg = syntax::floating(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_floating: " + "invalid floating: float must be like: -3.14159_26535, 6.022e+23, " + "inf, or nan (lowercase).", syntax::floating(spec), loc)); + } + str = reg.as_string(); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + floating_format_info fmt; + + if(is_hex) + { + fmt.fmt = floating_format::hex; + } + else + { + // since we already checked that the string conforms the TOML standard. + if(std::find(str.begin(), str.end(), 'e') != str.end() || + std::find(str.begin(), str.end(), 'E') != str.end()) + { + fmt.fmt = floating_format::scientific; // use exponent part + } + else + { + fmt.fmt = floating_format::fixed; // do not use exponent part + } + } + + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + floating_type val{0}; + + if(str == "inf" || str == "+inf") + { + TOML11_CONSTEXPR_IF(std::numeric_limits::has_infinity) + { + val = std::numeric_limits::infinity(); + } + else + { + return err(make_error_info("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: inf is not supported")); + } + } + else if(str == "-inf") + { + TOML11_CONSTEXPR_IF(std::numeric_limits::has_infinity) + { + val = -std::numeric_limits::infinity(); + } + else + { + return err(make_error_info("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: inf is not supported")); + } + } + else if(str == "nan" || str == "+nan") + { + TOML11_CONSTEXPR_IF(std::numeric_limits::has_quiet_NaN) + { + val = std::numeric_limits::quiet_NaN(); + } + else TOML11_CONSTEXPR_IF(std::numeric_limits::has_signaling_NaN) + { + val = std::numeric_limits::signaling_NaN(); + } + else + { + return err(make_error_info("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: NaN is not supported")); + } + } + else if(str == "-nan") + { + using std::copysign; + TOML11_CONSTEXPR_IF(std::numeric_limits::has_quiet_NaN) + { + val = copysign(std::numeric_limits::quiet_NaN(), floating_type(-1)); + } + else TOML11_CONSTEXPR_IF(std::numeric_limits::has_signaling_NaN) + { + val = copysign(std::numeric_limits::signaling_NaN(), floating_type(-1)); + } + else + { + return err(make_error_info("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: NaN is not supported")); + } + } + else + { + // set precision + const auto has_sign = ! str.empty() && (str.front() == '+' || str.front() == '-'); + const auto decpoint = std::find(str.begin(), str.end(), '.'); + const auto exponent = std::find_if(str.begin(), str.end(), + [](const char c) { return c == 'e' || c == 'E'; }); + if(decpoint != str.end() && exponent != str.end()) + { + assert(decpoint < exponent); + } + + if(fmt.fmt == floating_format::scientific) + { + // total width + fmt.prec = static_cast(std::distance(str.begin(), exponent)); + if(has_sign) + { + fmt.prec -= 1; + } + if(decpoint != str.end()) + { + fmt.prec -= 1; + } + } + else if(fmt.fmt == floating_format::hex) + { + fmt.prec = std::numeric_limits::max_digits10; + } + else + { + // width after decimal point + fmt.prec = static_cast(std::distance(std::next(decpoint), exponent)); + } + + auto src = source_location(region(loc)); + const auto res = TC::parse_float(str, src, is_hex); + if(res.is_ok()) + { + val = res.as_ok(); + } + else + { + return err(res.as_err()); + } + } + + // ---------------------------------------------------------------------- + // parse suffix (extension) + + if(spec.ext_num_suffix && loc.current() == '_') + { + const auto sfx_reg = syntax::num_suffix(spec).scan(loc); + if( ! sfx_reg.is_ok()) + { + auto src = source_location(region(loc)); + loc = first; + return err(make_error_info("toml::parse_floating: " + "invalid suffix: should be `_ non-digit-graph (graph | _graph)`", + std::move(src), "here")); + } + auto sfx = sfx_reg.as_string(); + assert( ! sfx.empty() && sfx.front() == '_'); + sfx.erase(sfx.begin()); // remove the first `_` + + fmt.suffix = sfx; + } + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); +} + +/* ============================================================================ + * ___ _ _ _ + * | \ __ _| |_ ___| |_(_)_ __ ___ + * | |) / _` | _/ -_) _| | ' \/ -_) + * |___/\__,_|\__\___|\__|_|_|_|_\___| + */ + +// all the offset_datetime, local_datetime, local_date parses date part. +template +result, error_info> +parse_local_date_only(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + local_date_format_info fmt; + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::local_date(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_local_date: " + "invalid date: date must be like: 1234-05-06, yyyy-mm-dd.", + syntax::local_date(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + const auto str = reg.as_string(); + + // 0123456789 + // yyyy-mm-dd + const auto year_r = from_string(str.substr(0, 4)); + const auto month_r = from_string(str.substr(5, 2)); + const auto day_r = from_string(str.substr(8, 2)); + + if(year_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: " + "failed to read year `" + str.substr(0, 4) + "`", + std::move(src), "here")); + } + if(month_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: " + "failed to read month `" + str.substr(5, 2) + "`", + std::move(src), "here")); + } + if(day_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: " + "failed to read day `" + str.substr(8, 2) + "`", + std::move(src), "here")); + } + + const auto year = year_r.unwrap(); + const auto month = month_r.unwrap(); + const auto day = day_r.unwrap(); + + { + // We briefly check whether the input date is valid or not. + // Actually, because of the historical reasons, there are several + // edge cases, such as 1582/10/5-1582/10/14 (only in several countries). + // But here, we do not care about it. + // It makes the code complicated and there is only low probability + // that such a specific date is needed in practice. If someone need to + // validate date accurately, that means that the one need a specialized + // library for their purpose in another layer. + + const bool is_leap = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); + const auto max_day = [month, is_leap]() { + if(month == 2) + { + return is_leap ? 29 : 28; + } + if(month == 4 || month == 6 || month == 9 || month == 11) + { + return 30; + } + return 31; + }(); + + if((month < 1 || 12 < month) || (day < 1 || max_day < day)) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: invalid date.", + std::move(src), "month must be 01-12, day must be any of " + "01-28,29,30,31 depending on the month/year.")); + } + } + + return ok(std::make_tuple( + local_date(year, static_cast(month - 1), day), + std::move(fmt), std::move(reg) + )); +} + +template +result, error_info> +parse_local_date(location& loc, const context& ctx) +{ + auto val_fmt_reg = parse_local_date_only(loc, ctx); + if(val_fmt_reg.is_err()) + { + return err(val_fmt_reg.unwrap_err()); + } + + auto val = std::move(std::get<0>(val_fmt_reg.unwrap())); + auto fmt = std::move(std::get<1>(val_fmt_reg.unwrap())); + auto reg = std::move(std::get<2>(val_fmt_reg.unwrap())); + + return ok(basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); +} + +// all the offset_datetime, local_datetime, local_time parses date part. +template +result, error_info> +parse_local_time_only(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + local_time_format_info fmt; + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::local_time(spec).scan(loc); + if( ! reg.is_ok()) + { + if(spec.v1_1_0_make_seconds_optional) + { + return err(make_syntax_error("toml::parse_local_time: " + "invalid time: time must be HH:MM(:SS.sss) (seconds are optional)", + syntax::local_time(spec), loc)); + } + else + { + return err(make_syntax_error("toml::parse_local_time: " + "invalid time: time must be HH:MM:SS(.sss) (subseconds are optional)", + syntax::local_time(spec), loc)); + } + } + + // ---------------------------------------------------------------------- + // it matches. gen value + const auto str = reg.as_string(); + + // at least we have HH:MM. + // 01234 + // HH:MM + const auto hour_r = from_string(str.substr(0, 2)); + const auto minute_r = from_string(str.substr(3, 2)); + + if(hour_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read hour `" + str.substr(0, 2) + "`", + std::move(src), "here")); + } + if(minute_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read minute `" + str.substr(3, 2) + "`", + std::move(src), "here")); + } + + const auto hour = hour_r.unwrap(); + const auto minute = minute_r.unwrap(); + + if((hour < 0 || 24 <= hour) || (minute < 0 || 60 <= minute)) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: invalid time.", + std::move(src), "hour must be 00-23, minute must be 00-59.")); + } + + // ----------------------------------------------------------------------- + // we have hour and minute. + // Since toml v1.1.0, second and subsecond part becomes optional. + // Check the version and return if second does not exist. + + if(str.size() == 5 && spec.v1_1_0_make_seconds_optional) + { + fmt.has_seconds = false; + fmt.subsecond_precision = 0; + return ok(std::make_tuple(local_time(hour, minute, 0), std::move(fmt), std::move(reg))); + } + assert(str.at(5) == ':'); + + // we have at least `:SS` part. `.subseconds` are optional. + + // 0 1 + // 012345678901234 + // HH:MM:SS.subsec + const auto sec_r = from_string(str.substr(6, 2)); + if(sec_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read second `" + str.substr(6, 2) + "`", + std::move(src), "here")); + } + const auto sec = sec_r.unwrap(); + + if(sec < 0 || 60 < sec) // :60 is allowed + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: invalid time.", + std::move(src), "second must be 00-60.")); + } + + if(str.size() == 8) + { + fmt.has_seconds = true; + fmt.subsecond_precision = 0; + return ok(std::make_tuple(local_time(hour, minute, sec), std::move(fmt), std::move(reg))); + } + + assert(str.at(8) == '.'); + + auto secfrac = str.substr(9, str.size() - 9); + + fmt.has_seconds = true; + fmt.subsecond_precision = secfrac.size(); + + while(secfrac.size() < 9) + { + secfrac += '0'; + } + assert(9 <= secfrac.size()); + const auto ms_r = from_string(secfrac.substr(0, 3)); + const auto us_r = from_string(secfrac.substr(3, 3)); + const auto ns_r = from_string(secfrac.substr(6, 3)); + + if(ms_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read milliseconds `" + secfrac.substr(0, 3) + "`", + std::move(src), "here")); + } + if(us_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read microseconds`" + str.substr(3, 3) + "`", + std::move(src), "here")); + } + if(ns_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read nanoseconds`" + str.substr(6, 3) + "`", + std::move(src), "here")); + } + const auto ms = ms_r.unwrap(); + const auto us = us_r.unwrap(); + const auto ns = ns_r.unwrap(); + + return ok(std::make_tuple(local_time(hour, minute, sec, ms, us, ns), std::move(fmt), std::move(reg))); +} + +template +result, error_info> +parse_local_time(location& loc, const context& ctx) +{ + const auto first = loc; + + auto val_fmt_reg = parse_local_time_only(loc, ctx); + if(val_fmt_reg.is_err()) + { + return err(val_fmt_reg.unwrap_err()); + } + + auto val = std::move(std::get<0>(val_fmt_reg.unwrap())); + auto fmt = std::move(std::get<1>(val_fmt_reg.unwrap())); + auto reg = std::move(std::get<2>(val_fmt_reg.unwrap())); + + return ok(basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); +} + +template +result, error_info> +parse_local_datetime(location& loc, const context& ctx) +{ + using char_type = location::char_type; + + const auto first = loc; + + local_datetime_format_info fmt; + + // ---------------------------------------------------------------------- + + auto date_fmt_reg = parse_local_date_only(loc, ctx); + if(date_fmt_reg.is_err()) + { + return err(date_fmt_reg.unwrap_err()); + } + + if(loc.current() == char_type('T')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::upper_T; + } + else if(loc.current() == char_type('t')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::lower_t; + } + else if(loc.current() == char_type(' ')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::space; + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_local_datetime: " + "expect date-time delimiter `T`, `t` or ` `(space).", + std::move(src), "here")); + } + + auto time_fmt_reg = parse_local_time_only(loc, ctx); + if(time_fmt_reg.is_err()) + { + return err(time_fmt_reg.unwrap_err()); + } + + fmt.has_seconds = std::get<1>(time_fmt_reg.unwrap()).has_seconds; + fmt.subsecond_precision = std::get<1>(time_fmt_reg.unwrap()).subsecond_precision; + + // ---------------------------------------------------------------------- + + region reg(first, loc); + local_datetime val(std::get<0>(date_fmt_reg.unwrap()), + std::get<0>(time_fmt_reg.unwrap())); + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); +} + +template +result, error_info> +parse_offset_datetime(location& loc, const context& ctx) +{ + using char_type = location::char_type; + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + offset_datetime_format_info fmt; + + // ---------------------------------------------------------------------- + // date part + + auto date_fmt_reg = parse_local_date_only(loc, ctx); + if(date_fmt_reg.is_err()) + { + return err(date_fmt_reg.unwrap_err()); + } + + // ---------------------------------------------------------------------- + // delimiter + + if(loc.current() == char_type('T')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::upper_T; + } + else if(loc.current() == char_type('t')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::lower_t; + } + else if(loc.current() == char_type(' ')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::space; + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_offset_datetime: " + "expect date-time delimiter `T` or ` `(space).", std::move(src), "here" + )); + } + + // ---------------------------------------------------------------------- + // time part + + auto time_fmt_reg = parse_local_time_only(loc, ctx); + if(time_fmt_reg.is_err()) + { + return err(time_fmt_reg.unwrap_err()); + } + + fmt.has_seconds = std::get<1>(time_fmt_reg.unwrap()).has_seconds; + fmt.subsecond_precision = std::get<1>(time_fmt_reg.unwrap()).subsecond_precision; + + // ---------------------------------------------------------------------- + // offset part + + const auto ofs_reg = syntax::time_offset(spec).scan(loc); + if( ! ofs_reg.is_ok()) + { + return err(make_syntax_error("toml::parse_offset_datetime: " + "invalid offset: offset must be like: Z, +01:00, or -10:00.", + syntax::time_offset(spec), loc)); + } + + const auto ofs_str = ofs_reg.as_string(); + + time_offset offset(0, 0); + + assert(ofs_str.size() != 0); + + if(ofs_str.at(0) == char_type('+') || ofs_str.at(0) == char_type('-')) + { + const auto hour_r = from_string(ofs_str.substr(1, 2)); + const auto minute_r = from_string(ofs_str.substr(4, 2)); + if(hour_r.is_err()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_offset_datetime: " + "Failed to read offset hour part", std::move(src), "here")); + } + if(minute_r.is_err()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_offset_datetime: " + "Failed to read offset minute part", std::move(src), "here")); + } + const auto hour = hour_r.unwrap(); + const auto minute = minute_r.unwrap(); + + if(ofs_str.at(0) == '+') + { + offset = time_offset(hour, minute); + } + else + { + offset = time_offset(-hour, -minute); + } + } + else + { + assert(ofs_str.at(0) == char_type('Z') || ofs_str.at(0) == char_type('z')); + } + + if (offset.hour < -24 || 24 < offset.hour || + offset.minute < -60 || 60 < offset.minute) + { + return err(make_error_info("toml::parse_offset_datetime: " + "too large offset: |hour| <= 24, |minute| <= 60", + source_location(region(first, loc)), "here")); + } + + + // ---------------------------------------------------------------------- + + region reg(first, loc); + offset_datetime val(local_datetime(std::get<0>(date_fmt_reg.unwrap()), + std::get<0>(time_fmt_reg.unwrap())), + offset); + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); +} + +/* ============================================================================ + * ___ _ _ + * / __| |_ _ _(_)_ _ __ _ + * \__ \ _| '_| | ' \/ _` | + * |___/\__|_| |_|_||_\__, | + * |___/ + */ + +template +result::string_type, error_info> +parse_utf8_codepoint(const region& reg) +{ + using string_type = typename basic_value::string_type; + using char_type = typename string_type::value_type; + + // assert(reg.as_lines().size() == 1); // XXX heavy check + + const auto str = reg.as_string(); + assert( ! str.empty()); + assert(str.front() == 'u' || str.front() == 'U' || str.front() == 'x'); + + std::uint_least32_t codepoint; + std::istringstream iss(str.substr(1)); + iss >> std::hex >> codepoint; + + const auto to_char = [](const std::uint_least32_t i) noexcept -> char_type { + const auto uc = static_cast(i & 0xFF); + return cxx::bit_cast(uc); + }; + + string_type character; + if(codepoint < 0x80) // U+0000 ... U+0079 ; just an ASCII. + { + character += static_cast(codepoint); + } + else if(codepoint < 0x800) //U+0080 ... U+07FF + { + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + character += to_char(0xC0|(codepoint >> 6 )); + character += to_char(0x80|(codepoint & 0x3F)); + } + else if(codepoint < 0x10000) // U+0800...U+FFFF + { + if(0xD800 <= codepoint && codepoint <= 0xDFFF) + { + auto src = source_location(reg); + return err(make_error_info("toml::parse_utf8_codepoint: " + "[0xD800, 0xDFFF] is not a valid UTF-8", + std::move(src), "here")); + } + assert(codepoint < 0xD800 || 0xDFFF < codepoint); + // 1110yyyy 10yxxxxx 10xxxxxx + character += to_char(0xE0| (codepoint >> 12)); + character += to_char(0x80|((codepoint >> 6 ) & 0x3F)); + character += to_char(0x80|((codepoint ) & 0x3F)); + } + else if(codepoint < 0x110000) // U+010000 ... U+10FFFF + { + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + character += to_char(0xF0| (codepoint >> 18)); + character += to_char(0x80|((codepoint >> 12) & 0x3F)); + character += to_char(0x80|((codepoint >> 6 ) & 0x3F)); + character += to_char(0x80|((codepoint ) & 0x3F)); + } + else // out of UTF-8 region + { + auto src = source_location(reg); + return err(make_error_info("toml::parse_utf8_codepoint: " + "input codepoint is too large.", + std::move(src), "must be in range [0x00, 0x10FFFF]")); + } + return ok(character); +} + +template +result::string_type, error_info> +parse_escape_sequence(location& loc, const context& ctx) +{ + using string_type = typename basic_value::string_type; + using char_type = typename string_type::value_type; + + const auto& spec = ctx.toml_spec(); + + assert( ! loc.eof()); + assert(loc.current() == '\\'); + loc.advance(); // consume the first backslash + + string_type retval; + + if (loc.current() == '\\') { retval += char_type('\\'); loc.advance(); } + else if(loc.current() == '"') { retval += char_type('\"'); loc.advance(); } + else if(loc.current() == 'b') { retval += char_type('\b'); loc.advance(); } + else if(loc.current() == 'f') { retval += char_type('\f'); loc.advance(); } + else if(loc.current() == 'n') { retval += char_type('\n'); loc.advance(); } + else if(loc.current() == 'r') { retval += char_type('\r'); loc.advance(); } + else if(loc.current() == 't') { retval += char_type('\t'); loc.advance(); } + else if(spec.v1_1_0_add_escape_sequence_e && loc.current() == 'e') + { + retval += char_type('\x1b'); + loc.advance(); + } + else if(spec.v1_1_0_add_escape_sequence_x && loc.current() == 'x') + { + const auto reg = syntax::escaped_x2(spec).scan(loc); + if( ! reg.is_ok()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_escape_sequence: " + "invalid token found in UTF-8 codepoint \\xhh", + std::move(src), "here")); + } + const auto utf8 = parse_utf8_codepoint(reg); + if(utf8.is_err()) + { + return err(utf8.as_err()); + } + retval += utf8.unwrap(); + } + else if(loc.current() == 'u') + { + const auto reg = syntax::escaped_u4(spec).scan(loc); + if( ! reg.is_ok()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_escape_sequence: " + "invalid token found in UTF-8 codepoint \\uhhhh", + std::move(src), "here")); + } + const auto utf8 = parse_utf8_codepoint(reg); + if(utf8.is_err()) + { + return err(utf8.as_err()); + } + retval += utf8.unwrap(); + } + else if(loc.current() == 'U') + { + const auto reg = syntax::escaped_U8(spec).scan(loc); + if( ! reg.is_ok()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_escape_sequence: " + "invalid token found in UTF-8 codepoint \\Uhhhhhhhh", + std::move(src), "here")); + } + const auto utf8 = parse_utf8_codepoint(reg); + if(utf8.is_err()) + { + return err(utf8.as_err()); + } + retval += utf8.unwrap(); + } + else + { + auto src = source_location(region(loc)); + std::string escape_seqs = "allowed escape seqs: \\\\, \\\", \\b, \\f, \\n, \\r, \\t"; + if(spec.v1_1_0_add_escape_sequence_e) + { + escape_seqs += ", \\e"; + } + if(spec.v1_1_0_add_escape_sequence_x) + { + escape_seqs += ", \\xhh"; + } + escape_seqs += ", \\uhhhh, or \\Uhhhhhhhh"; + + return err(make_error_info("toml::parse_escape_sequence: " + "unknown escape sequence.", std::move(src), escape_seqs)); + } + return ok(retval); +} + +template +result, error_info> +parse_ml_basic_string(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + string_format_info fmt; + fmt.fmt = string_format::multiline_basic; + + auto reg = syntax::ml_basic_string(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_ml_basic_string: " + "invalid string format", + syntax::ml_basic_string(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + // we already checked that it starts with """ and ends with """. + assert(str.substr(0, 3) == "\"\"\""); + str.erase(0, 3); + + assert(str.size() >= 3); + assert(str.substr(str.size()-3, 3) == "\"\"\""); + str.erase(str.size()-3, 3); + + // the first newline just after """ is trimmed + if(str.size() >= 1 && str.at(0) == '\n') + { + str.erase(0, 1); + fmt.start_with_newline = true; + } + else if(str.size() >= 2 && str.at(0) == '\r' && str.at(1) == '\n') + { + str.erase(0, 2); + fmt.start_with_newline = true; + } + + using string_type = typename basic_value::string_type; + string_type val; + { + auto iter = str.cbegin(); + while(iter != str.cend()) + { + if(*iter == '\\') // remove whitespaces around escaped-newline + { + // we assume that the string is not too long to copy + auto loc2 = make_temporary_location(make_string(iter, str.cend())); + if(syntax::escaped_newline(spec).scan(loc2).is_ok()) + { + std::advance(iter, loc2.get_location()); // skip escaped newline and indent + // now iter points non-WS char + assert(iter == str.end() || (*iter != ' ' && *iter != '\t')); + } + else // normal escape seq. + { + auto esc = parse_escape_sequence(loc2, ctx); + + // syntax does not check its value. the unicode codepoint may be + // invalid, e.g. out-of-bound, [0xD800, 0xDFFF] + if(esc.is_err()) + { + return err(esc.unwrap_err()); + } + + val += esc.unwrap(); + std::advance(iter, loc2.get_location()); + } + } + else // we already checked the syntax. we don't need to check it again. + { + val += static_cast(*iter); + ++iter; + } + } + } + + return ok(basic_value( + std::move(val), std::move(fmt), {}, std::move(reg) + )); +} + +template +result::string_type, region>, error_info> +parse_basic_string_only(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::basic_string(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_basic_string: " + "invalid string format", + syntax::basic_string(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + assert(str.back() == '\"'); + str.pop_back(); + assert(str.at(0) == '\"'); + str.erase(0, 1); + + using string_type = typename basic_value::string_type; + using char_type = typename string_type::value_type; + string_type val; + + { + auto iter = str.begin(); + while(iter != str.end()) + { + if(*iter == '\\') + { + auto loc2 = make_temporary_location(make_string(iter, str.end())); + + auto esc = parse_escape_sequence(loc2, ctx); + + // syntax does not check its value. the unicode codepoint may be + // invalid, e.g. out-of-bound, [0xD800, 0xDFFF] + if(esc.is_err()) + { + return err(esc.unwrap_err()); + } + + val += esc.unwrap(); + std::advance(iter, loc2.get_location()); + } + else + { + val += char_type(*iter); // we already checked the syntax. + ++iter; + } + } + } + return ok(std::make_pair(val, reg)); +} + +template +result, error_info> +parse_basic_string(location& loc, const context& ctx) +{ + const auto first = loc; + + string_format_info fmt; + fmt.fmt = string_format::basic; + + auto val_res = parse_basic_string_only(loc, ctx); + if(val_res.is_err()) + { + return err(std::move(val_res.unwrap_err())); + } + auto val = std::move(val_res.unwrap().first ); + auto reg = std::move(val_res.unwrap().second); + + return ok(basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); +} + +template +result, error_info> +parse_ml_literal_string(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + string_format_info fmt; + fmt.fmt = string_format::multiline_literal; + + auto reg = syntax::ml_literal_string(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_ml_literal_string: " + "invalid string format", + syntax::ml_literal_string(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + assert(str.substr(0, 3) == "'''"); + assert(str.substr(str.size()-3, 3) == "'''"); + str.erase(0, 3); + str.erase(str.size()-3, 3); + + // the first newline just after """ is trimmed + if(str.size() >= 1 && str.at(0) == '\n') + { + str.erase(0, 1); + fmt.start_with_newline = true; + } + else if(str.size() >= 2 && str.at(0) == '\r' && str.at(1) == '\n') + { + str.erase(0, 2); + fmt.start_with_newline = true; + } + + using string_type = typename basic_value::string_type; + string_type val(str.begin(), str.end()); + + return ok(basic_value( + std::move(val), std::move(fmt), {}, std::move(reg) + )); +} + +template +result::string_type, region>, error_info> +parse_literal_string_only(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::literal_string(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_literal_string: " + "invalid string format", + syntax::literal_string(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + assert(str.back() == '\''); + str.pop_back(); + assert(str.at(0) == '\''); + str.erase(0, 1); + + using string_type = typename basic_value::string_type; + string_type val(str.begin(), str.end()); + + return ok(std::make_pair(std::move(val), std::move(reg))); +} + +template +result, error_info> +parse_literal_string(location& loc, const context& ctx) +{ + const auto first = loc; + + string_format_info fmt; + fmt.fmt = string_format::literal; + + auto val_res = parse_literal_string_only(loc, ctx); + if(val_res.is_err()) + { + return err(std::move(val_res.unwrap_err())); + } + auto val = std::move(val_res.unwrap().first ); + auto reg = std::move(val_res.unwrap().second); + + return ok(basic_value( + std::move(val), std::move(fmt), {}, std::move(reg) + )); +} + +template +result, error_info> +parse_string(location& loc, const context& ctx) +{ + const auto first = loc; + + if( ! loc.eof() && loc.current() == '"') + { + if(literal("\"\"\"").scan(loc).is_ok()) + { + loc = first; + return parse_ml_basic_string(loc, ctx); + } + else + { + loc = first; + return parse_basic_string(loc, ctx); + } + } + else if( ! loc.eof() && loc.current() == '\'') + { + if(literal("'''").scan(loc).is_ok()) + { + loc = first; + return parse_ml_literal_string(loc, ctx); + } + else + { + loc = first; + return parse_literal_string(loc, ctx); + } + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_string: " + "not a string", std::move(src), "here")); + } +} + +template +result, error_info> +parse_null(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + if( ! spec.ext_null_value) + { + return err(make_error_info("toml::parse_null: " + "invalid spec: spec.ext_null_value must be true.", + source_location(region(loc)), "here")); + } + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::null_value(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_null: " + "invalid null: null must be lowercase. ", + syntax::null_value(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + // ---------------------------------------------------------------------- + // no format info for boolean + + return ok(basic_value(detail::none_t{}, std::move(reg))); +} + +/* ============================================================================ + * _ __ + * | |/ /___ _ _ + * | ' +result::key_type, error_info> +parse_simple_key(location& loc, const context& ctx) +{ + using key_type = typename basic_value::key_type; + const auto& spec = ctx.toml_spec(); + + if(loc.current() == '\"') + { + auto str_res = parse_basic_string_only(loc, ctx); + if(str_res.is_ok()) + { + return ok(std::move(str_res.unwrap().first)); + } + else + { + return err(std::move(str_res.unwrap_err())); + } + } + else if(loc.current() == '\'') + { + auto str_res = parse_literal_string_only(loc, ctx); + if(str_res.is_ok()) + { + return ok(std::move(str_res.unwrap().first)); + } + else + { + return err(std::move(str_res.unwrap_err())); + } + } + + // bare key. + + if(const auto bare = syntax::unquoted_key(spec).scan(loc)) + { + return ok(string_conv(bare.as_string())); + } + else + { + std::string postfix; + if(spec.v1_1_0_allow_non_english_in_bare_keys) + { + postfix = "Hint: Not all Unicode characters are allowed as bare key.\n"; + } + else + { + postfix = "Hint: non-ASCII scripts are allowed in toml v1.1.0, but not in v1.0.0.\n"; + } + return err(make_syntax_error("toml::parse_simple_key: " + "invalid key: key must be \"quoted\", 'quoted-literal', or bare key.", + syntax::unquoted_key(spec), loc, postfix)); + } +} + +// dotted key become vector of keys +template +result::key_type>, region>, error_info> +parse_key(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + using key_type = typename basic_value::key_type; + std::vector keys; + while( ! loc.eof()) + { + auto key = parse_simple_key(loc, ctx); + if( ! key.is_ok()) + { + return err(key.unwrap_err()); + } + keys.push_back(std::move(key.unwrap())); + + auto reg = syntax::dot_sep(spec).scan(loc); + if( ! reg.is_ok()) + { + break; + } + } + if(keys.empty()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_key: expected a new key, " + "but got nothing", std::move(src), "reached EOF")); + } + + return ok(std::make_pair(std::move(keys), region(first, loc))); +} + +// ============================================================================ + +// forward-decl to implement parse_array and parse_table +template +result, error_info> +parse_value(location&, context& ctx); + +template +result::key_type>, region>, + basic_value + >, error_info> +parse_key_value_pair(location& loc, context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto key_res = parse_key(loc, ctx); + if(key_res.is_err()) + { + loc = first; + return err(key_res.unwrap_err()); + } + + if( ! syntax::keyval_sep(spec).scan(loc).is_ok()) + { + auto e = make_syntax_error("toml::parse_key_value_pair: " + "invalid key value separator `=`", syntax::keyval_sep(spec), loc); + loc = first; + return err(std::move(e)); + } + + auto v_res = parse_value(loc, ctx); + if(v_res.is_err()) + { + // loc = first; + return err(v_res.unwrap_err()); + } + return ok(std::make_pair(std::move(key_res.unwrap()), std::move(v_res.unwrap()))); +} + +/* ============================================================================ + * __ _ _ _ _ _ __ _ _ _ + * / _` | '_| '_/ _` | || | + * \__,_|_| |_| \__,_|\_, | + * |__/ + */ + +// array(and multiline inline table with `{` and `}`) has the following format. +// `[` +// (ws|newline|comment-line)? (value) (ws|newline|comment-line)? `,` +// (ws|newline|comment-line)? (value) (ws|newline|comment-line)? `,` +// ... +// (ws|newline|comment-line)? (value) (ws|newline|comment-line)? (`,`)? +// (ws|newline|comment-line)? `]` +// it skips (ws|newline|comment-line) and returns the token. +template +struct multiline_spacer +{ + using comment_type = typename TC::comment_type; + bool newline_found; + indent_char indent_type; + std::int32_t indent; + comment_type comments; +}; +template +std::ostream& operator<<(std::ostream& os, const multiline_spacer& sp) +{ + os << "{newline=" << sp.newline_found << ", "; + os << "indent_type=" << sp.indent_type << ", "; + os << "indent=" << sp.indent << ", "; + os << "comments=" << sp.comments.size() << "}"; + return os; +} + +template +cxx::optional> +skip_multiline_spacer(location& loc, context& ctx, const bool newline_found = false) +{ + const auto& spec = ctx.toml_spec(); + + multiline_spacer spacer; + spacer.newline_found = newline_found; + spacer.indent_type = indent_char::none; + spacer.indent = 0; + spacer.comments.clear(); + + bool spacer_found = false; + while( ! loc.eof()) + { + if(auto comm = sequence(syntax::comment(spec), syntax::newline(spec)).scan(loc)) + { + spacer.newline_found = true; + auto comment = comm.as_string(); + if( ! comment.empty() && comment.back() == '\n') + { + comment.pop_back(); + if (!comment.empty() && comment.back() == '\r') + { + comment.pop_back(); + } + } + + spacer.comments.push_back(std::move(comment)); + spacer.indent_type = indent_char::none; + spacer.indent = 0; + spacer_found = true; + } + else if(auto nl = syntax::newline(spec).scan(loc)) + { + spacer.newline_found = true; + spacer.comments.clear(); + spacer.indent_type = indent_char::none; + spacer.indent = 0; + spacer_found = true; + } + else if(auto sp = repeat_at_least(1, character(cxx::bit_cast(' '))).scan(loc)) + { + spacer.indent_type = indent_char::space; + spacer.indent = static_cast(sp.length()); + spacer_found = true; + } + else if(auto tabs = repeat_at_least(1, character(cxx::bit_cast('\t'))).scan(loc)) + { + spacer.indent_type = indent_char::tab; + spacer.indent = static_cast(tabs.length()); + spacer_found = true; + } + else + { + break; // done + } + } + if( ! spacer_found) + { + return cxx::make_nullopt(); + } + return spacer; +} + +// not an [[array.of.tables]]. It parses ["this", "type"] +template +result, error_info> +parse_array(location& loc, context& ctx) +{ + const auto num_errors = ctx.errors().size(); + + const auto first = loc; + + if(loc.eof() || loc.current() != '[') + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_array: " + "The next token is not an array", std::move(src), "here")); + } + loc.advance(); + + typename basic_value::array_type val; + + array_format_info fmt; + fmt.fmt = array_format::oneline; + fmt.indent_type = indent_char::none; + + auto spacer = skip_multiline_spacer(loc, ctx); + if(spacer.has_value() && spacer.value().newline_found) + { + fmt.fmt = array_format::multiline; + } + + bool comma_found = true; + while( ! loc.eof()) + { + if(loc.current() == location::char_type(']')) + { + if(spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.closing_indent = spacer.value().indent; + } + break; + } + + if( ! comma_found) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_array: " + "expected value-separator `,` or closing `]`", + std::move(src), "here")); + } + + if(spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.body_indent = spacer.value().indent; + } + + if(auto elem_res = parse_value(loc, ctx)) + { + auto elem = std::move(elem_res.unwrap()); + + if(spacer.has_value()) // copy previous comments to value + { + elem.comments() = std::move(spacer.value().comments); + } + + // parse spaces between a value and a comma + // array = [ + // 42 , # the answer + // ^^^^ + // 3.14 # pi + // , 2.71 ^^^^ + // ^^ + spacer = skip_multiline_spacer(loc, ctx); + if(spacer.has_value()) + { + for(std::size_t i=0; i( + std::move(val), std::move(fmt), {}, region(first, loc) + )); +} + +/* ============================================================================ + * _ _ _ _ _ _ + * (_)_ _ | (_)_ _ ___ | |_ __ _| |__| |___ + * | | ' \| | | ' \/ -_) | _/ _` | '_ \ / -_) + * |_|_||_|_|_|_||_\___| \__\__,_|_.__/_\___| + */ + +// ---------------------------------------------------------------------------- +// insert_value is the most complicated part of the toml spec. +// +// To parse a toml file correctly, we sometimes need to check an exising value +// is appendable or not. +// +// For example while parsing an inline array of tables, +// +// ```toml +// aot = [ +// {a = "foo"}, +// {a = "bar", b = "baz"}, +// ] +// ``` +// +// this `aot` is appendable until parser reaches to `]`. After that, it becomes +// non-appendable. +// +// On the other hand, a normal array of tables, such as +// +// ```toml +// [[aot]] +// a = "foo" +// +// [[aot]] +// a = "bar" +// b = "baz" +// ``` +// This `[[aot]]` is appendable until the parser reaches to the EOF. +// +// +// It becomes a bit more difficult in case of dotted keys. +// In TOML, it is allowed to append a key-value pair to a table that is +// *implicitly* defined by a subtable definitino. +// +// ```toml +// [x.y.z] +// w = 123 +// +// [x] +// a = "foo" # OK. x is defined implicitly by `[x.y.z]`. +// ``` +// +// But if the table is defined by a dotted keys, it is not appendable. +// +// ```toml +// [x] +// y.z.w = 123 +// +// [x.y] +// # ERROR. x.y is already defined by a dotted table in the previous table. +// ``` +// +// Also, reopening a table using dotted keys is invalid. +// +// ```toml +// [x.y.z] +// w = 123 +// +// [x] +// y.z.v = 42 # ERROR. [x.y.z] is already defined. +// ``` +// +// +// ```toml +// [a] +// b.c = "foo" +// b.d = "bar" +// ``` +// +// +// ```toml +// a.b = "foo" +// [a] +// c = "bar" # ERROR +// ``` +// +// In summary, +// - a table must be defined only once. +// - assignment to an exising table is possible only when: +// - defining a subtable [x.y] to an existing table [x]. +// - defining supertable [x] explicitly after [x.y]. +// - adding dotted keys in the same table. + +enum class inserting_value_kind : std::uint8_t +{ + std_table, // insert [standard.table] + array_table, // insert [[array.of.tables]] + dotted_keys // insert a.b.c = "this" +}; + +template +result*, error_info> +insert_value(const inserting_value_kind kind, + typename basic_value::table_type* current_table_ptr, + const std::vector::key_type>& keys, region key_reg, + basic_value val) +{ + using value_type = basic_value; + using array_type = typename basic_value::array_type; + using table_type = typename basic_value::table_type; + + auto key_loc = source_location(key_reg); + + assert( ! keys.empty()); + + // dotted key can insert to dotted key tables defined at the same level. + // dotted key can NOT reopen a table even if it is implcitly-defined one. + // + // [x.y.z] # define x and x.y implicitly. + // a = 42 + // + // [x] # reopening implcitly defined table + // r.s.t = 3.14 # VALID r and r.s are new tables. + // r.s.u = 2.71 # VALID r and r.s are dotted-key tables. valid. + // + // y.z.b = "foo" # INVALID x.y.z are multiline table, not a dotted key. + // y.c = "bar" # INVALID x.y is implicit multiline table, not a dotted key. + + // a table cannot reopen dotted-key tables. + // + // [t1] + // t2.t3.v = 0 + // [t1.t2] # INVALID t1.t2 is defined as a dotted-key table. + + for(std::size_t i=0; i{}, key_reg)); + + assert(current_table.at(key).is_table()); + current_table_ptr = std::addressof(current_table.at(key).as_table()); + } + else if (found->second.is_table()) + { + const auto fmt = found->second.as_table_fmt().fmt; + if(fmt == table_format::oneline || fmt == table_format::multiline_oneline) + { + // foo = {bar = "baz"} or foo = { \n bar = "baz" \n } + return err(make_error_info("toml::insert_value: " + "failed to insert a value: inline table is immutable", + key_loc, "inserting this", + found->second.location(), "to this table")); + } + // dotted key cannot reopen a table. + if(kind ==inserting_value_kind::dotted_keys && fmt != table_format::dotted) + { + return err(make_error_info("toml::insert_value: " + "reopening a table using dotted keys", + key_loc, "dotted key cannot reopen a table", + found->second.location(), "this table is already closed")); + } + assert(found->second.is_table()); + current_table_ptr = std::addressof(found->second.as_table()); + } + else if(found->second.is_array_of_tables()) + { + // aot = [{this = "type", of = "aot"}] # cannot be reopened + if(found->second.as_array_fmt().fmt != array_format::array_of_tables) + { + return err(make_error_info("toml::insert_value:" + "inline array of tables are immutable", + key_loc, "inserting this", + found->second.location(), "inline array of tables")); + } + // appending to [[aot]] + + if(kind == inserting_value_kind::dotted_keys) + { + // [[array.of.tables]] + // [array.of] # reopening supertable is okay + // tables.x = "foo" # appending `x` to the first table + return err(make_error_info("toml::insert_value:" + "dotted key cannot reopen an array-of-tables", + key_loc, "inserting this", + found->second.location(), "to this array-of-tables.")); + } + + // insert_value_by_dotkeys::std_table + // [[array.of.tables]] + // [array.of.tables.subtable] # appending to the last aot + // + // insert_value_by_dotkeys::array_table + // [[array.of.tables]] + // [[array.of.tables.subtable]] # appending to the last aot + auto& current_array_table = found->second.as_array().back(); + + assert(current_array_table.is_table()); + current_table_ptr = std::addressof(current_array_table.as_table()); + } + else + { + return err(make_error_info("toml::insert_value: " + "failed to insert a value, value already exists", + key_loc, "while inserting this", + found->second.location(), "non-table value already exists")); + } + } + else // this is the last key. insert a new value. + { + switch(kind) + { + case inserting_value_kind::dotted_keys: + { + if(current_table.find(key) != current_table.end()) + { + return err(make_error_info("toml::insert_value: " + "failed to insert a value, value already exists", + key_loc, "inserting this", + current_table.at(key).location(), "but value already exists")); + } + current_table.emplace(key, std::move(val)); + return ok(std::addressof(current_table.at(key))); + } + case inserting_value_kind::std_table: + { + // defining a new table or reopening supertable + auto found = current_table.find(key); + if(found == current_table.end()) // define a new aot + { + current_table.emplace(key, std::move(val)); + return ok(std::addressof(current_table.at(key))); + } + else // the table is already defined, reopen it + { + // assigning a [std.table]. it must be an implicit table. + auto& target = found->second; + if( ! target.is_table() || // could be an array-of-tables + target.as_table_fmt().fmt != table_format::implicit) + { + return err(make_error_info("toml::insert_value: " + "failed to insert a table, table already defined", + key_loc, "inserting this", + target.location(), "this table is explicitly defined")); + } + + // merge table + for(const auto& kv : val.as_table()) + { + if(target.contains(kv.first)) + { + // [x.y.z] + // w = "foo" + // [x] + // y = "bar" + return err(make_error_info("toml::insert_value: " + "failed to insert a table, table keys conflict to each other", + key_loc, "inserting this table", + kv.second.location(), "having this value", + target.at(kv.first).location(), "already defined here")); + } + else + { + target[kv.first] = kv.second; + } + } + // change implicit -> explicit + target.as_table_fmt().fmt = table_format::multiline; + // change definition region + change_region_of_value(target, val); + + return ok(std::addressof(current_table.at(key))); + } + } + case inserting_value_kind::array_table: + { + auto found = current_table.find(key); + if(found == current_table.end()) // define a new aot + { + array_format_info fmt; + fmt.fmt = array_format::array_of_tables; + fmt.indent_type = indent_char::none; + + current_table.emplace(key, value_type( + array_type{ std::move(val) }, std::move(fmt), + std::vector{}, std::move(key_reg) + )); + + assert( ! current_table.at(key).as_array().empty()); + return ok(std::addressof(current_table.at(key).as_array().back())); + } + else // the array is already defined, append to it + { + if( ! found->second.is_array_of_tables()) + { + return err(make_error_info("toml::insert_value: " + "failed to insert an array of tables, value already exists", + key_loc, "while inserting this", + found->second.location(), "non-table value already exists")); + } + if(found->second.as_array_fmt().fmt != array_format::array_of_tables) + { + return err(make_error_info("toml::insert_value: " + "failed to insert a table, inline array of tables is immutable", + key_loc, "while inserting this", + found->second.location(), "this is inline array-of-tables")); + } + found->second.as_array().push_back(std::move(val)); + assert( ! current_table.at(key).as_array().empty()); + return ok(std::addressof(current_table.at(key).as_array().back())); + } + } + default: {assert(false);} + } + } + } + return err(make_error_info("toml::insert_key: no keys found", + std::move(key_loc), "here")); +} + +// ---------------------------------------------------------------------------- + +template +result, error_info> +parse_inline_table(location& loc, context& ctx) +{ + using table_type = typename basic_value::table_type; + + const auto num_errors = ctx.errors().size(); + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + if(loc.eof() || loc.current() != '{') + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: " + "The next token is not an inline table", std::move(src), "here")); + } + loc.advance(); + + table_type table; + table_format_info fmt; + fmt.fmt = table_format::oneline; + fmt.indent_type = indent_char::none; + + cxx::optional> spacer(cxx::make_nullopt()); + + if(spec.v1_1_0_allow_newlines_in_inline_tables) + { + spacer = skip_multiline_spacer(loc, ctx); + if(spacer.has_value() && spacer.value().newline_found) + { + fmt.fmt = table_format::multiline_oneline; + } + } + else + { + skip_whitespace(loc, ctx); + } + + bool still_empty = true; + bool comma_found = false; + while( ! loc.eof()) + { + // closing! + if(loc.current() == '}') + { + if(comma_found && ! spec.v1_1_0_allow_trailing_comma_in_inline_tables) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: trailing " + "comma is not allowed in TOML-v1.0.0)", std::move(src), "here")); + } + + if(spec.v1_1_0_allow_newlines_in_inline_tables) + { + if(spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.closing_indent = spacer.value().indent; + } + } + break; + } + + // if we already found a value and didn't found `,` nor `}`, error. + if( ! comma_found && ! still_empty) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: " + "expected value-separator `,` or closing `}`", + std::move(src), "here")); + } + + // parse indent. + if(spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.body_indent = spacer.value().indent; + } + + still_empty = false; // parsing a value... + if(auto kv_res = parse_key_value_pair(loc, ctx)) + { + auto keys = std::move(kv_res.unwrap().first.first); + auto key_reg = std::move(kv_res.unwrap().first.second); + auto val = std::move(kv_res.unwrap().second); + + auto ins_res = insert_value(inserting_value_kind::dotted_keys, + std::addressof(table), keys, std::move(key_reg), std::move(val)); + if(ins_res.is_err()) + { + ctx.report_error(std::move(ins_res.unwrap_err())); + // we need to skip until the next value (or end of the table) + // because we don't have valid kv pair. + while( ! loc.eof()) + { + const auto c = loc.current(); + if(c == ',' || c == '\n' || c == '}') + { + comma_found = (c == ','); + break; + } + loc.advance(); + } + continue; + } + + // if comment line follows immediately(without newline) after `,`, then + // the comment is for the elem. we need to check if comment follows `,`. + // + // (key) = (val) (ws|newline|comment-line)? `,` (ws)? (comment)? + + if(spec.v1_1_0_allow_newlines_in_inline_tables) + { + if(spacer.has_value()) // copy previous comments to value + { + for(std::size_t i=0; icomments().push_back(spacer.value().comments.at(i)); + } + } + spacer = skip_multiline_spacer(loc, ctx); + if(spacer.has_value()) + { + for(std::size_t i=0; icomments().push_back(spacer.value().comments.at(i)); + } + if(spacer.value().newline_found) + { + fmt.fmt = table_format::multiline_oneline; + if(spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.body_indent = spacer.value().indent; + } + } + } + } + else + { + skip_whitespace(loc, ctx); + } + + comma_found = character(',').scan(loc).is_ok(); + + if(spec.v1_1_0_allow_newlines_in_inline_tables) + { + auto com_res = parse_comment_line(loc, ctx); + if(com_res.is_err()) + { + ctx.report_error(com_res.unwrap_err()); + } + const bool comment_found = com_res.is_ok() && com_res.unwrap().has_value(); + if(comment_found) + { + fmt.fmt = table_format::multiline_oneline; + ins_res.unwrap()->comments().push_back(com_res.unwrap().value()); + } + if(comma_found) + { + spacer = skip_multiline_spacer(loc, ctx, comment_found); + if(spacer.has_value() && spacer.value().newline_found) + { + fmt.fmt = table_format::multiline_oneline; + } + } + } + else + { + skip_whitespace(loc, ctx); + } + } + else + { + ctx.report_error(std::move(kv_res.unwrap_err())); + while( ! loc.eof()) + { + if(loc.current() == '}') + { + break; + } + if( ! spec.v1_1_0_allow_newlines_in_inline_tables && loc.current() == '\n') + { + break; + } + loc.advance(); + } + break; + } + } + + if(loc.current() != '}') + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: " + "missing closing bracket `}`", + std::move(src), "expected `}`, reached line end")); + } + else + { + loc.advance(); // skip } + } + + // any error reported from this function + if(num_errors < ctx.errors().size()) + { + assert(ctx.has_error()); // already reported + return err(ctx.pop_last_error()); + } + + basic_value retval( + std::move(table), std::move(fmt), {}, region(first, loc)); + + return ok(std::move(retval)); +} + +/* ============================================================================ + * _ + * __ ____ _| |_ _ ___ + * \ V / _` | | || / -_) + * \_/\__,_|_|\_,_\___| + */ + +template +result +guess_number_type(const location& first, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + location loc = first; + + if(syntax::offset_datetime(spec).scan(loc).is_ok()) + { + return ok(value_t::offset_datetime); + } + loc = first; + + if(syntax::local_datetime(spec).scan(loc).is_ok()) + { + const auto curr = loc.current(); + // if offset_datetime contains bad offset, it syntax::offset_datetime + // fails to scan it. + if(curr == '+' || curr == '-') + { + return err(make_syntax_error("bad offset: must be [+-]HH:MM or Z", + syntax::time_offset(spec), loc, std::string( + "Hint: valid : +09:00, -05:30\n" + "Hint: invalid: +9:00, -5:30\n"))); + } + return ok(value_t::local_datetime); + } + loc = first; + + if(syntax::local_date(spec).scan(loc).is_ok()) + { + // bad time may appear after this. + + if( ! loc.eof()) + { + const auto c = loc.current(); + if(c == 'T' || c == 't') + { + loc.advance(); + + return err(make_syntax_error("bad time: must be HH:MM:SS.subsec", + syntax::local_time(spec), loc, std::string( + "Hint: valid : 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999\n" + "Hint: invalid: 1979-05-27T7:32:00, 1979-05-27 17:32\n"))); + } + if(c == ' ') + { + // A space is allowed as a delimiter between local time. + // But there is a case where bad time follows a space. + // - invalid: 2019-06-16 7:00:00 + // - valid : 2019-06-16 07:00:00 + loc.advance(); + if( ! loc.eof() && ('0' <= loc.current() && loc.current() <= '9')) + { + return err(make_syntax_error("bad time: must be HH:MM:SS.subsec", + syntax::local_time(spec), loc, std::string( + "Hint: valid : 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999\n" + "Hint: invalid: 1979-05-27T7:32:00, 1979-05-27 17:32\n"))); + } + } + if('0' <= c && c <= '9') + { + return err(make_syntax_error("bad datetime: missing T or space", + character_either("Tt "), loc, std::string( + "Hint: valid : 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999\n" + "Hint: invalid: 1979-05-27T7:32:00, 1979-05-27 17:32\n"))); + } + } + return ok(value_t::local_date); + } + loc = first; + + if(syntax::local_time(spec).scan(loc).is_ok()) + { + return ok(value_t::local_time); + } + loc = first; + + if(syntax::floating(spec).scan(loc).is_ok()) + { + if( ! loc.eof() && loc.current() == '_') + { + if(spec.ext_num_suffix && syntax::num_suffix(spec).scan(loc).is_ok()) + { + return ok(value_t::floating); + } + auto src = source_location(region(loc)); + return err(make_error_info( + "bad float: `_` must be surrounded by digits", + std::move(src), "invalid underscore", + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n")); + } + return ok(value_t::floating); + } + loc = first; + + if(spec.ext_hex_float) + { + if(syntax::hex_floating(spec).scan(loc).is_ok()) + { + if( ! loc.eof() && loc.current() == '_') + { + if(spec.ext_num_suffix && syntax::num_suffix(spec).scan(loc).is_ok()) + { + return ok(value_t::floating); + } + auto src = source_location(region(loc)); + return err(make_error_info( + "bad float: `_` must be surrounded by digits", + std::move(src), "invalid underscore", + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n")); + } + return ok(value_t::floating); + } + loc = first; + } + + if(auto int_reg = syntax::integer(spec).scan(loc)) + { + if( ! loc.eof()) + { + const auto c = loc.current(); + if(c == '_') + { + if(spec.ext_num_suffix && syntax::num_suffix(spec).scan(loc).is_ok()) + { + return ok(value_t::integer); + } + + if(int_reg.length() <= 2 && (int_reg.as_string() == "0" || + int_reg.as_string() == "-0" || int_reg.as_string() == "+0")) + { + auto src = source_location(region(loc)); + return err(make_error_info( + "bad integer: leading zero is not allowed in decimal int", + std::move(src), "leading zero", + "Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n")); + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info( + "bad integer: `_` must be surrounded by digits", + std::move(src), "invalid underscore", + "Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n")); + } + } + if('0' <= c && c <= '9') + { + if(loc.current() == '0') + { + loc.retrace(); + return err(make_error_info( + "bad integer: leading zero", + source_location(region(loc)), "leading zero is not allowed", + std::string("Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n") + )); + } + else // invalid digits, especially in oct/bin ints. + { + return err(make_error_info( + "bad integer: invalid digit after an integer", + source_location(region(loc)), "this digit is not allowed", + std::string("Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n") + )); + } + } + if(c == ':' || c == '-') + { + auto src = source_location(region(loc)); + return err(make_error_info("bad datetime: invalid format", + std::move(src), "here", + std::string("Hint: valid : 1979-05-27T07:32:00-07:00, 1979-05-27 07:32:00.999999Z\n" + "Hint: invalid: 1979-05-27T7:32:00-7:00, 1979-05-27 7:32-00:30") + )); + } + if(c == '.' || c == 'e' || c == 'E') + { + auto src = source_location(region(loc)); + return err(make_error_info("bad float: invalid format", + std::move(src), "here", std::string( + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n"))); + } + } + return ok(value_t::integer); + } + if( ! loc.eof() && loc.current() == '.') + { + auto src = source_location(region(loc)); + return err(make_error_info("bad float: integer part is required before decimal point", + std::move(src), "missing integer part", std::string( + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n") + )); + } + if( ! loc.eof() && loc.current() == '_') + { + auto src = source_location(region(loc)); + return err(make_error_info("bad number: `_` must be surrounded by digits", + std::move(src), "digits required before `_`", std::string( + "Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n") + )); + } + + auto src = source_location(region(loc)); + return err(make_error_info("bad format: unknown value appeared", + std::move(src), "here")); +} + +template +result +guess_value_type(const location& loc, const context& ctx) +{ + const auto& sp = ctx.toml_spec(); + location inner(loc); + + switch(loc.current()) + { + case '"' : {return ok(value_t::string); } + case '\'': {return ok(value_t::string); } + case '[' : {return ok(value_t::array); } + case '{' : {return ok(value_t::table); } + case 't' : + { + return ok(value_t::boolean); + } + case 'f' : + { + return ok(value_t::boolean); + } + case 'T' : // invalid boolean. + { + return err(make_syntax_error("toml::parse_value: " + "`true` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::boolean(sp), inner)); + } + case 'F' : + { + return err(make_syntax_error("toml::parse_value: " + "`false` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::boolean(sp), inner)); + } + case 'i' : // inf or string without quotes(syntax error). + { + if(literal("inf").scan(inner).is_ok()) + { + return ok(value_t::floating); + } + else + { + return err(make_syntax_error("toml::parse_value: " + "`inf` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + } + case 'I' : // Inf or string without quotes(syntax error). + { + return err(make_syntax_error("toml::parse_value: " + "`inf` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + case 'n' : // nan or null-extension + { + if(sp.ext_null_value) + { + if(literal("nan").scan(inner).is_ok()) + { + return ok(value_t::floating); + } + else if(literal("null").scan(inner).is_ok()) + { + return ok(value_t::empty); + } + else + { + return err(make_syntax_error("toml::parse_value: " + "Both `nan` and `null` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + } + else // must be nan. + { + if(literal("nan").scan(inner).is_ok()) + { + return ok(value_t::floating); + } + else + { + return err(make_syntax_error("toml::parse_value: " + "`nan` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + } + } + case 'N' : // nan or null-extension + { + if(sp.ext_null_value) + { + return err(make_syntax_error("toml::parse_value: " + "Both `nan` and `null` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + else + { + return err(make_syntax_error("toml::parse_value: " + "`nan` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + } + default : + { + return guess_number_type(loc, ctx); + } + } +} + +template +result, error_info> +parse_value(location& loc, context& ctx) +{ + const auto ty_res = guess_value_type(loc, ctx); + if(ty_res.is_err()) + { + return err(ty_res.unwrap_err()); + } + + switch(ty_res.unwrap()) + { + case value_t::empty: + { + if(ctx.toml_spec().ext_null_value) + { + return parse_null(loc, ctx); + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_value: unknown value appeared", + std::move(src), "here")); + } + } + case value_t::boolean : {return parse_boolean (loc, ctx);} + case value_t::integer : {return parse_integer (loc, ctx);} + case value_t::floating : {return parse_floating (loc, ctx);} + case value_t::string : {return parse_string (loc, ctx);} + case value_t::offset_datetime: {return parse_offset_datetime(loc, ctx);} + case value_t::local_datetime : {return parse_local_datetime (loc, ctx);} + case value_t::local_date : {return parse_local_date (loc, ctx);} + case value_t::local_time : {return parse_local_time (loc, ctx);} + case value_t::array : {return parse_array (loc, ctx);} + case value_t::table : {return parse_inline_table (loc, ctx);} + default: + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_value: unknown value appeared", + std::move(src), "here")); + } + } +} + +/* ============================================================================ + * _____ _ _ + * |_ _|_ _| |__| |___ + * | |/ _` | '_ \ / -_) + * |_|\__,_|_.__/_\___| + */ + +template +result::key_type>, region>, error_info> +parse_table_key(location& loc, context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::std_table(spec).scan(loc); + if(!reg.is_ok()) + { + return err(make_syntax_error("toml::parse_table_key: invalid table key", + syntax::std_table(spec), loc)); + } + + loc = first; + loc.advance(); // skip [ + skip_whitespace(loc, ctx); + + auto keys_res = parse_key(loc, ctx); + if(keys_res.is_err()) + { + return err(std::move(keys_res.unwrap_err())); + } + + skip_whitespace(loc, ctx); + loc.advance(); // ] + + return ok(std::make_pair(std::move(keys_res.unwrap().first), std::move(reg))); +} + +template +result::key_type>, region>, error_info> +parse_array_table_key(location& loc, context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::array_table(spec).scan(loc); + if(!reg.is_ok()) + { + return err(make_syntax_error("toml::parse_array_table_key: invalid array-of-tables key", + syntax::array_table(spec), loc)); + } + + loc = first; + loc.advance(); // [ + loc.advance(); // [ + skip_whitespace(loc, ctx); + + auto keys_res = parse_key(loc, ctx); + if(keys_res.is_err()) + { + return err(std::move(keys_res.unwrap_err())); + } + + skip_whitespace(loc, ctx); + loc.advance(); // ] + loc.advance(); // ] + + return ok(std::make_pair(std::move(keys_res.unwrap().first), std::move(reg))); +} + +// called after reading [table.keys] and comments around it. +// Since table may already contain a subtable ([x.y.z] can be defined before [x]), +// the table that is being parsed is passed as an argument. +template +result +parse_table(location& loc, context& ctx, basic_value& table) +{ + assert(table.is_table()); + + const auto num_errors = ctx.errors().size(); + const auto& spec = ctx.toml_spec(); + + // clear indent info + table.as_table_fmt().indent_type = indent_char::none; + + bool newline_found = true; + while( ! loc.eof()) + { + const auto start = loc; + + auto sp = skip_multiline_spacer(loc, ctx, newline_found); + + // if reached to EOF, the table ends here. return. + if(loc.eof()) + { + break; + } + // if next table is comming, return. + if(sequence(syntax::ws(spec), character('[')).scan(loc).is_ok()) + { + loc = start; + break; + } + // otherwise, it should be a key-value pair. + newline_found = newline_found || (sp.has_value() && sp.value().newline_found); + if( ! newline_found) + { + return err(make_error_info("toml::parse_table: " + "newline (LF / CRLF) or EOF is expected", + source_location(region(loc)), "here")); + } + if(sp.has_value() && sp.value().indent_type != indent_char::none) + { + table.as_table_fmt().indent_type = sp.value().indent_type; + table.as_table_fmt().body_indent = sp.value().indent; + } + + newline_found = false; // reset + if(auto kv_res = parse_key_value_pair(loc, ctx)) + { + auto keys = std::move(kv_res.unwrap().first.first); + auto key_reg = std::move(kv_res.unwrap().first.second); + auto val = std::move(kv_res.unwrap().second); + + if(sp.has_value()) + { + for(const auto& com : sp.value().comments) + { + val.comments().push_back(com); + } + } + + if(auto com_res = parse_comment_line(loc, ctx)) + { + if(auto com_opt = com_res.unwrap()) + { + val.comments().push_back(com_opt.value()); + newline_found = true; // comment includes newline at the end + } + } + else + { + ctx.report_error(std::move(com_res.unwrap_err())); + } + + auto ins_res = insert_value(inserting_value_kind::dotted_keys, + std::addressof(table.as_table()), + keys, std::move(key_reg), std::move(val)); + if(ins_res.is_err()) + { + ctx.report_error(std::move(ins_res.unwrap_err())); + } + } + else + { + ctx.report_error(std::move(kv_res.unwrap_err())); + skip_key_value_pair(loc, ctx); + } + } + + if(num_errors < ctx.errors().size()) + { + assert(ctx.has_error()); // already reported + return err(ctx.pop_last_error()); + } + return ok(); +} + +template +result, std::vector> +parse_file(location& loc, context& ctx) +{ + using value_type = basic_value; + using table_type = typename value_type::table_type; + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + if(loc.eof()) + { + return ok(value_type(table_type(), table_format_info{}, {}, region(loc))); + } + + value_type root(table_type(), table_format_info{}, {}, region(loc)); + root.as_table_fmt().fmt = table_format::multiline; + root.as_table_fmt().indent_type = indent_char::none; + + // parse top comment. + // + // ```toml + // # this is a comment for the top-level table. + // + // key = "the first value" + // ``` + // + // ```toml + // # this is a comment for "the first value". + // key = "the first value" + // ``` + while( ! loc.eof()) + { + if(auto com_res = parse_comment_line(loc, ctx)) + { + if(auto com_opt = com_res.unwrap()) + { + root.comments().push_back(std::move(com_opt.value())); + } + else // no comment found. + { + // if it is not an empty line, clear the root comment. + if( ! sequence(syntax::ws(spec), syntax::newline(spec)).scan(loc).is_ok()) + { + loc = first; + root.comments().clear(); + } + break; + } + } + else + { + ctx.report_error(std::move(com_res.unwrap_err())); + skip_comment_block(loc, ctx); + } + } + + // parse root table + { + const auto res = parse_table(loc, ctx, root); + if(res.is_err()) + { + ctx.report_error(std::move(res.unwrap_err())); + skip_until_next_table(loc, ctx); + } + } + + // parse tables + + while( ! loc.eof()) + { + auto sp = skip_multiline_spacer(loc, ctx, /*newline_found=*/true); + + if(auto key_res = parse_array_table_key(loc, ctx)) + { + auto key = std::move(std::get<0>(key_res.unwrap())); + auto reg = std::move(std::get<1>(key_res.unwrap())); + + std::vector com; + if(sp.has_value()) + { + for(std::size_t i=0; i(table_type()); + auto res = parse_table(loc, ctx, tmp); + if(res.is_err()) + { + ctx.report_error(res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + continue; + } + + auto tab_ptr = inserted.unwrap(); + assert(tab_ptr); + + const auto tab_res = parse_table(loc, ctx, *tab_ptr); + if(tab_res.is_err()) + { + ctx.report_error(tab_res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + + // parse_table first clears `indent_type`. + // to keep header indent info, we must store it later. + if(sp.has_value() && sp.value().indent_type != indent_char::none) + { + tab_ptr->as_table_fmt().indent_type = sp.value().indent_type; + tab_ptr->as_table_fmt().name_indent = sp.value().indent; + } + continue; + } + if(auto key_res = parse_table_key(loc, ctx)) + { + auto key = std::move(std::get<0>(key_res.unwrap())); + auto reg = std::move(std::get<1>(key_res.unwrap())); + + std::vector com; + if(sp.has_value()) + { + for(std::size_t i=0; i(table_type()); + auto res = parse_table(loc, ctx, tmp); + if(res.is_err()) + { + ctx.report_error(res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + continue; + } + + auto tab_ptr = inserted.unwrap(); + assert(tab_ptr); + + const auto tab_res = parse_table(loc, ctx, *tab_ptr); + if(tab_res.is_err()) + { + ctx.report_error(tab_res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + if(sp.has_value() && sp.value().indent_type != indent_char::none) + { + tab_ptr->as_table_fmt().indent_type = sp.value().indent_type; + tab_ptr->as_table_fmt().name_indent = sp.value().indent; + } + continue; + } + + // does not match array_table nor std_table. report an error. + const auto keytop = loc; + const auto maybe_array_of_tables = literal("[[").scan(loc).is_ok(); + loc = keytop; + + if(maybe_array_of_tables) + { + ctx.report_error(make_syntax_error("toml::parse_file: invalid array-table key", + syntax::array_table(spec), loc)); + } + else + { + ctx.report_error(make_syntax_error("toml::parse_file: invalid table key", + syntax::std_table(spec), loc)); + } + skip_until_next_table(loc, ctx); + } + + if( ! ctx.errors().empty()) + { + return err(std::move(ctx.errors())); + } + +#ifdef TOML11_ENABLE_ACCESS_CHECK + detail::unset_access_flag_recursively(root); +#endif + + return ok(std::move(root)); +} + +template +result, std::vector> +parse_impl(std::vector cs, std::string fname, const spec& s) +{ + using value_type = basic_value; + using table_type = typename value_type::table_type; + + // an empty file is a valid toml file. + if(cs.empty()) + { + auto src = std::make_shared>(std::move(cs)); + location loc(std::move(src), std::move(fname)); + return ok(value_type(table_type(), table_format_info{}, std::vector{}, region(loc))); + } + + // to simplify parser, add newline at the end if there is no LF. + // But, if it has raw CR, the file is invalid (in TOML, CR is not a valid + // newline char). if it ends with CR, do not add LF and report it. + if(cs.back() != '\n' && cs.back() != '\r') + { + cs.push_back('\n'); + } + + auto src = std::make_shared>(std::move(cs)); + + location loc(std::move(src), std::move(fname)); + + // skip BOM if found + if(loc.source()->size() >= 3) + { + auto first = loc; + + const auto c0 = loc.current(); loc.advance(); + const auto c1 = loc.current(); loc.advance(); + const auto c2 = loc.current(); loc.advance(); + + const auto bom_found = (c0 == 0xEF) && (c1 == 0xBB) && (c2 == 0xBF); + if( ! bom_found) + { + loc = first; + } + } + + context ctx(s); + + return parse_file(loc, ctx); +} + +} // detail + +// ----------------------------------------------------------------------------- +// parse(byte array) + +template +result, std::vector> +try_parse(std::vector content, std::string filename, + spec s = spec::default_version()) +{ + return detail::parse_impl(std::move(content), std::move(filename), std::move(s)); +} +template +basic_value +parse(std::vector content, std::string filename, + spec s = spec::default_version()) +{ + auto res = try_parse(std::move(content), std::move(filename), std::move(s)); + if(res.is_ok()) + { + return res.unwrap(); + } + else + { + std::string msg; + for(const auto& err : res.unwrap_err()) + { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } +} + +// ----------------------------------------------------------------------------- +// parse(istream) + +template +result, std::vector> +try_parse(std::istream& is, std::string fname = "unknown file", spec s = spec::default_version()) +{ + const auto beg = is.tellg(); + is.seekg(0, std::ios::end); + const auto end = is.tellg(); + const auto fsize = end - beg; + is.seekg(beg); + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize), '\0'); + is.read(reinterpret_cast(letters.data()), static_cast(fsize)); + + return detail::parse_impl(std::move(letters), std::move(fname), std::move(s)); +} + +template +basic_value parse(std::istream& is, std::string fname = "unknown file", spec s = spec::default_version()) +{ + auto res = try_parse(is, std::move(fname), std::move(s)); + if(res.is_ok()) + { + return res.unwrap(); + } + else + { + std::string msg; + for(const auto& err : res.unwrap_err()) + { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } +} + +// ----------------------------------------------------------------------------- +// parse(filename) + +template +result, std::vector> +try_parse(std::string fname, spec s = spec::default_version()) +{ + std::ifstream ifs(fname, std::ios_base::binary); + if(!ifs.good()) + { + std::vector e; + e.push_back(error_info("toml::parse: Error opening file \"" + fname + "\"", {})); + return err(std::move(e)); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return try_parse(ifs, std::move(fname), std::move(s)); +} + +template +basic_value parse(std::string fname, spec s = spec::default_version()) +{ + std::ifstream ifs(fname, std::ios_base::binary); + if(!ifs.good()) + { + throw file_io_error("toml::parse: error opening file", fname); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return parse(ifs, std::move(fname), std::move(s)); +} + +template +result, std::vector> +try_parse(const char (&fname)[N], spec s = spec::default_version()) +{ + return try_parse(std::string(fname), std::move(s)); +} + +template +basic_value parse(const char (&fname)[N], spec s = spec::default_version()) +{ + return parse(std::string(fname), std::move(s)); +} + +// ---------------------------------------------------------------------------- +// parse_str + +template +result, std::vector> +try_parse_str(std::string content, spec s = spec::default_version(), + cxx::source_location loc = cxx::source_location::current()) +{ + std::istringstream iss(std::move(content)); + std::string name("internal string" + cxx::to_string(loc)); + return try_parse(iss, std::move(name), std::move(s)); +} + +template +basic_value parse_str(std::string content, spec s = spec::default_version(), + cxx::source_location loc = cxx::source_location::current()) +{ + auto res = try_parse_str(std::move(content), std::move(s), std::move(loc)); + if(res.is_ok()) + { + return res.unwrap(); + } + else + { + std::string msg; + for(const auto& err : res.unwrap_err()) + { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } +} + +// ---------------------------------------------------------------------------- +// filesystem + +#if defined(TOML11_HAS_FILESYSTEM) + +template +cxx::enable_if_t::value, + result, std::vector>> +try_parse(const FSPATH& fpath, spec s = spec::default_version()) +{ + std::ifstream ifs(fpath, std::ios_base::binary); + if(!ifs.good()) + { + std::vector e; + e.push_back(error_info("toml::parse: Error opening file \"" + fpath.string() + "\"", {})); + return err(std::move(e)); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return try_parse(ifs, fpath.string(), std::move(s)); +} + +template +cxx::enable_if_t::value, + basic_value> +parse(const FSPATH& fpath, spec s = spec::default_version()) +{ + std::ifstream ifs(fpath, std::ios_base::binary); + if(!ifs.good()) + { + throw file_io_error("toml::parse: error opening file", fpath.string()); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return parse(ifs, fpath.string(), std::move(s)); +} +#endif + +// ----------------------------------------------------------------------------- +// FILE* + +template +result, std::vector> +try_parse(FILE* fp, std::string filename, spec s = spec::default_version()) +{ + const long beg = std::ftell(fp); + if (beg == -1L) + { + return err(std::vector{error_info( + std::string("Failed to access: \"") + filename + + "\", errno = " + std::to_string(errno), {} + )}); + } + + const int res_seekend = std::fseek(fp, 0, SEEK_END); + if (res_seekend != 0) + { + return err(std::vector{error_info( + std::string("Failed to seek: \"") + filename + + "\", errno = " + std::to_string(errno), {} + )}); + } + + const long end = std::ftell(fp); + if (end == -1L) + { + return err(std::vector{error_info( + std::string("Failed to access: \"") + filename + + "\", errno = " + std::to_string(errno), {} + )}); + } + + const auto fsize = end - beg; + + const auto res_seekbeg = std::fseek(fp, beg, SEEK_SET); + if (res_seekbeg != 0) + { + return err(std::vector{error_info( + std::string("Failed to seek: \"") + filename + + "\", errno = " + std::to_string(errno), {} + )}); + + } + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + const auto actual = std::fread(letters.data(), sizeof(char), static_cast(fsize), fp); + if(actual != static_cast(fsize)) + { + return err(std::vector{error_info( + std::string("File size changed: \"") + filename + + std::string("\" make sure that FILE* is in binary mode " + "to avoid LF <-> CRLF conversion"), {} + )}); + } + + return detail::parse_impl(std::move(letters), std::move(filename), std::move(s)); +} + +template +basic_value +parse(FILE* fp, std::string filename, spec s = spec::default_version()) +{ + const long beg = std::ftell(fp); + if (beg == -1L) + { + throw file_io_error(errno, "Failed to access", filename); + } + + const int res_seekend = std::fseek(fp, 0, SEEK_END); + if (res_seekend != 0) + { + throw file_io_error(errno, "Failed to seek", filename); + } + + const long end = std::ftell(fp); + if (end == -1L) + { + throw file_io_error(errno, "Failed to access", filename); + } + + const auto fsize = end - beg; + + const auto res_seekbeg = std::fseek(fp, beg, SEEK_SET); + if (res_seekbeg != 0) + { + throw file_io_error(errno, "Failed to seek", filename); + } + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + const auto actual = std::fread(letters.data(), sizeof(char), static_cast(fsize), fp); + if(actual != static_cast(fsize)) + { + throw file_io_error(errno, "File size changed; make sure that " + "FILE* is in binary mode to avoid LF <-> CRLF conversion", filename); + } + + auto res = detail::parse_impl(std::move(letters), std::move(filename), std::move(s)); + if(res.is_ok()) + { + return res.unwrap(); + } + else + { + std::string msg; + for(const auto& err : res.unwrap_err()) + { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } +} + +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +struct type_config; +struct ordered_type_config; + +extern template result, std::vector> try_parse(std::vector, std::string, spec); +extern template result, std::vector> try_parse(std::istream&, std::string, spec); +extern template result, std::vector> try_parse(std::string, spec); +extern template result, std::vector> try_parse(FILE*, std::string, spec); +extern template result, std::vector> try_parse_str(std::string, spec, cxx::source_location); + +extern template basic_value parse(std::vector, std::string, spec); +extern template basic_value parse(std::istream&, std::string, spec); +extern template basic_value parse(std::string, spec); +extern template basic_value parse(FILE*, std::string, spec); +extern template basic_value parse_str(std::string, spec, cxx::source_location); + +extern template result, std::vector> try_parse(std::vector, std::string, spec); +extern template result, std::vector> try_parse(std::istream&, std::string, spec); +extern template result, std::vector> try_parse(std::string, spec); +extern template result, std::vector> try_parse(FILE*, std::string, spec); +extern template result, std::vector> try_parse_str(std::string, spec, cxx::source_location); + +extern template basic_value parse(std::vector, std::string, spec); +extern template basic_value parse(std::istream&, std::string, spec); +extern template basic_value parse(std::string, spec); +extern template basic_value parse(FILE*, std::string, spec); +extern template basic_value parse_str(std::string, spec, cxx::source_location); + +#if defined(TOML11_HAS_FILESYSTEM) +extern template cxx::enable_if_t::value, result, std::vector>> try_parse(const std::filesystem::path&, spec); +extern template cxx::enable_if_t::value, result, std::vector>> try_parse(const std::filesystem::path&, spec); +extern template cxx::enable_if_t::value, basic_value > parse (const std::filesystem::path&, spec); +extern template cxx::enable_if_t::value, basic_value > parse (const std::filesystem::path&, spec); +#endif // filesystem + +} // toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_PARSER_HPP +#ifndef TOML11_LITERAL_HPP +#define TOML11_LITERAL_HPP + +#ifndef TOML11_LITERAL_FWD_HPP +#define TOML11_LITERAL_FWD_HPP + + +namespace toml +{ + +namespace detail +{ +// implementation +::toml::value literal_internal_impl(location loc); +} // detail + +inline namespace literals +{ +inline namespace toml_literals +{ + +::toml::value operator"" _toml(const char* str, std::size_t len); + +#if defined(TOML11_HAS_CHAR8_T) +// value of u8"" literal has been changed from char to char8_t and char8_t is +// NOT compatible to char +::toml::value operator"" _toml(const char8_t* str, std::size_t len); +#endif + +} // toml_literals +} // literals +} // toml +#endif // TOML11_LITERAL_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_LITERAL_IMPL_HPP +#define TOML11_LITERAL_IMPL_HPP + + +namespace toml +{ + +namespace detail +{ +// implementation +TOML11_INLINE ::toml::value literal_internal_impl(location loc) +{ + const auto s = ::toml::spec::default_version(); + context ctx(s); + + const auto front = loc; + + // ------------------------------------------------------------------------ + // check if it is a raw value. + + // skip empty lines and comment lines + auto sp = skip_multiline_spacer(loc, ctx); + if(loc.eof()) + { + ::toml::value val; + if(sp.has_value()) + { + for(std::size_t i=0; i(str), + reinterpret_cast(str + len), + c.begin()); + if( ! c.empty() && c.back()) + { + c.push_back('\n'); // to make it easy to parse comment, we add newline + } + + return literal_internal_impl(::toml::detail::location( + std::make_shared(std::move(c)), + "TOML literal encoded in a C++ code")); +} + +#if defined(__cpp_char8_t) +# if __cpp_char8_t >= 201811L +# define TOML11_HAS_CHAR8_T 1 +# endif +#endif + +#if defined(TOML11_HAS_CHAR8_T) +// value of u8"" literal has been changed from char to char8_t and char8_t is +// NOT compatible to char +TOML11_INLINE ::toml::value +operator"" _toml(const char8_t* str, std::size_t len) +{ + if(len == 0) + { + return ::toml::value{}; + } + + ::toml::detail::location::container_type c(len); + std::copy(reinterpret_cast(str), + reinterpret_cast(str + len), + c.begin()); + if( ! c.empty() && c.back()) + { + c.push_back('\n'); // to make it easy to parse comment, we add newline + } + + return literal_internal_impl(::toml::detail::location( + std::make_shared(std::move(c)), + "TOML literal encoded in a C++ code")); +} +#endif + +} // toml_literals +} // literals +} // toml +#endif // TOML11_LITERAL_IMPL_HPP +#endif + +#endif // TOML11_LITERAL_HPP +#ifndef TOML11_SERIALIZER_HPP +#define TOML11_SERIALIZER_HPP + + +#include +#include +#include + +#include +#include + +namespace toml +{ + +struct serialization_error final : public ::toml::exception +{ + public: + explicit serialization_error(std::string what_arg, source_location loc) + : what_(std::move(what_arg)), loc_(std::move(loc)) + {} + ~serialization_error() noexcept override = default; + + const char* what() const noexcept override {return what_.c_str();} + source_location const& location() const noexcept {return loc_;} + + private: + std::string what_; + source_location loc_; +}; + +namespace detail +{ +template +class serializer +{ + public: + + using value_type = basic_value; + + using key_type = typename value_type::key_type ; + using comment_type = typename value_type::comment_type ; + using boolean_type = typename value_type::boolean_type ; + using integer_type = typename value_type::integer_type ; + using floating_type = typename value_type::floating_type ; + using string_type = typename value_type::string_type ; + using local_time_type = typename value_type::local_time_type ; + using local_date_type = typename value_type::local_date_type ; + using local_datetime_type = typename value_type::local_datetime_type ; + using offset_datetime_type = typename value_type::offset_datetime_type; + using array_type = typename value_type::array_type ; + using table_type = typename value_type::table_type ; + + using char_type = typename string_type::value_type; + + public: + + explicit serializer(const spec& sp) + : spec_(sp), force_inline_(false), current_indent_(0) + {} + + string_type operator()(const std::vector& ks, const value_type& v) + { + for(const auto& k : ks) + { + this->keys_.push_back(k); + } + return (*this)(v); + } + + string_type operator()(const key_type& k, const value_type& v) + { + this->keys_.push_back(k); + return (*this)(v); + } + + string_type operator()(const value_type& v) + { + switch(v.type()) + { + case value_t::boolean : {return (*this)(v.as_boolean (), v.as_boolean_fmt (), v.location());} + case value_t::integer : {return (*this)(v.as_integer (), v.as_integer_fmt (), v.location());} + case value_t::floating : {return (*this)(v.as_floating (), v.as_floating_fmt (), v.location());} + case value_t::string : {return (*this)(v.as_string (), v.as_string_fmt (), v.location());} + case value_t::offset_datetime: {return (*this)(v.as_offset_datetime(), v.as_offset_datetime_fmt(), v.location());} + case value_t::local_datetime : {return (*this)(v.as_local_datetime (), v.as_local_datetime_fmt (), v.location());} + case value_t::local_date : {return (*this)(v.as_local_date (), v.as_local_date_fmt (), v.location());} + case value_t::local_time : {return (*this)(v.as_local_time (), v.as_local_time_fmt (), v.location());} + case value_t::array : + { + return (*this)(v.as_array(), v.as_array_fmt(), v.comments(), v.location()); + } + case value_t::table : + { + string_type retval; + if(this->keys_.empty()) // it might be the root table. emit comments here. + { + retval += format_comments(v.comments(), v.as_table_fmt().indent_type); + } + if( ! retval.empty()) // we have comment. + { + retval += char_type('\n'); + } + + retval += (*this)(v.as_table(), v.as_table_fmt(), v.comments(), v.location()); + return retval; + } + case value_t::empty: + { + if(this->spec_.ext_null_value) + { + return string_conv("null"); + } + break; + } + default: + { + break; + } + } + throw serialization_error(format_error( + "[error] toml::serializer: toml::basic_value " + "does not have any valid type.", v.location(), "here"), v.location()); + } + + private: + + string_type operator()(const boolean_type& b, const boolean_format_info&, const source_location&) // {{{ + { + if(b) + { + return string_conv("true"); + } + else + { + return string_conv("false"); + } + } // }}} + + string_type operator()(const integer_type i, const integer_format_info& fmt, const source_location& loc) // {{{ + { + std::ostringstream oss; + this->set_locale(oss); + + const auto insert_spacer = [&fmt](std::string s) -> std::string { + if(fmt.spacer == 0) {return s;} + + std::string sign; + if( ! s.empty() && (s.at(0) == '+' || s.at(0) == '-')) + { + sign += s.at(0); + s.erase(s.begin()); + } + + std::string spaced; + std::size_t counter = 0; + for(auto iter = s.rbegin(); iter != s.rend(); ++iter) + { + if(counter != 0 && counter % fmt.spacer == 0) + { + spaced += '_'; + } + spaced += *iter; + counter += 1; + } + if(!spaced.empty() && spaced.back() == '_') {spaced.pop_back();} + + s.clear(); + std::copy(spaced.rbegin(), spaced.rend(), std::back_inserter(s)); + return sign + s; + }; + + std::string retval; + if(fmt.fmt == integer_format::dec) + { + oss << std::setw(static_cast(fmt.width)) << std::dec << i; + retval = insert_spacer(oss.str()); + + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + retval += '_'; + retval += fmt.suffix; + } + } + else + { + if(i < 0) + { + throw serialization_error(format_error("binary, octal, hexadecimal " + "integer does not allow negative value", loc, "here"), loc); + } + switch(fmt.fmt) + { + case integer_format::hex: + { + oss << std::noshowbase + << std::setw(static_cast(fmt.width)) + << std::setfill('0') + << std::hex; + if(fmt.uppercase) + { + oss << std::uppercase; + } + else + { + oss << std::nouppercase; + } + oss << i; + retval = std::string("0x") + insert_spacer(oss.str()); + break; + } + case integer_format::oct: + { + oss << std::setw(static_cast(fmt.width)) << std::setfill('0') << std::oct << i; + retval = std::string("0o") + insert_spacer(oss.str()); + break; + } + case integer_format::bin: + { + integer_type x{i}; + std::string tmp; + std::size_t bits(0); + while(x != 0) + { + if(fmt.spacer != 0) + { + if(bits != 0 && (bits % fmt.spacer) == 0) {tmp += '_';} + } + if(x % 2 == 1) { tmp += '1'; } else { tmp += '0'; } + x >>= 1; + bits += 1; + } + for(; bits < fmt.width; ++bits) + { + if(fmt.spacer != 0) + { + if(bits != 0 && (bits % fmt.spacer) == 0) {tmp += '_';} + } + tmp += '0'; + } + for(auto iter = tmp.rbegin(); iter != tmp.rend(); ++iter) + { + oss << *iter; + } + retval = std::string("0b") + oss.str(); + break; + } + default: + { + throw serialization_error(format_error( + "none of dec, hex, oct, bin: " + to_string(fmt.fmt), + loc, "here"), loc); + } + } + } + return string_conv(retval); + } // }}} + + string_type operator()(const floating_type f, const floating_format_info& fmt, const source_location&) // {{{ + { + using std::isnan; + using std::isinf; + using std::signbit; + + std::ostringstream oss; + this->set_locale(oss); + + if(isnan(f)) + { + if(signbit(f)) + { + oss << '-'; + } + oss << "nan"; + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_'; + oss << fmt.suffix; + } + return string_conv(oss.str()); + } + + if(isinf(f)) + { + if(signbit(f)) + { + oss << '-'; + } + oss << "inf"; + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_'; + oss << fmt.suffix; + } + return string_conv(oss.str()); + } + + switch(fmt.fmt) + { + case floating_format::defaultfloat: + { + if(fmt.prec != 0) + { + oss << std::setprecision(static_cast(fmt.prec)); + } + oss << f; + // since defaultfloat may omit point, we need to add it + std::string s = oss.str(); + if (s.find('.') == std::string::npos && + s.find('e') == std::string::npos && + s.find('E') == std::string::npos ) + { + s += ".0"; + } + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + s += '_'; + s += fmt.suffix; + } + return string_conv(s); + } + case floating_format::fixed: + { + if(fmt.prec != 0) + { + oss << std::setprecision(static_cast(fmt.prec)); + } + oss << std::fixed << f; + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_' << fmt.suffix; + } + return string_conv(oss.str()); + } + case floating_format::scientific: + { + if(fmt.prec != 0) + { + oss << std::setprecision(static_cast(fmt.prec)); + } + oss << std::scientific << f; + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_' << fmt.suffix; + } + return string_conv(oss.str()); + } + case floating_format::hex: + { + if(this->spec_.ext_hex_float) + { + oss << std::hexfloat << f; + // suffix is only for decimal numbers. + return string_conv(oss.str()); + } + else // no hex allowed. output with max precision. + { + oss << std::setprecision(std::numeric_limits::max_digits10) + << std::scientific << f; + // suffix is only for decimal numbers. + return string_conv(oss.str()); + } + } + default: + { + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_' << fmt.suffix; + } + return string_conv(oss.str()); + } + } + } // }}} + + string_type operator()(string_type s, const string_format_info& fmt, const source_location& loc) // {{{ + { + string_type retval; + switch(fmt.fmt) + { + case string_format::basic: + { + retval += char_type('"'); + retval += this->escape_basic_string(s); + retval += char_type('"'); + return retval; + } + case string_format::literal: + { + if(std::find(s.begin(), s.end(), char_type('\n')) != s.end()) + { + throw serialization_error(format_error("toml::serializer: " + "(non-multiline) literal string cannot have a newline", + loc, "here"), loc); + } + retval += char_type('\''); + retval += s; + retval += char_type('\''); + return retval; + } + case string_format::multiline_basic: + { + retval += string_conv("\"\"\""); + if(fmt.start_with_newline) + { + retval += char_type('\n'); + } + + retval += this->escape_ml_basic_string(s); + + retval += string_conv("\"\"\""); + return retval; + } + case string_format::multiline_literal: + { + retval += string_conv("'''"); + if(fmt.start_with_newline) + { + retval += char_type('\n'); + } + retval += s; + retval += string_conv("'''"); + return retval; + } + default: + { + throw serialization_error(format_error( + "[error] toml::serializer::operator()(string): " + "invalid string_format value", loc, "here"), loc); + } + } + } // }}} + + string_type operator()(const local_date_type& d, const local_date_format_info&, const source_location&) // {{{ + { + std::ostringstream oss; + oss << d; + return string_conv(oss.str()); + } // }}} + + string_type operator()(const local_time_type& t, const local_time_format_info& fmt, const source_location&) // {{{ + { + return this->format_local_time(t, fmt.has_seconds, fmt.subsecond_precision); + } // }}} + + string_type operator()(const local_datetime_type& dt, const local_datetime_format_info& fmt, const source_location&) // {{{ + { + std::ostringstream oss; + oss << dt.date; + switch(fmt.delimiter) + { + case datetime_delimiter_kind::upper_T: { oss << 'T'; break; } + case datetime_delimiter_kind::lower_t: { oss << 't'; break; } + case datetime_delimiter_kind::space: { oss << ' '; break; } + default: { oss << 'T'; break; } + } + return string_conv(oss.str()) + + this->format_local_time(dt.time, fmt.has_seconds, fmt.subsecond_precision); + } // }}} + + string_type operator()(const offset_datetime_type& odt, const offset_datetime_format_info& fmt, const source_location&) // {{{ + { + std::ostringstream oss; + oss << odt.date; + switch(fmt.delimiter) + { + case datetime_delimiter_kind::upper_T: { oss << 'T'; break; } + case datetime_delimiter_kind::lower_t: { oss << 't'; break; } + case datetime_delimiter_kind::space: { oss << ' '; break; } + default: { oss << 'T'; break; } + } + oss << string_conv(this->format_local_time(odt.time, fmt.has_seconds, fmt.subsecond_precision)); + oss << odt.offset; + return string_conv(oss.str()); + } // }}} + + string_type operator()(const array_type& a, const array_format_info& fmt, const comment_type& com, const source_location& loc) // {{{ + { + array_format f = fmt.fmt; + if(fmt.fmt == array_format::default_format) + { + // [[in.this.form]], you cannot add a comment to the array itself + // (but you can add a comment to each table). + // To keep comments, we need to avoid multiline array-of-tables + // if array itself has a comment. + if( ! this->keys_.empty() && + ! a.empty() && + com.empty() && + std::all_of(a.begin(), a.end(), [](const value_type& e) {return e.is_table();})) + { + f = array_format::array_of_tables; + } + else + { + f = array_format::oneline; + + // check if it becomes long + std::size_t approx_len = 0; + for(const auto& e : a) + { + // have a comment. cannot be inlined + if( ! e.comments().empty()) + { + f = array_format::multiline; + break; + } + // possibly long types ... + if(e.is_array() || e.is_table() || e.is_offset_datetime() || e.is_local_datetime()) + { + f = array_format::multiline; + break; + } + else if(e.is_boolean()) + { + approx_len += (*this)(e.as_boolean(), e.as_boolean_fmt(), e.location()).size(); + } + else if(e.is_integer()) + { + approx_len += (*this)(e.as_integer(), e.as_integer_fmt(), e.location()).size(); + } + else if(e.is_floating()) + { + approx_len += (*this)(e.as_floating(), e.as_floating_fmt(), e.location()).size(); + } + else if(e.is_string()) + { + if(e.as_string_fmt().fmt == string_format::multiline_basic || + e.as_string_fmt().fmt == string_format::multiline_literal) + { + f = array_format::multiline; + break; + } + approx_len += 2 + (*this)(e.as_string(), e.as_string_fmt(), e.location()).size(); + } + else if(e.is_local_date()) + { + approx_len += 10; // 1234-56-78 + } + else if(e.is_local_time()) + { + approx_len += 15; // 12:34:56.789012 + } + + if(approx_len > 60) // key, ` = `, `[...]` < 80 + { + f = array_format::multiline; + break; + } + approx_len += 2; // `, ` + } + } + } + if(this->force_inline_ && f == array_format::array_of_tables) + { + f = array_format::multiline; + } + if(a.empty() && f == array_format::array_of_tables) + { + f = array_format::oneline; + } + + // -------------------------------------------------------------------- + + if(f == array_format::array_of_tables) + { + if(this->keys_.empty()) + { + throw serialization_error("array of table must have its key. " + "use format(key, v)", loc); + } + string_type retval; + for(const auto& e : a) + { + assert(e.is_table()); + + this->current_indent_ += e.as_table_fmt().name_indent; + retval += this->format_comments(e.comments(), e.as_table_fmt().indent_type); + retval += this->format_indent(e.as_table_fmt().indent_type); + this->current_indent_ -= e.as_table_fmt().name_indent; + + retval += string_conv("[["); + retval += this->format_keys(this->keys_).value(); + retval += string_conv("]]\n"); + + retval += this->format_ml_table(e.as_table(), e.as_table_fmt()); + } + return retval; + } + else if(f == array_format::oneline) + { + // ignore comments. we cannot emit comments + string_type retval; + retval += char_type('['); + for(const auto& e : a) + { + this->force_inline_ = true; + retval += (*this)(e); + retval += string_conv(", "); + } + if( ! a.empty()) + { + retval.pop_back(); // ` ` + retval.pop_back(); // `,` + } + retval += char_type(']'); + this->force_inline_ = false; + return retval; + } + else + { + assert(f == array_format::multiline); + + string_type retval; + retval += string_conv("[\n"); + + for(const auto& e : a) + { + this->current_indent_ += fmt.body_indent; + retval += this->format_comments(e.comments(), fmt.indent_type); + retval += this->format_indent(fmt.indent_type); + this->current_indent_ -= fmt.body_indent; + + this->force_inline_ = true; + retval += (*this)(e); + retval += string_conv(",\n"); + } + this->force_inline_ = false; + + this->current_indent_ += fmt.closing_indent; + retval += this->format_indent(fmt.indent_type); + this->current_indent_ -= fmt.closing_indent; + + retval += char_type(']'); + return retval; + } + } // }}} + + string_type operator()(const table_type& t, const table_format_info& fmt, const comment_type& com, const source_location& loc) // {{{ + { + if(this->force_inline_) + { + if(fmt.fmt == table_format::multiline_oneline) + { + return this->format_ml_inline_table(t, fmt); + } + else + { + return this->format_inline_table(t, fmt); + } + } + else + { + if(fmt.fmt == table_format::multiline) + { + string_type retval; + // comment is emitted inside format_ml_table + if(auto k = this->format_keys(this->keys_)) + { + this->current_indent_ += fmt.name_indent; + retval += this->format_comments(com, fmt.indent_type); + retval += this->format_indent(fmt.indent_type); + this->current_indent_ -= fmt.name_indent; + retval += char_type('['); + retval += k.value(); + retval += string_conv("]\n"); + } + // otherwise, its the root. + + retval += this->format_ml_table(t, fmt); + return retval; + } + else if(fmt.fmt == table_format::oneline) + { + return this->format_inline_table(t, fmt); + } + else if(fmt.fmt == table_format::multiline_oneline) + { + return this->format_ml_inline_table(t, fmt); + } + else if(fmt.fmt == table_format::dotted) + { + std::vector keys; + if(this->keys_.empty()) + { + throw serialization_error(format_error("toml::serializer: " + "dotted table must have its key. use format(key, v)", + loc, "here"), loc); + } + keys.push_back(this->keys_.back()); + + const auto retval = this->format_dotted_table(t, fmt, loc, keys); + keys.pop_back(); + return retval; + } + else + { + assert(fmt.fmt == table_format::implicit); + + string_type retval; + for(const auto& kv : t) + { + const auto& k = kv.first; + const auto& v = kv.second; + + if( ! v.is_table() && ! v.is_array_of_tables()) + { + throw serialization_error(format_error("toml::serializer: " + "an implicit table cannot have non-table value.", + v.location(), "here"), v.location()); + } + if(v.is_table()) + { + if(v.as_table_fmt().fmt != table_format::multiline && + v.as_table_fmt().fmt != table_format::implicit) + { + throw serialization_error(format_error("toml::serializer: " + "an implicit table cannot have non-multiline table", + v.location(), "here"), v.location()); + } + } + else + { + assert(v.is_array()); + for(const auto& e : v.as_array()) + { + if(e.as_table_fmt().fmt != table_format::multiline && + v.as_table_fmt().fmt != table_format::implicit) + { + throw serialization_error(format_error("toml::serializer: " + "an implicit table cannot have non-multiline table", + e.location(), "here"), e.location()); + } + } + } + + keys_.push_back(k); + retval += (*this)(v); + keys_.pop_back(); + } + return retval; + } + } + } // }}} + + private: + + string_type escape_basic_string(const string_type& s) const // {{{ + { + string_type retval; + for(const char_type c : s) + { + switch(c) + { + case char_type('\\'): {retval += string_conv("\\\\"); break;} + case char_type('\"'): {retval += string_conv("\\\""); break;} + case char_type('\b'): {retval += string_conv("\\b" ); break;} + case char_type('\t'): {retval += string_conv("\\t" ); break;} + case char_type('\f'): {retval += string_conv("\\f" ); break;} + case char_type('\n'): {retval += string_conv("\\n" ); break;} + case char_type('\r'): {retval += string_conv("\\r" ); break;} + default : + { + if(c == char_type(0x1B) && spec_.v1_1_0_add_escape_sequence_e) + { + retval += string_conv("\\e"); + } + else if((char_type(0x00) <= c && c <= char_type(0x08)) || + (char_type(0x0A) <= c && c <= char_type(0x1F)) || + c == char_type(0x7F)) + { + if(spec_.v1_1_0_add_escape_sequence_x) + { + retval += string_conv("\\x"); + } + else + { + retval += string_conv("\\u00"); + } + const auto c1 = c / 16; + const auto c2 = c % 16; + retval += static_cast('0' + c1); + if(c2 < 10) + { + retval += static_cast('0' + c2); + } + else // 10 <= c2 + { + retval += static_cast('A' + (c2 - 10)); + } + } + else + { + retval += c; + } + } + } + } + return retval; + } // }}} + + string_type escape_ml_basic_string(const string_type& s) // {{{ + { + string_type retval; + for(const char_type c : s) + { + switch(c) + { + case char_type('\\'): {retval += string_conv("\\\\"); break;} + case char_type('\b'): {retval += string_conv("\\b" ); break;} + case char_type('\t'): {retval += string_conv("\\t" ); break;} + case char_type('\f'): {retval += string_conv("\\f" ); break;} + case char_type('\n'): {retval += string_conv("\n" ); break;} + case char_type('\r'): {retval += string_conv("\\r" ); break;} + default : + { + if(c == char_type(0x1B) && spec_.v1_1_0_add_escape_sequence_e) + { + retval += string_conv("\\e"); + } + else if((char_type(0x00) <= c && c <= char_type(0x08)) || + (char_type(0x0A) <= c && c <= char_type(0x1F)) || + c == char_type(0x7F)) + { + if(spec_.v1_1_0_add_escape_sequence_x) + { + retval += string_conv("\\x"); + } + else + { + retval += string_conv("\\u00"); + } + const auto c1 = c / 16; + const auto c2 = c % 16; + retval += static_cast('0' + c1); + if(c2 < 10) + { + retval += static_cast('0' + c2); + } + else // 10 <= c2 + { + retval += static_cast('A' + (c2 - 10)); + } + } + else + { + retval += c; + } + } + } + } + // Only 1 or 2 consecutive `"`s are allowed in multiline basic string. + // 3 consecutive `"`s are considered as a closing delimiter. + // We need to check if there are 3 or more consecutive `"`s and insert + // backslash to break them down into several short `"`s like the `str6` + // in the following example. + // ```toml + // str4 = """Here are two quotation marks: "". Simple enough.""" + // # str5 = """Here are three quotation marks: """.""" # INVALID + // str5 = """Here are three quotation marks: ""\".""" + // str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" + // ``` + auto found_3_quotes = retval.find(string_conv("\"\"\"")); + while(found_3_quotes != string_type::npos) + { + retval.replace(found_3_quotes, 3, string_conv("\"\"\\\"")); + found_3_quotes = retval.find(string_conv("\"\"\"")); + } + return retval; + } // }}} + + string_type format_local_time(const local_time_type& t, const bool has_seconds, const std::size_t subsec_prec) // {{{ + { + std::ostringstream oss; + oss << std::setfill('0') << std::setw(2) << static_cast(t.hour); + oss << ':'; + oss << std::setfill('0') << std::setw(2) << static_cast(t.minute); + if(has_seconds) + { + oss << ':'; + oss << std::setfill('0') << std::setw(2) << static_cast(t.second); + if(subsec_prec != 0) + { + std::ostringstream subsec; + subsec << std::setfill('0') << std::setw(3) << static_cast(t.millisecond); + subsec << std::setfill('0') << std::setw(3) << static_cast(t.microsecond); + subsec << std::setfill('0') << std::setw(3) << static_cast(t.nanosecond); + std::string subsec_str = subsec.str(); + oss << '.' << subsec_str.substr(0, subsec_prec); + } + } + return string_conv(oss.str()); + } // }}} + + string_type format_ml_table(const table_type& t, const table_format_info& fmt) // {{{ + { + const auto format_later = [](const value_type& v) -> bool { + + const bool is_ml_table = v.is_table() && + v.as_table_fmt().fmt != table_format::oneline && + v.as_table_fmt().fmt != table_format::multiline_oneline && + v.as_table_fmt().fmt != table_format::dotted ; + + const bool is_ml_array_table = v.is_array_of_tables() && + v.as_array_fmt().fmt != array_format::oneline && + v.as_array_fmt().fmt != array_format::multiline; + + return is_ml_table || is_ml_array_table; + }; + + string_type retval; + this->current_indent_ += fmt.body_indent; + for(const auto& kv : t) + { + const auto& key = kv.first; + const auto& val = kv.second; + if(format_later(val)) + { + continue; + } + this->keys_.push_back(key); + + retval += format_comments(val.comments(), fmt.indent_type); + retval += format_indent(fmt.indent_type); + if(val.is_table() && val.as_table_fmt().fmt == table_format::dotted) + { + retval += (*this)(val); + } + else + { + retval += format_key(key); + retval += string_conv(" = "); + retval += (*this)(val); + retval += char_type('\n'); + } + this->keys_.pop_back(); + } + this->current_indent_ -= fmt.body_indent; + + if( ! retval.empty()) + { + retval += char_type('\n'); // for readability, add empty line between tables + } + for(const auto& kv : t) + { + if( ! format_later(kv.second)) + { + continue; + } + // must be a [multiline.table] or [[multiline.array.of.tables]]. + // comments will be generated inside it. + this->keys_.push_back(kv.first); + retval += (*this)(kv.second); + this->keys_.pop_back(); + } + return retval; + } // }}} + + string_type format_inline_table(const table_type& t, const table_format_info&) // {{{ + { + // comments are ignored because we cannot write without newline + string_type retval; + retval += char_type('{'); + for(const auto& kv : t) + { + this->force_inline_ = true; + retval += this->format_key(kv.first); + retval += string_conv(" = "); + retval += (*this)(kv.second); + retval += string_conv(", "); + } + if( ! t.empty()) + { + retval.pop_back(); // ' ' + retval.pop_back(); // ',' + } + retval += char_type('}'); + this->force_inline_ = false; + return retval; + } // }}} + + string_type format_ml_inline_table(const table_type& t, const table_format_info& fmt) // {{{ + { + string_type retval; + retval += string_conv("{\n"); + this->current_indent_ += fmt.body_indent; + for(const auto& kv : t) + { + this->force_inline_ = true; + retval += format_comments(kv.second.comments(), fmt.indent_type); + retval += format_indent(fmt.indent_type); + retval += kv.first; + retval += string_conv(" = "); + + this->force_inline_ = true; + retval += (*this)(kv.second); + + retval += string_conv(",\n"); + } + if( ! t.empty()) + { + retval.pop_back(); // '\n' + retval.pop_back(); // ',' + } + this->current_indent_ -= fmt.body_indent; + this->force_inline_ = false; + + this->current_indent_ += fmt.closing_indent; + retval += format_indent(fmt.indent_type); + this->current_indent_ -= fmt.closing_indent; + + retval += char_type('}'); + return retval; + } // }}} + + string_type format_dotted_table(const table_type& t, const table_format_info& fmt, // {{{ + const source_location&, std::vector& keys) + { + // lets say we have: `{"a": {"b": {"c": {"d": "foo", "e": "bar"} } }` + // and `a` and `b` are `dotted`. + // + // - in case if `c` is `oneline`: + // ```toml + // a.b.c = {d = "foo", e = "bar"} + // ``` + // + // - in case if and `c` is `dotted`: + // ```toml + // a.b.c.d = "foo" + // a.b.c.e = "bar" + // ``` + + string_type retval; + + for(const auto& kv : t) + { + const auto& key = kv.first; + const auto& val = kv.second; + + keys.push_back(key); + + // format recursive dotted table? + if (val.is_table() && + val.as_table_fmt().fmt != table_format::oneline && + val.as_table_fmt().fmt != table_format::multiline_oneline) + { + retval += this->format_dotted_table(val.as_table(), val.as_table_fmt(), val.location(), keys); + } + else // non-table or inline tables. format normally + { + retval += format_comments(val.comments(), fmt.indent_type); + retval += format_indent(fmt.indent_type); + retval += format_keys(keys).value(); + retval += string_conv(" = "); + this->force_inline_ = true; // sub-table must be inlined + retval += (*this)(val); + retval += char_type('\n'); + this->force_inline_ = false; + } + keys.pop_back(); + } + return retval; + } // }}} + + string_type format_key(const key_type& key) // {{{ + { + if(key.empty()) + { + return string_conv("\"\""); + } + + // check the key can be a bare (unquoted) key + auto loc = detail::make_temporary_location(string_conv(key)); + auto reg = detail::syntax::unquoted_key(this->spec_).scan(loc); + if(reg.is_ok() && loc.eof()) + { + return key; + } + + //if it includes special characters, then format it in a "quoted" key. + string_type formatted = string_conv("\""); + for(const char_type c : key) + { + switch(c) + { + case char_type('\\'): {formatted += string_conv("\\\\"); break;} + case char_type('\"'): {formatted += string_conv("\\\""); break;} + case char_type('\b'): {formatted += string_conv("\\b" ); break;} + case char_type('\t'): {formatted += string_conv("\\t" ); break;} + case char_type('\f'): {formatted += string_conv("\\f" ); break;} + case char_type('\n'): {formatted += string_conv("\\n" ); break;} + case char_type('\r'): {formatted += string_conv("\\r" ); break;} + default : + { + // ASCII ctrl char + if( (char_type(0x00) <= c && c <= char_type(0x08)) || + (char_type(0x0A) <= c && c <= char_type(0x1F)) || + c == char_type(0x7F)) + { + if(spec_.v1_1_0_add_escape_sequence_x) + { + formatted += string_conv("\\x"); + } + else + { + formatted += string_conv("\\u00"); + } + const auto c1 = c / 16; + const auto c2 = c % 16; + formatted += static_cast('0' + c1); + if(c2 < 10) + { + formatted += static_cast('0' + c2); + } + else // 10 <= c2 + { + formatted += static_cast('A' + (c2 - 10)); + } + } + else + { + formatted += c; + } + break; + } + } + } + formatted += string_conv("\""); + return formatted; + } // }}} + cxx::optional format_keys(const std::vector& keys) // {{{ + { + if(keys.empty()) + { + return cxx::make_nullopt(); + } + + string_type formatted; + for(const auto& ky : keys) + { + formatted += format_key(ky); + formatted += char_type('.'); + } + formatted.pop_back(); // remove the last dot '.' + return formatted; + } // }}} + + string_type format_comments(const discard_comments&, const indent_char) const // {{{ + { + return string_conv(""); + } // }}} + string_type format_comments(const preserve_comments& comments, const indent_char indent_type) const // {{{ + { + string_type retval; + for(const auto& c : comments) + { + if(c.empty()) {continue;} + retval += format_indent(indent_type); + if(c.front() != '#') {retval += char_type('#');} + retval += string_conv(c); + if(c.back() != '\n') {retval += char_type('\n');} + } + return retval; + } // }}} + + string_type format_indent(const indent_char indent_type) const // {{{ + { + const auto indent = static_cast((std::max)(0, this->current_indent_)); + if(indent_type == indent_char::space) + { + return string_conv(make_string(indent, ' ')); + } + else if(indent_type == indent_char::tab) + { + return string_conv(make_string(indent, '\t')); + } + else + { + return string_type{}; + } + } // }}} + + std::locale set_locale(std::ostream& os) const + { + return os.imbue(std::locale::classic()); + } + + private: + + spec spec_; + bool force_inline_; // table inside an array without fmt specification + std::int32_t current_indent_; + std::vector keys_; +}; +} // detail + +template +typename basic_value::string_type +format(const basic_value& v, const spec s = spec::default_version()) +{ + detail::serializer ser(s); + return ser(v); +} +template +typename basic_value::string_type +format(const typename basic_value::key_type& k, + const basic_value& v, + const spec s = spec::default_version()) +{ + detail::serializer ser(s); + return ser(k, v); +} +template +typename basic_value::string_type +format(const std::vector::key_type>& ks, + const basic_value& v, + const spec s = spec::default_version()) +{ + detail::serializer ser(s); + return ser(ks, v); +} + +template +std::ostream& operator<<(std::ostream& os, const basic_value& v) +{ + os << format(v); + return os; +} + +} // toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +struct type_config; +struct ordered_type_config; + +extern template typename basic_value::string_type +format(const basic_value&, const spec); + +extern template typename basic_value::string_type +format(const typename basic_value::key_type& k, + const basic_value& v, const spec); + +extern template typename basic_value::string_type +format(const std::vector::key_type>& ks, + const basic_value& v, const spec s); + +extern template typename basic_value::string_type +format(const basic_value&, const spec); + +extern template typename basic_value::string_type +format(const typename basic_value::key_type& k, + const basic_value& v, const spec); + +extern template typename basic_value::string_type +format(const std::vector::key_type>& ks, + const basic_value& v, const spec s); + +namespace detail +{ +extern template class serializer<::toml::type_config>; +extern template class serializer<::toml::ordered_type_config>; +} // detail +} // toml +#endif // TOML11_COMPILE_SOURCES + + +#endif // TOML11_SERIALIZER_HPP +#ifndef TOML11_TOML_HPP +#define TOML11_TOML_HPP + +// The MIT License (MIT) +// +// Copyright (c) 2017-now Toru Niina +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// IWYU pragma: begin_exports +// IWYU pragma: end_exports -#endif// TOML_FOR_MODERN_CPP +#endif// TOML11_TOML_HPP diff --git a/vendor/toml/acceptor.hpp b/vendor/toml/acceptor.hpp deleted file mode 100644 index 11844ea..0000000 --- a/vendor/toml/acceptor.hpp +++ /dev/null @@ -1,777 +0,0 @@ -#ifndef TOML11_ACCEPTOR -#define TOML11_ACCEPTOR -#include -#include -#include -#include "exception.hpp" - -namespace toml -{ - -template -struct is_character -{ - typedef charT value_type; - constexpr static value_type target = c; - - template::value_type, - value_type>::value>::type> - constexpr static Iterator invoke(Iterator iter, Iterator end) - { - return iter == end ? iter : *iter == c ? std::next(iter) : iter; - } -}; - -template -struct is_in_range -{ - typedef charT value_type; - constexpr static value_type upper = up; - constexpr static value_type lower = lw; - static_assert(lower <= upper, "lower <= upper"); - - template::value_type, - value_type>::value>::type> - constexpr static Iterator invoke(Iterator iter, Iterator end) - { - return iter == end ? iter : - (lower <= *iter && *iter <= upper) ? std::next(iter) : iter; - } -}; - -template -struct is_one_of -{ - typedef typename headT::value_type value_type; - static_assert( - std::is_same::value_type>::value, - "different value_type"); - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - const Iterator tmp = headT::invoke(iter, end); - return (tmp != iter) ? tmp : is_one_of::invoke(iter, end); - } -}; -template -struct is_one_of -{ - typedef typename tailT::value_type value_type; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - const Iterator tmp = tailT::invoke(iter, end); - return (tmp != iter) ? tmp : iter; - } -}; - -// just a wrapper for maybe_ignored -template -struct is_ignorable -{ - typedef typename condT::value_type value_type; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - const Iterator tmp = condT::invoke(iter, end); - return (tmp != iter) ? tmp : iter; - } -}; - -template -struct maybe_ignored : std::false_type{}; -template -struct maybe_ignored> : std::true_type{}; - -template -struct is_chain_of_impl -{ - typedef typename headT::value_type value_type; - static_assert(std::is_same::value_type>::value, - "different value_type"); - - constexpr static bool ignorable = maybe_ignored::value; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end, Iterator rollback) - { - const Iterator tmp = headT::invoke(iter, end); - return (tmp == iter && !ignorable) ? rollback : - is_chain_of_impl::invoke(tmp, end, rollback); - } -}; - -template -struct is_chain_of_impl -{ - typedef typename tailT::value_type value_type; - constexpr static bool ignorable = maybe_ignored::value; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end, Iterator rollback) - { - const Iterator tmp = tailT::invoke(iter, end); - return (tmp == iter) ? (ignorable ? iter : rollback) : tmp; - } -}; - -template -struct is_chain_of -{ - typedef typename is_chain_of_impl::value_type value_type; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - return is_chain_of_impl::invoke(iter, end, iter); - } -}; - -constexpr inline std::size_t repeat_infinite(){return 0ul;} - -template -struct is_repeat_of -{ - typedef typename condT::value_type value_type; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - const Iterator rollback = iter; - Iterator tmp; - for(auto i=0ul; i -struct is_repeat_of -{ - typedef typename condT::value_type value_type; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - Iterator tmp = condT::invoke(iter, end); - while(tmp != iter) - { - iter = tmp; - tmp = condT::invoke(iter, end); - } - return iter; - } -}; - -template -struct is_none_of -{ - typedef typename headT::value_type value_type; - static_assert( - std::is_same::value_type>::value, - "different value_type"); - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - const Iterator tmp = headT::invoke(iter, end); - return (tmp != iter) ? iter : is_none_of::invoke(iter, end); - } -}; - -template -struct is_none_of -{ - typedef typename tailT::value_type value_type; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - const Iterator tmp = tailT::invoke(iter, end); - return (tmp != iter) ? iter : std::next(iter); - } -}; - -template -struct is_not_but -{ - typedef typename notT::value_type value_type; - static_assert( - std::is_same::value, - "different value type"); - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - return (iter != notT::invoke(iter, end)) ? iter : butT::invoke(iter, end); - } -}; - -template -using is_space = is_character; -template -using is_tab = is_character; -template -using is_number = is_in_range; -template -using is_lowercase = is_in_range; -template -using is_uppercase = is_in_range; -template -using is_alphabet = is_one_of, is_uppercase>; -template -using is_hex = is_one_of, is_in_range, - is_in_range>; -template -using is_whitespace = is_one_of, is_tab>; -template -using is_any_num_of_ws = - is_ignorable, repeat_infinite()>>; - -template -using is_newline = is_one_of, - is_chain_of, is_character>>; -template -using is_barekey_component = is_one_of, is_number, - is_character, is_character>; -template -using is_barekey = is_repeat_of, repeat_infinite()>; -template -using is_comment = - is_chain_of< - is_character, - is_repeat_of>, repeat_infinite()>, - is_newline - >; - -template -using is_basic_inline_string_component = - is_one_of< - is_none_of< is_in_range, is_character, - is_character, is_newline>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character, - is_repeat_of, 4>>, - is_chain_of, is_character, - is_repeat_of, 8>> - >; -template -using is_basic_inline_string = - is_not_but< - is_repeat_of, 3>, // not multiline - is_chain_of< - is_character, - is_ignorable, - repeat_infinite()>>, - is_character - > - >; -template -using is_basic_multiline_string_component = - is_one_of< - is_none_of< is_in_range, - is_repeat_of, 3>, - is_character>, - is_newline, - is_chain_of, is_newline>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character>, - is_chain_of, is_character, - is_repeat_of, 4>>, - is_chain_of, is_character, - is_repeat_of, 8>> - >; -template -using is_basic_multiline_string = - is_chain_of< - is_repeat_of, 3>, - is_ignorable, - repeat_infinite()>>, - is_repeat_of, 3> - >; - -template -using is_literal_inline_string_component = - is_none_of, is_character>; - -template -using is_literal_inline_string = - is_not_but< - is_repeat_of, 3>, - is_chain_of< - is_character, - is_ignorable, - repeat_infinite()>>, - is_character - > - >; - -template -using is_literal_multiline_string_component = - is_one_of< - is_none_of, - is_repeat_of, 3>>, - is_newline - >; - -template -using is_literal_multiline_string = - is_chain_of< - is_repeat_of, 3>, - is_ignorable, - repeat_infinite()>>, - is_repeat_of, 3> - >; - -template -using is_string = - is_one_of< - is_basic_inline_string, - is_basic_multiline_string, - is_literal_inline_string, - is_literal_multiline_string - >; - - -template -using is_sign = is_one_of, is_character>; -template -using is_nonzero_number = is_in_range; - -template -using is_integer_component = - is_not_but< - is_repeat_of, 2>, - is_one_of< - is_character, is_number - > - >; -template -using is_integer = - is_chain_of< - is_ignorable>, - is_one_of< - is_character, - is_chain_of< - is_nonzero_number, - is_ignorable, - repeat_infinite()> - > - > - > - >; - -template -using is_fractional_part = - is_chain_of< - is_character, - is_repeat_of, repeat_infinite()> - >; -template -using is_exponent_part = - is_chain_of< - is_one_of, is_character>, - is_integer - >; -template -using is_float = - is_one_of< - is_chain_of< - is_integer, - is_fractional_part, - is_exponent_part - >, - is_chain_of< - is_integer, - is_fractional_part - >, - is_chain_of< - is_integer, - is_exponent_part - > - >; - -template -using is_boolean = - is_one_of< - is_chain_of< - is_character, - is_character, - is_character, - is_character - >, - is_chain_of< - is_character, - is_character, - is_character, - is_character, - is_character - > - >; - -template -using is_local_time = - is_chain_of< - is_repeat_of, 2>, - is_character, - is_repeat_of, 2>, - is_character, - is_repeat_of, 2>, - is_ignorable< - is_chain_of< - is_character, - is_repeat_of, repeat_infinite()> - > - > - >; - -template -using is_local_date = - is_chain_of< - is_repeat_of, 4>, - is_character, - is_repeat_of, 2>, - is_character, - is_repeat_of, 2> - >; - -template -using is_local_date_time = - is_chain_of< - is_local_date, - is_character, - is_local_time - >; - -template -using is_offset = - is_one_of< - is_character, - is_chain_of< - is_sign, - is_repeat_of, 2>, - is_character, - is_repeat_of, 2> - > - >; - -template -using is_offset_date_time = - is_chain_of< - is_local_date_time, - is_offset - >; - -template -using is_datetime = - is_one_of< - is_offset_date_time, - is_local_date_time, - is_local_date, - is_local_time - >; - -template -using is_fundamental_type = - is_one_of< - is_basic_inline_string, - is_basic_multiline_string, - is_literal_inline_string, - is_literal_multiline_string, - is_offset_date_time, - is_local_date_time, - is_local_date, - is_local_time, - is_boolean, - is_float, - is_integer - >; - -template -using is_skippable_in_array = - is_repeat_of< - is_one_of, is_newline, is_comment>, - repeat_infinite() - >; - -template -struct is_inline_table; - -template -using is_key = - is_one_of< - is_barekey, - is_string - >; - - -template -using is_fixed_type_array = - is_chain_of< - is_character, - is_ignorable< - is_repeat_of< - is_chain_of< - is_ignorable>, - is_array_component, - is_ignorable>, - is_character - >, - repeat_infinite() - > - >, - is_ignorable< - is_chain_of< - is_ignorable>, - is_array_component, - is_ignorable>, - is_ignorable> - > - >, - is_ignorable>, - is_character - >; - -template -struct is_array -{ - typedef charT value_type; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - return is_one_of< - is_fixed_type_array>, - is_fixed_type_array>, - is_fixed_type_array>, - is_fixed_type_array>, - is_fixed_type_array>, - is_fixed_type_array>, - is_fixed_type_array> - >::invoke(iter, end); - } -}; - -template -struct is_inline_table -{ - typedef charT value_type; - - template::value_type, - value_type>::value>::type> - static Iterator invoke(Iterator iter, Iterator end) - { - typedef is_one_of, - is_array, is_inline_table> is_component; - - typedef is_chain_of< - is_any_num_of_ws, - is_key, - is_any_num_of_ws, - is_character, - is_ignorable< - is_repeat_of< - is_chain_of< - is_any_num_of_ws, - is_inline_key_value_pair, - is_any_num_of_ws, - is_character - >, - repeat_infinite() - > - >, - is_ignorable< - is_chain_of< - is_any_num_of_ws, - is_inline_key_value_pair, - is_any_num_of_ws, - is_ignorable> - > - >, - is_any_num_of_ws, - is_character - > entity; - return entity::invoke(iter, end); - } -}; - -template -using is_value = - is_one_of, is_array, is_inline_table>; - -// [] -template -using is_table_definition = - is_chain_of< - is_any_num_of_ws, - is_character, - is_any_num_of_ws, - is_key, - is_ignorable< - is_repeat_of< - is_chain_of< - is_any_num_of_ws, - is_character, - is_any_num_of_ws, - is_key, - is_any_num_of_ws - >, - repeat_infinite()> - >, - is_character - >; - -template -using is_array_of_table_definition = - is_chain_of< - is_any_num_of_ws, - is_repeat_of, 2>, - is_any_num_of_ws, - is_key, - is_ignorable< - is_repeat_of< - is_chain_of< - is_any_num_of_ws, - is_character, - is_any_num_of_ws, - is_key, - is_any_num_of_ws - >, - repeat_infinite()> - >, - is_repeat_of, 2> - >; - -template -using is_key_value_pair = - is_chain_of< - is_any_num_of_ws, - is_key, - is_any_num_of_ws, - is_character::undef && - dt.minute != basic_datetime::undef && - dt.second != basic_datetime::undef) - { - if(date) os << 'T'; - os << std::setfill('0') << std::setw(2) << dt.hour << ':' - << std::setfill('0') << std::setw(2) << dt.minute << ':' - << std::setfill('0') << std::setw(2) << dt.second << '.' - << std::setfill('0') << std::setw(3) << dt.millisecond - << std::setfill('0') << std::setw(3) << dt.microsecond; - } - if(dt.offset_hour != basic_datetime::nooffset && - dt.offset_minute != basic_datetime::nooffset) - { - if(dt.offset_hour == 0 && dt.offset_minute == 0) - { - os << 'Z'; - } - else - { - char sign = ' '; - iT oh = dt.offset_hour; - iT om = dt.offset_minute; - om += oh * 60; - if(om > 0) sign = '+'; else sign='-'; - oh = om / 60; - om -= oh * 60; - os << sign << std::setfill('0') << std::setw(2) << std::abs(oh) << ':' - << std::setfill('0') << std::setw(2) << std::abs(om); - } - } - return os; -} - -template -inline bool -operator==(basic_datetime const& lhs, basic_datetime const& rhs) -{ - return lhs.year == rhs.year && lhs.month == rhs.month && - lhs.day == rhs.day && lhs.minute == rhs.minute && - lhs.second == rhs.second && lhs.millisecond == rhs.millisecond && - lhs.microsecond == rhs.microsecond && - lhs.offset_hour == rhs.offset_hour && - lhs.offset_minute == rhs.offset_minute; -} - -template -inline bool -operator!=(basic_datetime const& lhs, basic_datetime const& rhs) -{ - return !(lhs == rhs); -} - -template -inline bool -operator<(basic_datetime const& lhs, basic_datetime const& rhs) -{ - return std::time_t(lhs) < std::time_t(rhs); -} - -template -inline bool -operator<=(basic_datetime const& lhs, basic_datetime const& rhs) -{ - return std::time_t(lhs) <= std::time_t(rhs); -} - -template -inline bool -operator>(basic_datetime const& lhs, basic_datetime const& rhs) -{ - return std::time_t(lhs) > std::time_t(rhs); -} - -template -inline bool -operator>=(basic_datetime const& lhs, basic_datetime const& rhs) -{ - return std::time_t(lhs) >= std::time_t(rhs); -} - - -}//toml -#endif// TOML11_DATETIME diff --git a/vendor/toml/exception.hpp b/vendor/toml/exception.hpp deleted file mode 100644 index 87316b6..0000000 --- a/vendor/toml/exception.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef TOML11_EXCEPTION -#define TOML11_EXCEPTION -#include -#include - -namespace toml -{ - -struct exception : public std::exception -{ - public: - virtual ~exception() noexcept override = default; - virtual const char* what() const noexcept override {return "";} -}; - -struct syntax_error : public toml::exception -{ - public: - explicit syntax_error(const std::string& what_arg) : what_(what_arg){} - explicit syntax_error(const char* what_arg) : what_(what_arg){} - virtual ~syntax_error() noexcept override = default; - virtual const char* what() const noexcept override {return what_.c_str();} - - protected: - std::string what_; -}; - -struct type_error : public toml::exception -{ - public: - explicit type_error(const std::string& what_arg) : what_(what_arg){} - explicit type_error(const char* what_arg) : what_(what_arg){} - virtual ~type_error() noexcept override = default; - virtual const char* what() const noexcept override {return what_.c_str();} - - protected: - std::string what_; -}; - -struct internal_error : public toml::exception -{ - public: - explicit internal_error(const std::string& what_arg) : what_(what_arg){} - explicit internal_error(const char* what_arg) : what_(what_arg){} - virtual ~internal_error() noexcept override = default; - virtual const char* what() const noexcept override {return what_.c_str();} - protected: - std::string what_; -}; - -} // toml -#endif // TOML_EXCEPTION diff --git a/vendor/toml/format.hpp b/vendor/toml/format.hpp deleted file mode 100644 index c4726dc..0000000 --- a/vendor/toml/format.hpp +++ /dev/null @@ -1,386 +0,0 @@ -#ifndef TOML11_FORMAT -#define TOML11_FORMAT -#include "value.hpp" -#include -#include -#include -#include -#include - -namespace toml -{ - -// synopsis -// toml::format("key", value, toml::make_inline(80)) -// toml::format("key", value, toml::forceinline) -// std::cout << toml::make_inline(80) << value; -// std::cout << toml::forceinline << value; - -template, - typename alloc = std::allocator> -std::basic_string -format(const value& v); - -template, - typename alloc = std::allocator> -std::basic_string -format(const value& v, std::size_t mk); - -template, - typename alloc = std::allocator> -std::basic_string -format(const toml::key& k, const value& v); - -template, - typename alloc = std::allocator> -std::basic_string -format(const toml::key& k, const value& v, std::size_t mk); - -template -struct format_impl; - -template<> struct format_impl -{ - typedef detail::toml_default_type::type type; - - std::basic_string - operator()(const type& val) - { - return val ? "true" : "false"; - } -}; - -template<> struct format_impl -{ - typedef detail::toml_default_type::type type; - - std::basic_string - operator()(const type& val) - { - return std::to_string(val); - } -}; - -template<> struct format_impl -{ - typedef detail::toml_default_type::type type; - - std::basic_string - operator()(const type& val) - { - std::basic_ostringstream oss; - oss << std::showpoint << val; - if(oss.str().back() == '.') oss << '0'; - return oss.str(); - } -}; - -template<> struct format_impl -{ - typedef detail::toml_default_type::type type; - - std::size_t max_length; - - format_impl() : max_length(80){} - format_impl(std::size_t mx) : max_length(mx){} - - std::basic_string - operator()(const type& val) - { - auto tmp = make_inline(val); - if(max_length == std::numeric_limits::max() || - tmp.size() <= max_length) return tmp; - return convert_multiline(std::move(tmp)); - } - - private: - - std::basic_string - make_inline(const std::basic_string& val) - { - std::basic_string str; - str += '"'; - for(const auto& c : val) - { - if('\0' < c && c < '\31') - { - switch(c) - { - case '\b': str += "\\b"; break; - case '\t': str += "\\t"; break; - case '\n': str += "\\n"; break; - case '\f': str += "\\f"; break; - case '\r': str += "\\r"; break; - default: - { - str += 'u'; - std::basic_ostringstream oss; - oss << std::setw(4) << std::setfill('0') << std::hex - << static_cast(c); - auto hexdig = oss.str(); - std::transform(hexdig.begin(), hexdig.end(), hexdig.begin(), ::toupper); - str += oss.str(); - break; - } - } - } - else if(c == '"') - { - str += "\\\""; - } - else if(c == '\\') - { - str += "\\\\"; - } - else - { - str += c; - } - } - str += '"'; - return str; - } - - std::basic_string - convert_multiline(std::basic_string&& val) - { - std::basic_string str; str.reserve(val.size() + 6); - str += "\"\"\"\n"; - std::size_t current = 0; - for(auto iter = val.begin()+1; iter != val.end()-1; ++iter) - { - if(*iter != '\\') - { - if(current + 1 == max_length) str += "\\\n"; - str += *iter; continue; - } - assert(std::next(iter) < val.end()-1); - if(*std::next(iter) == 'u') - { - if(current + 5 == max_length) str += "\\\n"; - assert(iter + 5 < val.end()-1); - str += *iter; ++iter; // u - str += *iter; ++iter; // 0 - str += *iter; ++iter; // 1 - str += *iter; ++iter; // 2 - str += *iter; continue;// 3 - } - if(current + 2 == max_length) str += "\\\n"; - str += *iter; ++iter; str += *iter; - } - str += "\"\"\""; - return str; - } - -}; - -template<> struct format_impl -{ - typedef detail::toml_default_type::type type; - - std::basic_string - operator()(const type& val) - { - std::basic_ostringstream oss; - oss << val; - return oss.str(); - } -}; - -// TODO max length! -template<> struct format_impl -{ - typedef detail::toml_default_type::type type; - - std::size_t max_length; - - format_impl() : max_length(80){} - format_impl(std::size_t mx) : max_length(mx){} - - std::basic_string - operator()(const type& val) - { - std::basic_string retval; - retval += '['; - for(const auto& item : val) - { - auto tmp = format(val, max_length - 1); - retval += tmp; - retval += ", "; - if(tmp.size() * 2 > max_length) retval += '\n'; - } - retval += ']'; - return retval; - } -}; - -// TODO max length && inline! -template<> struct format_impl -{ - typedef detail::toml_default_type::type type; - - std::size_t max_length; - - format_impl() : max_length(80){} - format_impl(std::size_t mx) : max_length(mx){} - - std::basic_string - operator()(const type& val) - { - std::basic_string retval; - for(const auto& item : val) - { - retval += item.first; - retval += " = "; - retval += format(item.second); - retval += '\n'; - } - return retval; - } -}; - -template -std::basic_string -format(const value& v) -{ - switch(v.type()) - { - case value_t::Boolean : return format_impl{}(v.template cast()); - case value_t::Integer : return format_impl{}(v.template cast()); - case value_t::Float : return format_impl{}(v.template cast()); - case value_t::String : return format_impl{}(v.template cast()); - case value_t::Datetime: return format_impl{}(v.template cast()); - case value_t::Array : return format_impl{}(v.template cast()); - case value_t::Table : return format_impl{}(v.template cast()); - case value_t::Empty : throw std::runtime_error("toml::format: empty value"); - case value_t::Unknown : throw std::runtime_error("toml::format: unknown value"); - default: throw std::logic_error("toml::format: unknown enum value"); - } -} - -template -std::basic_string -format(const value& v, std::size_t inl) -{ - switch(v.type()) - { - case value_t::Boolean : return format_impl{}(v.template cast()); - case value_t::Integer : return format_impl{}(v.template cast()); - case value_t::Float : return format_impl{}(v.template cast()); - case value_t::String : return format_impl{inl}(v.template cast()); - case value_t::Datetime: return format_impl{}(v.template cast()); - case value_t::Array : return format_impl{inl}(v.template cast()); - case value_t::Table : return format_impl{inl}(v.template cast()); - case value_t::Empty : throw std::runtime_error("toml::format: empty value"); - case value_t::Unknown : throw std::runtime_error("toml::format: unknown value"); - default: throw std::logic_error("toml::format: unknown enum value"); - } -} - -template -std::basic_string -format(std::basic_string key, const value& val) -{ - std::basic_string retval(std::move(key)); - retval += " = "; - retval += format(val); - return retval; -} - -template -std::basic_string -format(std::basic_string key, const value& val, std::size_t mk) -{ - std::basic_string retval(std::move(key)); - retval += " = "; - retval += format(val, mk); - return retval; -} - - -// ----------------------------- stream operators ----------------------------- - -namespace detail -{ - -template -struct inline_limit -{ - static_assert(std::is_same::value, "do not instantiate this"); - static const int index; - T limit; - inline_limit() = default; - ~inline_limit() = default; - constexpr inline_limit(T i): limit(i){} - constexpr operator T() const {return limit;} - - static void callback(std::ios_base::event ev, std::ios_base& ios, int idx) - { - void*& info = ios.pword(idx); - switch (ev) - { - case std::ios_base::erase_event: - { - delete static_cast(info); - break; - } - case std::ios_base::copyfmt_event: - { - info = new std::size_t(*static_cast(info)); - break; - } - case std::ios_base::imbue_event: - { - break; - } - } - } -}; - -template -const int inline_limit::index = std::ios_base::xalloc(); - -} //detail - -template> -std::basic_ostream& -operator<<(std::basic_ostream& os, - const detail::inline_limit& inl) -{ - void*& info = os.pword(detail::inline_limit::index); - if(!os.bad()) - { - if(info == nullptr) - { - os.register_callback(detail::inline_limit::callback, - detail::inline_limit::index); - info = new std::size_t(inl.limit); - } - else - { - *static_cast(info) = inl.limit; - } - } - return os; -} - -constexpr static detail::inline_limit forceinline( - std::numeric_limits::max()); - -inline detail::inline_limit make_inline(std::size_t sz) -{ - return detail::inline_limit(sz); -} - -template> -std::basic_ostream& -operator<<(std::basic_ostream& os, - const toml::value& v) -{ - std::size_t* info = - static_cast(os.pword(detail::inline_limit::index)); - return os << (info == nullptr ? toml::format(v) : toml::format(v, *info)); -} - -} -#endif // TOML11_FORMAT diff --git a/vendor/toml/from_toml.hpp b/vendor/toml/from_toml.hpp deleted file mode 100644 index 8014110..0000000 --- a/vendor/toml/from_toml.hpp +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef TOML11_FROM_TOML -#define TOML11_FROM_TOML -#include "value.hpp" - -namespace toml -{ - -template(), - typename std::enable_if<(vT != toml::value_t::Unknown && - vT != value_t::Empty), std::nullptr_t>::type = nullptr> -void from_toml(T& x, const toml::value& v) -{ - if(v.type() != vT) - throw type_error("from_toml: value type: " + stringize(v.type()) + - std::string(" is not arguemnt type: ") + stringize(vT)); - x = v.cast(); - return; -} - -template(), - typename std::enable_if<(vT == toml::value_t::Unknown) && - (!toml::detail::is_map::value) && - toml::detail::is_container::value, std::nullptr_t>::type = nullptr> -void from_toml(T& x, const toml::value& v) -{ - // TODO the case of x is not dynamic container case - if(v.type() != value_t::Array) - throw type_error("from_toml: value type: " + stringize(v.type()) + - std::string(" is not argument type: Array")); - const auto& ar = v.cast(); - try - { - toml::resize(x, ar.size()); - } - catch(std::invalid_argument& iv) - { - throw toml::type_error("toml::from_toml: static array size is not enough"); - } - auto iter = x.begin(); - for(const auto& val : ar) - { - typename T::value_type v; - from_toml(v, val); - *iter = std::move(v); - ++iter; - } - return; -} - -template(), - typename std::enable_if<(vT == toml::value_t::Unknown) && - toml::detail::is_map::value, std::nullptr_t>::type = nullptr> -void from_toml(T& x, const toml::value& v) -{ - if(v.type() != value_t::Table) - throw type_error("from_toml: value type: " + stringize(v.type()) + - std::string(" is not argument type: Table")); - x.clear(); - const auto& tb = v.cast(); - for(const auto& kv : tb) - { - x.insert(kv); - } - return; -} - -namespace detail -{ - -template -constexpr toml::value_t determine_castable_type() -{ - return check_type() != toml::value_t::Unknown ? check_type() : - toml::detail::is_map::value ? toml::value_t::Table : - toml::detail::is_container::value ? toml::value_t::Array : - toml::value_t::Unknown; -} - -template -struct from_toml_tie_impl -{ - constexpr static std::size_t index = sizeof...(Ts) - N; - constexpr static toml::value_t type_index = - determine_castable_type< - typename std::tuple_element>::type>(); - - static void invoke(std::tuple tie, const toml::value& v) - { - // static_cast is needed because with intel c++ compiler, operator== - // is only defined when the two types are strictly equal, and type_index - // is const toml::value_t, while v.type() is toml::value_t. - if(static_cast(type_index) == v.type()) - { - from_toml(std::get(tie), v); - return; - } - return from_toml_tie_impl::invoke(tie, v); - } -}; - -template -struct from_toml_tie_impl<0, Ts...> -{ - static void invoke(std::tuple tie, const toml::value& v) - { - return; - } -}; - -} // detail - -template -void from_toml(std::tuple tie, const toml::value& v) -{ - detail::from_toml_tie_impl::invoke(tie, v); - return; -} - - -} // toml -#endif // TOML11_FROM_TOML diff --git a/vendor/toml/get.hpp b/vendor/toml/get.hpp deleted file mode 100644 index c532fe5..0000000 --- a/vendor/toml/get.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef TOML11_GET -#define TOML11_GET -#include "value.hpp" -#include - -namespace toml -{ - -template(), - typename std::enable_if<(vT != toml::value_t::Unknown && - vT != value_t::Empty), std::nullptr_t>::type = nullptr> -inline T get(const toml::value& v) -{ - return static_cast(v.cast()); -} - -// array-like type -template(), - typename std::enable_if<(vT == toml::value_t::Unknown) && - (!toml::detail::is_map::value) && - toml::detail::is_container::value, std::nullptr_t>::type = nullptr> -T get(const toml::value& v) -{ - if(v.type() != value_t::Array) - throw type_error("get: value type: " + stringize(v.type()) + - std::string(" is not argument type: Array")); - - const auto& ar = v.cast(); - T tmp; - try - { - toml::resize(tmp, ar.size()); - } - catch(std::invalid_argument& iv) - { - throw toml::type_error("toml::get: static array size is not enough"); - } - std::transform(ar.cbegin(), ar.cend(), tmp.begin(), - [](toml::value const& elem){return get(elem);}); - return tmp; -} - -// table-like case -template(), - typename std::enable_if<(vT == toml::value_t::Unknown) && - toml::detail::is_map::value, std::nullptr_t>::type = nullptr> -T get(const toml::value& v) -{ - if(v.type() != value_t::Table) - throw type_error("get: value type: " + stringize(v.type()) + - std::string(" is not argument type: Table")); - T tmp; - const auto& tb = v.cast(); - for(const auto& kv : tb){tmp.insert(kv);} - return tmp; -} - -// get_or ----------------------------------------------------------------- - -template -inline typename std::remove_cv::type>::type -get_or(const toml::Table& tab, const toml::key& ky, T&& opt) -{ - if(tab.count(ky) == 0) {return std::forward(opt);} - return get::type>::type>(tab.find(ky)->second); -} - -} // toml -#endif// TOML11_GET diff --git a/vendor/toml/parser.hpp b/vendor/toml/parser.hpp deleted file mode 100644 index f470330..0000000 --- a/vendor/toml/parser.hpp +++ /dev/null @@ -1,1130 +0,0 @@ -#ifndef TOML11_PARSER -#define TOML11_PARSER -#include "value.hpp" -#include "acceptor.hpp" -#include -#include -#include -#include -#include - -namespace toml -{ - -namespace detail -{ - -// it is just an inferior vertion of boost/std::optional -template -struct result -{ - result() : ok_(false){} - ~result() = default; - result(const result& rhs) = default; - result(result&& rhs) = default; - result& operator=(const result& rhs) = default; - result& operator=(result&& rhs) = default; - - result(const T& v) : ok_(true), value_(v){} - result(T&& v) : ok_(true), value_(std::move(v)){} - result& operator=(const T& rhs){ok_ = true; value_ = rhs; return *this;} - result& operator=(T&& rhs) {ok_ = true; value_ = rhs; return *this;} - - template - result& operator=(const result& u) {ok_ = u.ok(); if(ok_)value_ = u.move(); return *this;} - template - result& operator=(result&& u) {ok_ = u.ok(); if(ok_)value_ = u.move(); return *this;} - template - result(const result& u): ok_(u.ok()){if(ok_)value_ = u.get();} - template - result(result&& u): ok_(u.ok()){if(ok_)value_ = u.move();} - - bool ok() const {return ok_;} - operator bool() const {return ok_;} - - T& get() {if(!ok_) throw std::logic_error("result::get"); return value_;} - T const& get() const {if(!ok_) throw std::logic_error("result::get"); return value_;} - T&& move() - {if(!ok_) throw std::logic_error("result::move"); ok_ = false; return std::move(value_);} - - private: - bool ok_; - T value_; -}; - -}//detail - -struct parse_escape_sequence -{ - typedef toml::character value_type; - typedef toml::String string_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair invoke(Iterator iter, Iterator end) - { - const auto beg = iter; - if(iter == end || *iter != '\\') - return std::make_pair(result_type{}, iter); - ++iter; - switch(*iter) - { - case '\\': return std::make_pair(string_type("\\"), std::next(iter)); - case '"' : return std::make_pair(string_type("\""), std::next(iter)); - case 'b' : return std::make_pair(string_type("\b"), std::next(iter)); - case 't' : return std::make_pair(string_type("\t"), std::next(iter)); - case 'n' : return std::make_pair(string_type("\n"), std::next(iter)); - case 'f' : return std::make_pair(string_type("\f"), std::next(iter)); - case 'r' : return std::make_pair(string_type("\r"), std::next(iter)); - case 'u' : - { - if(std::distance(iter, end) < 5) - throw std::make_pair(iter, syntax_error( - "invalid escape sequence: " + std::string(beg, end))); - return std::make_pair(utf8_to_char(make_codepoint( - string_type(iter+1, iter+5))), iter+5); - } - case 'U': - { - if(std::distance(iter, end) < 8) - throw std::make_pair(iter, syntax_error( - "invalid escape sequence: " + std::string(beg, end))); - return std::make_pair(utf8_to_char(make_codepoint( - string_type(iter+1, iter+9))), iter+9); - } - default: throw std::make_pair(iter, syntax_error( - "unkwnon escape sequence: " + std::string(iter, end))); - } - } - - static unsigned int make_codepoint(string_type str) - { - unsigned int codepoint; - std::basic_istringstream iss(str); - iss >> std::hex >> codepoint; - return codepoint; - } - - static string_type utf8_to_char(const unsigned int codepoint) - { - string_type character; - if(codepoint < 0x80) - { - character += static_cast(codepoint); - } - else if(codepoint < 0x800) - { - character += static_cast(0xC0| codepoint >> 6); - character += static_cast(0x80|(codepoint & 0x3F)); - } - else if(codepoint < 0x10000) - { - character += static_cast(0xE0| codepoint >>12); - character += static_cast(0x80|(codepoint >>6&0x3F)); - character += static_cast(0x80|(codepoint & 0x3F)); - } - else - { - character += static_cast(0xF0| codepoint >>18); - character += static_cast(0x80|(codepoint >>12&0x3F)); - character += static_cast(0x80|(codepoint >>6 &0x3F)); - character += static_cast(0x80|(codepoint & 0x3F)); - } - return character; - } -}; - -struct parse_basic_inline_string -{ - typedef toml::character value_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = - is_basic_inline_string::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - if(std::distance(iter, end) < 2) - throw internal_error("is_basic_inline_string"); - - toml::String result; result.reserve(std::distance(iter, end)-2); - ++iter; - const Iterator last = std::prev(end); // ignore '"' - while(iter != last) - { - if(*iter == '\\') - { - auto r = parse_escape_sequence::invoke(iter, last); - if(!r.first.ok()) - throw internal_error("parse_basic_inline_string"); - result += r.first.move(); - iter = r.second; - } - else - { - result += *iter; - ++iter; - } - } - return std::make_pair(result, end); - } -}; - -struct parse_basic_multiline_string -{ - typedef toml::character value_type; - typedef toml::String string_type; - typedef detail::result result_type; - - typedef is_chain_of, is_newline> - is_line_ending_backslash; - typedef is_repeat_of, is_newline>, - repeat_infinite()> ws_nl_after_backslash_remover; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = - is_basic_multiline_string::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - if(std::distance(iter, end) < 6) - throw internal_error("is_basic_inline_string"); - - toml::String result; result.reserve(std::distance(iter, end)-6); - std::advance(iter, 3); - const Iterator last = end - 3; - iter = is_newline::invoke(iter, last); - while(iter != last) - { - if(*iter == '\\') - { - if(is_line_ending_backslash::invoke(iter, last) != iter) - { - iter = ws_nl_after_backslash_remover::invoke(std::next(iter), last); - } - else - { - auto r = parse_escape_sequence::invoke(iter, last); - if(!r.first.ok()) - throw internal_error("parse_basic_inline_string"); - result += r.first.move(); - iter = r.second; - } - } - else - { - result.push_back(*iter); - ++iter; - } - } - return std::make_pair(result, end); - } -}; - -struct parse_literal_inline_string -{ - typedef toml::character value_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = - is_literal_inline_string::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - if(std::distance(iter, end) < 2) - throw internal_error("is_literal_inline_string"); - - toml::String result; result.reserve(std::distance(iter, end)-2); - ++iter; - const Iterator last = end - 1; - while(iter != last) - { - result.push_back(*iter); - ++iter; - } - return std::make_pair(result, end); - } -}; - -struct parse_literal_multiline_string -{ - typedef toml::character value_type; - typedef detail::result result_type; - - typedef is_chain_of, is_newline> - is_line_ending_backslash; - typedef is_repeat_of, is_newline>, - repeat_infinite()> ws_nl_after_backslash_remover; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = - is_literal_multiline_string::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - if(std::distance(iter, end) < 6) - throw internal_error("is_literal_multiline_string"); - - toml::String result; result.reserve(std::distance(iter, end)-6); - std::advance(iter, 3); - const Iterator last = end - 3; - iter = is_newline::invoke(iter, last); // trim first newline if exist - while(iter != last) - { - result.push_back(*iter); - ++iter; - } - return std::make_pair(result, end); - } -}; - -struct parse_string -{ - typedef toml::character value_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - std::pair result; - if((result = parse_basic_inline_string::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_basic_multiline_string::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_literal_inline_string::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_literal_multiline_string::invoke(iter, range_end)).first.ok()) - return result; - else - return std::make_pair(result_type{}, iter); - } -}; - -struct parse_integer -{ - typedef toml::character value_type; - typedef std::basic_string string_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair invoke(Iterator iter, Iterator range_end) - { - const Iterator end = is_integer::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - string_type result; result.resize(std::distance(iter, end)); - std::copy_if(iter, end, result.begin(), [](value_type c){return c != '_';}); - return std::make_pair(std::stoll(result), end); - } -}; - -struct parse_float -{ - typedef toml::character value_type; - typedef std::basic_string string_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = is_float::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - string_type result; result.resize(std::distance(iter, end)); - std::copy_if(iter, end, result.begin(), [](value_type c){return c != '_';}); - try{ - return std::make_pair(std::stod(result), end); - } - catch(std::out_of_range& oor) - { - std::cout << "extremely large Float value appeared: " - << result << "; it is negrected" << std::endl; - return std::make_pair(0, end); - } - } -}; - -struct parse_boolean -{ - typedef toml::character value_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = is_boolean::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - return std::make_pair((std::distance(iter, end) == 4), end); - } -}; - -struct parse_local_time -{ - typedef toml::character value_type; - typedef std::basic_string string_type; - typedef detail::result result_type; - typedef typename toml::Datetime::number_type number_type; - template - using nums = is_repeat_of, N>; - typedef is_character delim; - typedef is_character fract; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = is_local_time::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - toml::Datetime result; - result.hour = std::stoi(string_type(iter, nums<2>::invoke(iter, end))); - iter = delim::invoke(nums<2>::invoke(iter, end), end); - result.minute = std::stoi(string_type(iter, nums<2>::invoke(iter, end))); - iter = delim::invoke(nums<2>::invoke(iter, end), end); - result.second = std::stoi(string_type(iter, nums<2>::invoke(iter, end))); - iter = fract::invoke(nums<2>::invoke(iter, end), end); - if(iter == end) - { - result.millisecond = 0.0; - result.microsecond = 0.0; - } - else if(std::distance(iter, end) <= 3) - { - result.millisecond = parse_number(iter, end); - result.microsecond = 0.0; - } - else - { - result.millisecond = parse_number(iter, iter + 3); - result.microsecond = parse_number(iter + 3, end); - } - result.offset_hour = toml::Datetime::nooffset; - result.offset_minute = toml::Datetime::nooffset; - result.year = toml::Datetime::undef; - result.month = toml::Datetime::undef; - result.day = toml::Datetime::undef; - return std::make_pair(result, end); - } - - template::value_type, - value_type>::value>::type> - static number_type parse_number(Iterator iter, Iterator end) - { - if(std::distance(iter, end) > 3) end = iter + 3; - string_type str(iter, end); - while(str.size() < 3){str += '0';} - return std::stoi(str); - } -}; - -struct parse_local_date -{ - typedef toml::character value_type; - typedef std::basic_string string_type; - typedef detail::result result_type; - template - using nums = is_repeat_of, N>; - typedef is_character delim; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = is_local_date::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - toml::Datetime result; - result.year = std::stoi(string_type(iter, nums<4>::invoke(iter, end))); - iter = delim::invoke(nums<4>::invoke(iter, end), end); - result.month = std::stoi(string_type(iter, nums<2>::invoke(iter, end))); - iter = delim::invoke(nums<2>::invoke(iter, end), end); - result.day = std::stoi(string_type(iter, nums<2>::invoke(iter, end))); - - result.offset_hour = toml::Datetime::nooffset; - result.offset_minute = toml::Datetime::nooffset; - result.hour = toml::Datetime::undef; - result.minute = toml::Datetime::undef; - result.second = toml::Datetime::undef; - result.millisecond = toml::Datetime::undef; - result.microsecond = toml::Datetime::undef; - return std::make_pair(result, end); - } -}; - -struct parse_local_date_time -{ - typedef toml::character value_type; - typedef std::basic_string string_type; - typedef detail::result result_type; - template - using nums = is_repeat_of, N>; - typedef is_character delim; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = - is_local_date_time::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - auto ld = parse_local_date::invoke(iter, end); - if(!ld.first.ok()) - throw std::make_pair(iter, syntax_error("invalid local datetime")); - toml::Datetime result(ld.first.move()); - iter = delim::invoke(ld.second, end);// 'T' - - const auto time = parse_local_time::invoke(iter, end); - result.hour = time.first.get().hour; - result.minute = time.first.get().minute; - result.second = time.first.get().second; - result.millisecond = time.first.get().millisecond; - result.microsecond = time.first.get().microsecond; - result.offset_hour = toml::Datetime::nooffset; - result.offset_minute = toml::Datetime::nooffset; - return std::make_pair(result, end); - } -}; - -struct parse_offset_date_time -{ - typedef toml::character value_type; - typedef std::basic_string string_type; - typedef detail::result result_type; - template - using nums = is_repeat_of, N>; - typedef is_character delim; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = - is_offset_date_time::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - auto ldt = parse_local_date_time::invoke(iter, end); - if(!ldt.first.ok()) - throw std::make_pair(iter, syntax_error("invalid offset datetime")); - toml::Datetime result(ldt.first.move()); - iter = ldt.second; - if(*iter == 'Z') - { - result.offset_hour = 0; - result.offset_minute = 0; - } - else - { - if(*iter != '+' && *iter != '-') - throw std::make_pair(iter, syntax_error("invalid offset-datetime")); - const int sign = (*iter == '-') ? -1 : 1; - ++iter; - result.offset_hour = sign * - std::stoi(string_type(iter, nums<2>::invoke(iter, end))); - iter = delim::invoke(nums<2>::invoke(iter, end), end); - result.offset_minute = sign * - std::stoi(string_type(iter, nums<2>::invoke(iter, end))); - } - return std::make_pair(result, end); - } -}; - -struct parse_datetime -{ - typedef toml::character value_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - std::pair result; - if((result = parse_offset_date_time::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_local_date_time::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_local_date::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_local_time::invoke(iter, range_end)).first.ok()) - return result; - else - return std::make_pair(result_type{}, iter); - } -}; - -template -struct parse_fixed_type_array -{ - typedef toml::character value_type; - typedef detail::result result_type; - typedef acceptorT acceptor_type; - typedef parserT parser_type; - typedef is_skippable_in_array skippable; - - template::value_type, - value_type>::value>::type> - static std::pair invoke(Iterator iter, Iterator range_end) - { - const Iterator end = - is_fixed_type_array::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - toml::Array result; - const Iterator last = std::prev(end); - iter = skippable::invoke(std::next(iter), last); - while(iter != last) - { - const Iterator tmp = acceptor_type::invoke(iter, last); - if(tmp == iter) - throw std::make_pair(iter, syntax_error("parse_array")); - auto next = parser_type::invoke(iter, last); - if(!next.first.ok()) - throw std::make_pair(iter, syntax_error("parse_array")); - result.emplace_back(next.first.move()); - iter = tmp; - iter = skippable::invoke(iter, last); - iter = is_character::invoke(iter, last); - iter = skippable::invoke(iter, last); - } - return std::make_pair(result, end); - } -}; - -template -struct parse_inline_table; - -template -struct parse_array -{ - typedef charT value_type; - static_assert(std::is_same::value, ""); - typedef detail::result result_type; - typedef is_skippable_in_array skippable; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - if(iter == is_array::invoke(iter, range_end)) - return std::make_pair(result_type{}, iter); - - std::pair result; - if((result = parse_fixed_type_array, - parse_boolean>::invoke(iter, range_end)).first.ok()) return result; - else if((result = parse_fixed_type_array, - parse_string>::invoke(iter, range_end)).first.ok()) return result; - else if((result = parse_fixed_type_array, - parse_datetime>::invoke(iter, range_end)).first.ok()) return result; - else if((result = parse_fixed_type_array, - parse_float>::invoke(iter, range_end)).first.ok()) return result; - else if((result = parse_fixed_type_array, - parse_integer>::invoke(iter, range_end)).first.ok()) return result; - else if((result = parse_fixed_type_array, - parse_array>::invoke(iter, range_end)).first.ok()) return result; - else if((result = parse_fixed_type_array, - parse_inline_table>::invoke(iter, range_end)).first.ok()) - return result; - else if(skippable::invoke(std::next(iter), range_end) == // empty - std::prev(is_array::invoke(iter, range_end)) - ) return std::make_pair( - toml::Array{}, is_array::invoke(iter, range_end)); - else throw std::make_pair(iter, syntax_error("no valid array here")); - } -}; - -template -struct parse_value -{ - typedef charT value_type; - static_assert(std::is_same::value, ""); - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - std::pair result; - if((result = parse_boolean::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_string::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_datetime::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_float::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_integer::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_array::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_inline_table::invoke(iter, range_end)).first.ok()) - return result; - else - return std::make_pair(result_type{}, iter); - } -}; - -struct parse_barekey -{ - typedef toml::character value_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = is_barekey::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - return std::make_pair(toml::key(iter, end), end); - } -}; - -struct parse_key -{ - typedef toml::character value_type; - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - std::pair result; - if((result = parse_barekey::invoke(iter, range_end)).first.ok()) - return result; - else if((result = parse_string::invoke(iter, range_end)).first.ok()) - return result; - else return std::make_pair(result_type{}, iter); - } -}; - -template -struct parse_key_value_pair -{ - typedef charT value_type; - static_assert(std::is_same::value, ""); - typedef detail::result> result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - auto tmp_key = parse_key::invoke(iter, range_end); - if(!tmp_key.first.ok()) - return std::make_pair(result_type{}, iter); - iter = is_any_num_of_ws::invoke(tmp_key.second, range_end); - if(*iter != '=') - throw std::make_pair(iter, syntax_error("invalid key value pair")); - iter = is_any_num_of_ws::invoke(std::next(iter), range_end); - - auto tmp_value = parse_value::invoke(iter, range_end); - if(!tmp_value.first.ok()) - throw std::make_pair(iter, syntax_error("invalid key value pair")); - - iter = tmp_value.second; - - return std::make_pair(std::make_pair( - tmp_key.first.move(), tmp_value.first.move()), - is_any_num_of_ws::invoke(tmp_value.second, range_end)); - } -}; - -template -struct parse_inline_table -{ - typedef charT value_type; - static_assert(std::is_same::value, ""); - typedef detail::result result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = is_inline_table::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - iter = is_any_num_of_ws::invoke(std::next(iter), range_end); - - const Iterator last = std::prev(end); - toml::Table result; - while(iter != last) - { - auto tmp = parse_key_value_pair::invoke(iter, last); - if(!tmp.first.ok()) - throw std::make_pair(iter, syntax_error("parse_inline_table")); - - result.emplace(tmp.first.move()); - iter = tmp.second; - - iter = is_any_num_of_ws::invoke(iter, last); - iter = is_character::invoke(iter, last); - iter = is_any_num_of_ws::invoke(iter, last); - } - return std::make_pair(result, end); - } -}; - -struct parse_table_definition -{ - typedef toml::character value_type; - typedef detail::result> result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = - is_table_definition::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - std::vector result; - result.reserve(std::count(iter, end, '.')+1); - - const Iterator last = std::prev(end); - iter = is_any_num_of_ws::invoke(iter, last); - iter = is_any_num_of_ws::invoke(std::next(iter), last); - - auto tmp = parse_key::invoke(iter, last); - if(!tmp.first.ok()) - throw std::make_pair(iter, syntax_error("table definition")); - result.emplace_back(tmp.first.move()); - iter = is_any_num_of_ws::invoke(tmp.second, last); - - while(iter != last) - { - iter = is_character::invoke(iter, last); - iter = is_any_num_of_ws::invoke(iter, last); - - tmp = parse_key::invoke(iter, last); - if(!tmp.first.ok()) - throw std::make_pair(iter, syntax_error("table definition")); - result.emplace_back(tmp.first.move()); - iter = is_any_num_of_ws::invoke(tmp.second, last); - } - return std::make_pair(result, end); - } -}; - -struct parse_array_of_table_definition -{ - typedef toml::character value_type; - typedef detail::result> result_type; - - template::value_type, - value_type>::value>::type> - static std::pair - invoke(Iterator iter, Iterator range_end) - { - const Iterator end = - is_array_of_table_definition::invoke(iter, range_end); - if(iter == end) return std::make_pair(result_type{}, iter); - - if(std::distance(iter, end) < 5) - throw std::make_pair(iter, syntax_error("invalid array_of_table definition")); - - std::vector result; - result.reserve(std::count(iter, end, '.')+1); - - const Iterator last = end - 2; - iter = is_any_num_of_ws::invoke(iter, last) + 2; - iter = is_any_num_of_ws::invoke(iter, last); - - auto tmp = parse_key::invoke(iter, last); - if(!tmp.first.ok()) - throw std::make_pair(iter, syntax_error("array of table definition")); - result.emplace_back(tmp.first.move()); - iter = is_any_num_of_ws::invoke(tmp.second, last); - - while(iter != last) - { - iter = is_character::invoke(iter, last); - iter = is_any_num_of_ws::invoke(iter, last); - - tmp = parse_key::invoke(iter, last); - if(!tmp.first.ok()) - throw std::make_pair(iter, syntax_error("array of table definition")); - result.emplace_back(tmp.first.move()); - iter = is_any_num_of_ws::invoke(tmp.second, last); - } - return std::make_pair(result, end); - } -}; - -struct parse_data -{ - typedef toml::character value_type; - typedef toml::Table result_type; - - template::value_type, - value_type>::value>::type> - static result_type invoke(Iterator iter, const Iterator end) - { - toml::Table result; - auto noname = parse_table_contents(iter, end); - result = std::move(noname.first); - iter = skip_empty(noname.second, end); - - while(iter != end) - { - iter = skip_empty(iter, end); - std::pair>, Iterator> tabname; - if((tabname = parse_table_definition::invoke(iter, end)).first.ok()) - { - auto contents = parse_table_contents(tabname.second, end); - push_table(result, std::move(contents.first), - tabname.first.get().begin(), tabname.first.get().end()); - iter = contents.second; - } - else if((tabname = parse_array_of_table_definition::invoke(iter, end)).first.ok()) - { - auto contents = parse_table_contents(tabname.second, end); - push_array_of_table(result, std::move(contents.first), - tabname.first.get().begin(), tabname.first.get().end()); - iter = contents.second; - } - else - throw std::make_pair(iter, syntax_error("parse_data: unknown line")); - } - return result; - } - - template::value_type, - value_type>::value>::type> - static Iterator - skip_empty(Iterator iter, Iterator end) - { - while(iter != end) - { - if(*iter == '#') - { - while(iter != end && - iter == is_newline::invoke(iter, end)){++iter;} - } - else if(iter == is_newline::invoke(iter, end) && - iter == is_whitespace::invoke(iter, end)) - { - return iter; - } - else - { - ++iter; - } - } - return iter; - } - - template::value_type, - value_type>::value>::type> - static std::pair - parse_table_contents(Iterator iter, Iterator end) - { - toml::Table table; - iter = skip_empty(iter, end); - while(iter != end) - { - auto kv = parse_key_value_pair::invoke(iter, end); - if(!kv.first.ok()) return std::make_pair(table, iter); - - table.emplace(kv.first.move()); - iter = kv.second; - iter = skip_empty(iter, end); - } - return std::make_pair(table, iter); - } - - template::value_type, - toml::key>::value>::type> - static void - push_table(toml::Table& data, toml::Table&& v, Iterator iter, Iterator end) - { - if(iter == std::prev(end)) - { - if(data.count(*iter) == 1) - throw syntax_error("duplicate key: " + *iter); - data.emplace(*iter, std::move(v)); - return; - } - - if(data.count(*iter) == 0) - { - data.emplace(*iter, toml::Table()); - return push_table(data[*iter].template cast(), - std::move(v), std::next(iter), end); - } - else if(data[*iter].type() == value_t::Table) - { - return push_table(data[*iter].template cast(), - std::move(v), std::next(iter), end); - } - else if(data[*iter].type() == value_t::Array) - { - auto& ar = data[*iter].template cast(); - if(ar.empty()) ar.emplace_back(toml::Table{}); - if(ar.back().type() != value_t::Table) - throw syntax_error("assign table into array having non-table type: " + *iter); - return push_table(ar.back().template cast(), - std::move(v), std::next(iter), end); - } - else - throw syntax_error("assign table into not table: " + *iter); - } - - template::value_type, - toml::key>::value>::type> - static void - push_array_of_table(toml::Table& data, toml::Table&& v, - Iterator iter, Iterator end) - { - //XXX Iterator::value_type == toml::key - if(iter == std::prev(end)) - { - if(data.count(*iter) == 0) - data.emplace(*iter, toml::Array()); - else if(data.at(*iter).type() != value_t::Array) - throw syntax_error("duplicate key: " + *iter); - - data[*iter].template cast().emplace_back(std::move(v)); - return; - } - - if(data.count(*iter) == 0) - { - data.emplace(*iter, toml::Table()); - return push_array_of_table(data[*iter].template cast(), - std::move(v), std::next(iter), end); - } - else if(data[*iter].type() == value_t::Table) - { - return push_array_of_table(data[*iter].template cast(), - std::move(v), std::next(iter), end); - } - else if(data[*iter].type() == value_t::Array) - { - auto& ar = data[*iter].template cast(); - if(ar.empty()) ar.emplace_back(toml::Table{}); - if(ar.back().type() != value_t::Table) - throw syntax_error("assign table into array having non-table type: " + *iter); - return push_array_of_table(ar.back().template cast(), - std::move(v), std::next(iter), end); - } - else - throw syntax_error("assign array of table into not table: " + *iter); - } - -}; - -template> -toml::Table parse(std::basic_istream& is) -{ - const auto initial = is.tellg(); - is.seekg(0, std::ios::end); - const auto eofpos = is.tellg(); - const std::size_t size = eofpos - initial; - is.seekg(initial); - std::vector contents(size); - typedef std::vector::const_iterator iterator_type; - is.read(contents.data(), size); - try - { - return parse_data::invoke(contents.cbegin(), contents.cend()); - } - catch(std::pair iter_except) - { - std::cerr << "toml syntax error." << std::endl; - auto iter = iter_except.first; - const std::size_t nline = 1 + std::count(contents.cbegin(), iter, '\n'); - std::cerr << "processing at line " << nline << std::endl; - while(*iter != '\n' && iter != contents.cbegin()){--iter;} - ++iter; - while(*iter != '\n' && iter != contents.cend()) - { - std::cerr << *iter; ++iter; - } - std::cerr << std::endl; - - throw iter_except.second; - } -} - -inline toml::Table parse(const char* filename) -{ - std::ifstream ifs(filename, std::ios_base::in | std::ios_base::binary); - if(!ifs.good()) - { - throw std::runtime_error("file open error: " + std::string(filename)); - } - return parse(ifs); -} - -template> -inline toml::Table parse(const std::basic_string& filename) -{ - std::ifstream ifs(filename, std::ios_base::in | std::ios_base::binary); - if(!ifs.good()) - { - throw std::runtime_error("file open error: " + filename); - } - return parse(ifs); -} - - - -}// toml -#endif// TOML11_PARSER diff --git a/vendor/toml/to_toml.hpp b/vendor/toml/to_toml.hpp deleted file mode 100644 index 9f373dd..0000000 --- a/vendor/toml/to_toml.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef TOML11_TO_TOML -#define TOML11_TO_TOML -#include "value.hpp" - -namespace toml -{ - -template(), - typename std::enable_if<(vT != toml::value_t::Unknown && - vT != value_t::Empty), std::nullptr_t>::type = nullptr> -inline toml::value to_toml(T&& x) -{ - return toml::value(std::forward(x)); -} - -template(), - typename std::enable_if<(vT == toml::value_t::Unknown) && - (!toml::detail::is_map::value) && - toml::detail::is_container::value, std::nullptr_t>::type = nullptr> -toml::value to_toml(T&& x) -{ - toml::Array tmp; tmp.reserve(std::distance(std::begin(x), std::end(x))); - for(auto iter = std::begin(x); iter != std::end(x); ++iter) - tmp.emplace_back(*iter); - return toml::value(std::move(tmp)); -} - -template(), - typename std::enable_if<(vT == toml::value_t::Unknown) && - toml::detail::is_map::value, std::nullptr_t>::type = nullptr> -toml::value to_toml(T&& x) -{ - toml::Table tmp; - for(auto iter = std::begin(x); iter != std::end(x); ++iter) - tmp.emplace(iter->first, to_toml(iter->second)); - return toml::value(std::move(tmp)); -} - -template -inline toml::value to_toml(std::initializer_list init) -{ - return toml::value(std::move(init)); -} - -inline toml::value -to_toml(std::initializer_list> init) -{ - return toml::value(std::move(init)); -} - -} // toml -#endif // TOML11_TO_TOML diff --git a/vendor/toml/traits.hpp b/vendor/toml/traits.hpp deleted file mode 100644 index 5e6e425..0000000 --- a/vendor/toml/traits.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef TOML11_TRAITS -#define TOML11_TRAITS -#include - -namespace toml -{ -namespace detail -{ - -template -using unwrap_t = typename std::decay::type; - -struct has_iterator_impl -{ - template static std::true_type check(typename T::iterator*); - template static std::false_type check(...); -}; -struct has_value_type_impl -{ - template static std::true_type check(typename T::value_type*); - template static std::false_type check(...); -}; -struct has_key_type_impl -{ - template static std::true_type check(typename T::key_type*); - template static std::false_type check(...); -}; -struct has_mapped_type_impl -{ - template static std::true_type check(typename T::mapped_type*); - template static std::false_type check(...); -}; -struct has_resize_method_impl -{ - constexpr static std::size_t dummy=0; - template static std::true_type check(decltype(std::declval().resize(dummy))*); - template static std::false_type check(...); -}; - -/// Intel C++ compiler can not use decltype in parent class declaration, here -/// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076 -#ifdef __INTEL_COMPILER -#define decltype(...) std::enable_if::type -#endif - -template -struct has_iterator : decltype(has_iterator_impl::check(nullptr)){}; -template -struct has_value_type : decltype(has_value_type_impl::check(nullptr)){}; -template -struct has_key_type : decltype(has_key_type_impl::check(nullptr)){}; -template -struct has_mapped_type : decltype(has_mapped_type_impl::check(nullptr)){}; -template -struct has_resize_method : decltype(has_resize_method_impl::check(nullptr)){}; - -#ifdef __INTEL_COMPILER -#undef decltype(...) -#endif - -template -struct is_container : std::integral_constant::value && has_value_type::value>{}; - -template -struct is_map : std::integral_constant::value && has_key_type::value && - has_mapped_type::value>{}; - -}// detail -}//toml -#endif // TOML_TRAITS diff --git a/vendor/toml/utility.hpp b/vendor/toml/utility.hpp deleted file mode 100644 index 0df0d07..0000000 --- a/vendor/toml/utility.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef TOML11_UTILITY -#define TOML11_UTILITY -#include "traits.hpp" -#include -#include - -namespace toml -{ - -template -inline std::unique_ptr make_unique(Ts&& ... args) -{ - return std::unique_ptr(new T(std::forward(args)...)); -} - -namespace detail -{ - -template -inline void resize_impl(T& container, std::size_t N, std::true_type) -{ - container.resize(N); - return ; -} - -template -inline void resize_impl(T& container, std::size_t N, std::false_type) -{ - if(container.size() >= N) return; - else throw std::invalid_argument("not resizable type"); -} - -} - -template -inline void resize(T& container, std::size_t N) -{ - if(container.size() == N) return; - else return detail::resize_impl(container, N, detail::has_resize_method()); -} - -}// toml -#endif // TOML11_UTILITY diff --git a/vendor/toml/value.hpp b/vendor/toml/value.hpp deleted file mode 100644 index d773c24..0000000 --- a/vendor/toml/value.hpp +++ /dev/null @@ -1,719 +0,0 @@ -#ifndef TOML11_VALUE -#define TOML11_VALUE -#include "datetime.hpp" -#include "traits.hpp" -#include "utility.hpp" -#include "exception.hpp" -#include -#include -#include -#include -#include - -namespace toml -{ - -using character = char; - -class value; -using key = std::basic_string; - -using Boolean = bool; -using Integer = std::int64_t; -using Float = double; -using String = std::basic_string; -using Datetime = basic_datetime; -using Array = std::vector; -using Table = std::unordered_map; - -enum class value_t : std::uint8_t -{ - Boolean = 1, - Integer = 2, - Float = 3, - String = 4, - Datetime = 5, - Array = 6, - Table = 7, - Empty = 0, - Unknown = 255, -}; - -template> -inline std::basic_ostream& -operator<<(std::basic_ostream& os, value_t t) -{ - switch(t) - { - case toml::value_t::Boolean : os << "Boolean"; return os; - case toml::value_t::Integer : os << "Integer"; return os; - case toml::value_t::Float : os << "Float"; return os; - case toml::value_t::String : os << "String"; return os; - case toml::value_t::Datetime: os << "Datetime"; return os; - case toml::value_t::Array : os << "Array"; return os; - case toml::value_t::Table : os << "Table"; return os; - case toml::value_t::Empty : os << "Empty"; return os; - case toml::value_t::Unknown : os << "Unknown"; return os; - default : os << "Nothing"; return os; - } -} - -template, - typename alloc = std::allocator> -inline std::basic_string -stringize(value_t t) -{ - switch(t) - { - case toml::value_t::Boolean : return "Boolean"; - case toml::value_t::Integer : return "Integer"; - case toml::value_t::Float : return "Float"; - case toml::value_t::String : return "String"; - case toml::value_t::Datetime: return "Datetime"; - case toml::value_t::Array : return "Array"; - case toml::value_t::Table : return "Table"; - case toml::value_t::Empty : return "Empty"; - case toml::value_t::Unknown : return "Unknown"; - default : return "Nothing"; - } -} - -namespace detail -{ - -template -constexpr inline value_t check_type() -{ - return std::is_same, toml::Boolean >::value ? value_t::Boolean : - std::is_integral>::value ? value_t::Integer : - std::is_floating_point>::value ? value_t::Float : - std::is_convertible, toml::String >::value ? value_t::String : - std::is_convertible, toml::Datetime>::value ? value_t::Datetime: - std::is_convertible, toml::Array >::value ? value_t::Array : - std::is_convertible, toml::Table >::value ? value_t::Table : - value_t::Unknown; -} - -constexpr inline bool is_valid(value_t vt) -{ - return vt != value_t::Unknown; -} - -template struct is_toml_array : std::false_type{}; -template<> struct is_toml_array : std::true_type {}; -template struct is_toml_table : std::false_type{}; -template<> struct is_toml_table : std::true_type {}; - -struct is_key_convertible_impl -{ - template - static std::is_convertible - check(typename T::key_type*); - - template static std::false_type check(...); -}; - -/// Intel C++ compiler can not use decltype in parent class declaration, here -/// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076 -#ifdef __INTEL_COMPILER -#define decltype(...) std::enable_if::type -#endif - -template -struct is_key_convertible : decltype(is_key_convertible_impl::check(nullptr)){}; - -#ifdef __INTEL_COMPILER -#undef decltype(...) -#endif - -template struct toml_default_type{}; -template<> struct toml_default_type{typedef Boolean type;}; -template<> struct toml_default_type{typedef Integer type;}; -template<> struct toml_default_type{typedef Float type;}; -template<> struct toml_default_type{typedef String type;}; -template<> struct toml_default_type{typedef Datetime type;}; -template<> struct toml_default_type{typedef Array type;}; -template<> struct toml_default_type{typedef Table type;}; -template<> struct toml_default_type{typedef void type;}; -template<> struct toml_default_type{typedef void type;}; - -struct storage_base -{ - storage_base(): type(toml::value_t::Empty){} - storage_base(toml::value_t t): type(t){} - virtual ~storage_base() = default; - toml::value_t type; -}; - -template -struct storage : public storage_base -{ - static_assert(is_toml_array::value || is_toml_table::value, - "toml::detail::storage is for toml::Array or toml::Table!"); - typedef T value_type; - - storage() = default; - ~storage() noexcept override = default; - storage(storage const&) = default; - storage(storage&&) = default; - storage& operator=(storage const&) = default; - storage& operator=(storage&&) = default; - - storage(value_type const& v) : value(v){} - storage(value_type&& v) : value(std::move(v)){} - - value_type value; -}; -} // detail - -template -struct value_traits -{ - constexpr static value_t type_index = detail::check_type(); - constexpr static bool is_toml_type = detail::is_valid(detail::check_type()); - typedef typename detail::toml_default_type::type type; -}; - -class value -{ - typedef std::unique_ptr storage_ptr; - - public: - - value() : type_(value_t::Empty){} - ~value(); - - value(const value& v); - value(value&& v); - value& operator=(const value& v); - value& operator=(value&& v); - - template::is_toml_type, std::nullptr_t>::type = nullptr> - value(T&& v); - - template::is_toml_type, std::nullptr_t>::type = nullptr> - value& operator=(T&& v); - - template::is_toml_type, std::nullptr_t>::type = nullptr> - value(std::initializer_list init); - - value(std::initializer_list> init); - - value_t type() const {return type_;} - - template - typename detail::toml_default_type::type const& cast() const; - template - typename detail::toml_default_type::type& cast(); - - private: - - void switch_clean(value_t t); - template struct switch_assign; - template struct switch_cast; - - static bool should_be_cleaned(value_t vt) - { - return (vt == value_t::String) || (vt == value_t::Array) || - (vt == value_t::Table) || (vt == value_t::Datetime); - } - - private: - - value_t type_; - union - { - Boolean boolean_; - Integer integer_; - Float float_; - String string_; - Datetime datetime_; - storage_ptr storage_; //ptr to table or array - }; -}; - -template<> struct value::switch_assign -{ - template - static void invoke(value& v, valT&& val) - { - v.boolean_ = static_cast(val); - } -}; -template<> struct value::switch_assign -{ - template - static void invoke(value& v, valT&& val) - { - v.integer_ = static_cast(val); - } -}; -template<> struct value::switch_assign -{ - template - static void invoke(value& v, valT&& val) - { - v.float_ = static_cast(val); - } -}; -template<> struct value::switch_assign -{ - template - static void invoke(value& v, valT&& val) - { - new(&v.string_) String(val); - } -}; -template<> struct value::switch_assign -{ - template - static void invoke(value& v, valT&& val) - { - new(&v.datetime_) Datetime(val); - } -}; -template<> struct value::switch_assign -{ - template - static void invoke(value& v, valT&& val) - { - new(&v.storage_) storage_ptr( - toml::make_unique>(val)); - } -}; -template<> struct value::switch_assign -{ - template - static void invoke(value& v, valT&& val) - { - new(&v.storage_) storage_ptr( - toml::make_unique>(val)); - } -}; - -template<> struct value::switch_cast -{ - static Boolean& invoke(value& v) {return v.boolean_;} - static Boolean const& invoke(value const& v) {return v.boolean_;} -}; -template<> struct value::switch_cast -{ - static Integer& invoke(value& v) {return v.integer_;} - static Integer const& invoke(value const& v) {return v.integer_;} -}; -template<> struct value::switch_cast -{ - static Float& invoke(value& v) {return v.float_;} - static Float const& invoke(value const& v) {return v.float_;} -}; -template<> struct value::switch_cast -{ - static String& invoke(value& v) {return v.string_;} - static String const& invoke(value const& v) {return v.string_;} -}; -template<> struct value::switch_cast -{ - static Datetime& invoke(value& v) {return v.datetime_;} - static Datetime const& invoke(value const& v) {return v.datetime_;} -}; -template<> struct value::switch_cast -{ - // switch_cast assumes tmeplate argument is correct. - // if not, the behaviour is undefined. - static Array& invoke(value& v) - { - return static_cast*>(v.storage_.get())->value; - } - static Array const& invoke(value const& v) - { - return static_cast*>(v.storage_.get())->value; - } -}; -template<> struct value::switch_cast -{ - static Table& invoke(value& v) - { - return static_cast*>(v.storage_.get())->value; - } - static Table const& invoke(value const& v) - { - return static_cast*>(v.storage_.get())->value; - } -}; - -inline void value::switch_clean(value_t t) -{ - switch(t) - { - case value_t::Boolean : {boolean_.~Boolean(); return;} - case value_t::Integer : {integer_.~Integer(); return;} - case value_t::Float : {float_.~Float(); return;} - case value_t::String : {string_.~String(); return;} - case value_t::Datetime : {datetime_.~Datetime(); return;} - case value_t::Array : {storage_.~storage_ptr(); return;} - case value_t::Table : {storage_.~storage_ptr(); return;} - case value_t::Empty : return; - case value_t::Unknown : assert(false); - default : assert(false); - } -} - -inline value::~value() -{ - switch_clean(this->type_); -} - -inline value::value(const value& v) : type_(v.type()) -{ - switch(v.type()) - { - case value_t::Boolean : - { - switch_assign::invoke( - *this, v.cast()); - break; - } - case value_t::Integer : - { - switch_assign::invoke( - *this, v.cast()); - break; - } - case value_t::Float : - { - switch_assign::invoke( - *this, v.cast()); - break; - } - case value_t::String : - { - switch_assign::invoke( - *this, v.cast()); - break; - } - case value_t::Datetime: - { - switch_assign::invoke( - *this, v.cast()); - break; - } - case value_t::Array : - { - switch_assign::invoke( - *this, v.cast()); - break; - } - case value_t::Table : - { - switch_assign::invoke( - *this, v.cast()); - break; - } - case value_t::Empty : break; - case value_t::Unknown : assert(false); - default: assert(false); - } -} - -inline value::value(value&& v) -{ - this->type_ = v.type_; - switch(this->type_) - { - case value_t::Boolean : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Integer : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Float : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::String : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Datetime: - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Array : - { - new(&this->storage_) storage_ptr(std::move(v.storage_)); - break; - } - case value_t::Table : - { - new(&this->storage_) storage_ptr(std::move(v.storage_)); - break; - } - case value_t::Empty : break; - case value_t::Unknown : assert(false); - default: assert(false); - } -} - -inline value& value::operator=(const value& v) -{ - if(should_be_cleaned(this->type_)) - { - this->switch_clean(this->type_); - } - this->type_ = v.type(); - - switch(this->type_) - { - case value_t::Boolean : - { - switch_assign::invoke(*this, - v.cast()); - break; - } - case value_t::Integer : - { - switch_assign::invoke(*this, - v.cast()); - break; - } - case value_t::Float : - { - switch_assign::invoke(*this, - v.cast()); - break; - } - case value_t::String : - { - switch_assign::invoke(*this, - v.cast()); - break; - } - case value_t::Datetime: - { - switch_assign::invoke(*this, - v.cast()); - break; - } - case value_t::Array : - { - switch_assign::invoke(*this, - v.cast()); - break; - } - case value_t::Table : - { - switch_assign::invoke(*this, - v.cast()); - break; - } - case value_t::Empty : break; - case value_t::Unknown : assert(false); - default: assert(false); - } - return *this; -} - -inline value& value::operator=(value&& v) -{ - if(should_be_cleaned(this->type_)) - { - this->switch_clean(this->type_); - } - this->type_ = v.type_; - - switch(this->type_) - { - case value_t::Boolean : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Integer : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Float : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::String : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Datetime: - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Array : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Table : - { - switch_assign::invoke(*this, - std::move(v.cast())); - break; - } - case value_t::Empty : break; - case value_t::Unknown : assert(false); - default: assert(false); - } - return *this; -} - -template::is_toml_type, std::nullptr_t>::type> -value::value(T&& v) : type_(toml::detail::check_type()) -{ - switch_assign()>::invoke( - *this, std::forward(v)); -} - -template::is_toml_type, std::nullptr_t>::type> -value& value::operator=(T&& v) -{ - if(should_be_cleaned(this->type_)) - { - switch_clean(this->type_); - } - this->type_ = toml::detail::check_type(); - switch_assign()>::invoke( - *this, std::forward(v)); - return *this; -} - -template::is_toml_type, std::nullptr_t>::type> -value::value(std::initializer_list init) - : type_(toml::value_t::Array) -{ - toml::Array arr; arr.reserve(init.size()); - for(auto&& item : init) - arr.emplace_back(std::move(item)); - switch_assign::invoke(*this, std::move(arr)); -} - -inline value::value( - std::initializer_list> init) - : type_(toml::value_t::Table) -{ - toml::Table tmp; - for(auto&& item : init) - tmp.emplace(std::move(item.first), std::move(item.second)); - switch_assign::invoke(*this, std::move(tmp)); -} - -template -inline typename detail::toml_default_type::type const& -value::cast() const -{ - if(T != this->type_) - throw type_error("current type: " + stringize(this->type_) + - std::string(" is not query type: ") + stringize(T)); - return switch_cast::invoke(*this); -} -template -inline typename detail::toml_default_type::type& -value::cast() -{ - if(T != this->type_) - throw type_error("current type: " + stringize(this->type_) + - std::string(" is not query type: ") + stringize(T)); - return switch_cast::invoke(*this); -} - -inline bool operator==(const toml::value& lhs, const toml::value& rhs) -{ - if(lhs.type() != rhs.type()) return false; - switch(lhs.type()) - { - case value_t::Boolean : - return lhs.cast() == rhs.cast(); - case value_t::Integer : - return lhs.cast() == rhs.cast(); - case value_t::Float : - return lhs.cast() == rhs.cast(); - case value_t::String : - return lhs.cast() == rhs.cast(); - case value_t::Datetime: - return lhs.cast() == rhs.cast(); - case value_t::Array : - return lhs.cast() == rhs.cast(); - case value_t::Table : - return lhs.cast() == rhs.cast(); - case value_t::Empty : return true; - case value_t::Unknown : return false; - default: return false; - } -} -inline bool operator<(const toml::value& lhs, const toml::value& rhs) -{ - if(lhs.type() != rhs.type()) return (lhs.type() < rhs.type()); - switch(lhs.type()) - { - case value_t::Boolean : - return lhs.cast() < rhs.cast(); - case value_t::Integer : - return lhs.cast() < rhs.cast(); - case value_t::Float : - return lhs.cast() < rhs.cast(); - case value_t::String : - return lhs.cast() < rhs.cast(); - case value_t::Datetime: - return lhs.cast() < rhs.cast(); - case value_t::Array : - return lhs.cast() < rhs.cast(); - case value_t::Table : - return lhs.cast() < rhs.cast(); - case value_t::Empty : return false; - case value_t::Unknown : return false; - default: return false; - } -} - -inline bool operator!=(const toml::value& lhs, const toml::value& rhs) -{ - return !(lhs == rhs); -} -inline bool operator<=(const toml::value& lhs, const toml::value& rhs) -{ - return (lhs < rhs) || (lhs == rhs); -} -inline bool operator>(const toml::value& lhs, const toml::value& rhs) -{ - return !(lhs <= rhs); -} -inline bool operator>=(const toml::value& lhs, const toml::value& rhs) -{ - return !(lhs < rhs); -} - -}// toml -#endif// TOML11_VALUE diff --git a/vendor/toml11-LICENSE b/vendor/toml11-LICENSE new file mode 100644 index 0000000..f55c511 --- /dev/null +++ b/vendor/toml11-LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Toru Niina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. From 0730bb9d30190addb654d0844942a368722e6a17 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 19:01:25 +0200 Subject: [PATCH 05/28] Add a unit test suite There were no tests at all, so nothing in this repository could be changed with any confidence. The interfaces to hang them on already existed and were simply unused: IStatusengine, IMessageHandler and IMessageQueueHandler are pure interfaces, and Configuration only depends on the first one. 33 test cases covering the encoder, the configuration parser, the log level thresholds and the queue identifier tables. They need neither an installed naemon nor a running broker: the checked in headers under devtools/ci are enough to compile against, and naemon_stubs.cpp provides the handful of symbols that are actually referenced, capturing log output so it can be asserted on. The suite is opt in via -Dtests=true so that packagers and offline builds are unaffected; doctest comes from a wrap. CI runs it twice, once plain and once under address and undefined behaviour sanitizers, which is what would have caught the allocator mismatch and the connection leak fixed earlier in this branch. Two of the tests are there specifically to pin down decisions rather than behaviour: that rabbitmq queues stay durable by default, since RabbitMQ 4 refuses to declare a queue that is neither durable nor exclusive, and that both shipped configuration files still parse, which is the regression guard for the toml11 migration. The suite earned its keep immediately - it caught an unsequenced argument evaluation introduced while deduplicating the queue name tables. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- .github/workflows/build.yml | 45 ++++++ .gitignore | 3 + meson.build | 4 + meson_options.txt | 2 + subprojects/doctest.wrap | 10 ++ tests/main.cpp | 2 + tests/meson.build | 31 ++++ tests/naemon_stubs.cpp | 21 +++ tests/test_configuration.cpp | 277 +++++++++++++++++++++++++++++++++++ tests/test_encoding.cpp | 78 ++++++++++ tests/test_logstream.cpp | 101 +++++++++++++ tests/test_queue.cpp | 70 +++++++++ tests/test_support.h | 49 +++++++ 13 files changed, 693 insertions(+) create mode 100644 subprojects/doctest.wrap create mode 100644 tests/main.cpp create mode 100644 tests/meson.build create mode 100644 tests/naemon_stubs.cpp create mode 100644 tests/test_configuration.cpp create mode 100644 tests/test_encoding.cpp create mode 100644 tests/test_logstream.cpp create mode 100644 tests/test_queue.cpp create mode 100644 tests/test_support.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 54f1342..6f07fb8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,6 +91,51 @@ jobs: -Dnagios_include_dir=/opt/nagios/include build ninja -C build + tests: + name: unit tests (${{ matrix.sanitize && 'sanitized' || 'plain' }}) + runs-on: ubuntu-latest + container: + image: ubuntu:24.04 + strategy: + fail-fast: false + matrix: + sanitize: [false, true] + steps: + - name: Install dependencies + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + g++ pkg-config meson ninja-build git ca-certificates \ + libglib2.0-dev libgearman-dev libjson-c-dev libuchardet-dev \ + librabbitmq-dev libssl-dev uuid-dev + + - uses: actions/checkout@v4 + + - name: Provide naemon headers + run: | + set -e + mkdir -p /opt/naemon/include /usr/local/lib/pkgconfig + ln -s "$(readlink -f devtools/ci/naemon)" /opt/naemon/include/naemon + ln -s "$(readlink -f devtools/ci/naemon.pc)" /usr/local/lib/pkgconfig/naemon.pc + + - name: Run tests + env: + PKG_CONFIG_PATH: /usr/local/lib/pkgconfig + run: | + if [ "${{ matrix.sanitize }}" = "true" ]; then + meson setup -Dtests=true -Db_sanitize=address,undefined build + else + meson setup -Dtests=true build + fi + meson test -C build --print-errorlogs + + - name: Upload test logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: testlog-${{ matrix.sanitize }} + path: build/meson-logs/testlog.txt + format: name: clang-format (advisory) runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 12b00c6..6c6aa36 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ /vendor/iconv /vendor/yaml_cpp /build +# meson subprojects: keep the .wrap files, ignore what meson downloads +/subprojects/* +!/subprojects/*.wrap diff --git a/meson.build b/meson.build index b98cbdd..6e3bfc0 100644 --- a/meson.build +++ b/meson.build @@ -22,3 +22,7 @@ endif toml_dep = declare_dependency(include_directories: include_directories('vendor')).as_system('system') subdir('src') + +if get_option('tests') + subdir('tests') +endif diff --git a/meson_options.txt b/meson_options.txt index 3cf9fe9..0ffb933 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -3,3 +3,5 @@ option('gearman', type: 'boolean', value: true) option('rabbitmq', type: 'boolean', value: true) option('nagios', type: 'boolean', value: false) option('nagios_include_dir', type: 'string', value: '') +option('tests', type: 'boolean', value: false, + description: 'Build the unit tests. Needs the doctest subproject.') diff --git a/subprojects/doctest.wrap b/subprojects/doctest.wrap new file mode 100644 index 0000000..fc0b2fb --- /dev/null +++ b/subprojects/doctest.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = doctest-2.5.3 +source_url = https://github.com/doctest/doctest/archive/refs/tags/v2.5.3.tar.gz +source_filename = doctest-2.5.3.tar.gz +source_hash = 174ebc4e769928959614789c5b4e9c3d0a0f81a62bb608756b127bfebfb21331 +source_fallback_url = https://wrapdb.mesonbuild.com/v2/doctest_2.5.3-1/get_source/doctest-2.5.3.tar.gz +wrapdb_version = 2.5.3-1 + +[provide] +dependency_names = doctest diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..0a3f254 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,2 @@ +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..b5f9137 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,31 @@ +doctest_dep = dependency('doctest') + +test_deps = [doctest_dep, uchardet_dep, jsonc_dep, toml_dep] +test_cpp_args = statusengine_cpp_args + [ + '-DSTATUSENGINE_EXAMPLE_CONFIG="' + meson.project_source_root() / 'statusengine.toml' + '"', + '-DSTATUSENGINE_DEVENV_CONFIG="' + meson.project_source_root() / 'devtools/buildenv/statusengine.toml' + '"', +] + +if get_option('nagios') + test_deps += [nagios_dep] +else + test_deps += [glib_dep] + # The tests do not need an installed naemon, the checked in headers are enough: + # nothing under test calls into libnaemon, and the few symbols that are referenced + # are provided by naemon_stubs.cpp. + test_deps += [declare_dependency( + include_directories: include_directories('../devtools/ci')).as_system('system')] +endif + +test_incdirs = include_directories('.', '../src') + +statusengine_tests = executable('statusengine-tests', + ['main.cpp', 'naemon_stubs.cpp', 'test_encoding.cpp', 'test_configuration.cpp', 'test_logstream.cpp', 'test_queue.cpp', + '../src/Encoding.cpp'], + include_directories: test_incdirs, + dependencies: test_deps, + cpp_args: test_cpp_args, + build_by_default: true, + install: false) + +test('unit', statusengine_tests, protocol: 'exitcode', timeout: 120) diff --git a/tests/naemon_stubs.cpp b/tests/naemon_stubs.cpp new file mode 100644 index 0000000..1e253e1 --- /dev/null +++ b/tests/naemon_stubs.cpp @@ -0,0 +1,21 @@ +// Minimal stand-ins for the naemon symbols the tested code references. The broker is a +// shared module that naemon resolves at load time, so a test binary has to provide them +// itself. Only the symbols actually pulled in by the tested translation units are needed. + +#include "test_support.h" + +#include +#include +#include + +std::vector capturedLogs; + +// Signature has to match naemon/logging.h exactly. +extern "C" void nm_log(int, const char *fmt, ...) { + char buffer[4096]; + va_list args; + va_start(args, fmt); + std::vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + capturedLogs.emplace_back(buffer); +} diff --git a/tests/test_configuration.cpp b/tests/test_configuration.cpp new file mode 100644 index 0000000..3b14902 --- /dev/null +++ b/tests/test_configuration.cpp @@ -0,0 +1,277 @@ +#include + +#include +#include +#include +#include +#include + +#include "test_support.h" + +#include "Configuration.h" + +using statusengine::Configuration; +using statusengine::FakeStatusengine; +using statusengine::LogLevel; +using statusengine::Queue; +using statusengine::WorkerQueue; + +namespace { + /// Writes a config to a temporary file and removes it again when it goes out of scope. + class TempConfig { + public: + explicit TempConfig(const std::string &contents) { + char nameTemplate[] = "/tmp/statusengine-test-XXXXXX"; + int fd = mkstemp(nameTemplate); + REQUIRE(fd != -1); + close(fd); + path = nameTemplate; + std::ofstream out(path); + out << contents; + } + ~TempConfig() { + std::remove(path.c_str()); + } + TempConfig(const TempConfig &) = delete; + TempConfig &operator=(const TempConfig &) = delete; + + std::string path; + }; +} // namespace + +TEST_CASE("Configuration loads a minimal gearman config") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[[Gearman]] +URL = "127.0.0.1:4730" +HostStatus = "statusngin_hoststatus" +WorkerCommand = "statusngin_cmd" +)"); + + REQUIRE(cfg.Load(file.path)); + + auto gearmans = cfg.GetGearmanConfiguration(); + REQUIRE(gearmans->size() == 1); + CHECK(gearmans->at(0)->URL == "127.0.0.1:4730"); + + auto queues = gearmans->at(0)->GetQueueNames(); + REQUIRE(queues->size() == 1); + CHECK(queues->at(Queue::HostStatus) == "statusngin_hoststatus"); + + auto workerQueues = gearmans->at(0)->GetWorkerQueueNames(); + REQUIRE(workerQueues->size() == 1); + CHECK(workerQueues->at(WorkerQueue::Command) == "statusngin_cmd"); +} + +TEST_CASE("Configuration reports a missing file instead of throwing") { + FakeStatusengine se; + Configuration cfg(se); + + CHECK_FALSE(cfg.Load("/nonexistent/statusengine.toml")); + CHECK(FakeStatusengine::Logged("Could not read file")); +} + +TEST_CASE("Configuration rejects a syntax error") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file("this is not = = valid toml\n"); + + CHECK_FALSE(cfg.Load(file.path)); +} + +TEST_CASE("Configuration rejects an unknown bulk queue identifier") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[Bulk] +Queues = ["HostStatus", "ThisQueueDoesNotExist"] +)"); + + CHECK_FALSE(cfg.Load(file.path)); + CHECK(FakeStatusengine::Logged("unknown queue identifier")); +} + +TEST_CASE("Configuration rejects an unknown log level") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[Log] +Level = "Chatty" +)"); + + CHECK_FALSE(cfg.Load(file.path)); + CHECK(FakeStatusengine::Logged("Unknown log level")); +} + +TEST_CASE("Configuration reads the log level") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[Log] +Level = "Error" +)"); + + REQUIRE(cfg.Load(file.path)); + CHECK(cfg.GetLogLevel() == LogLevel::Error); +} + +TEST_CASE("Configuration defaults to warning level") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file("[Bulk]\nMaximum = 10\n"); + + REQUIRE(cfg.Load(file.path)); + CHECK(cfg.GetLogLevel() == LogLevel::Warning); +} + +TEST_CASE("Configuration applies bulk and worker defaults") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file("[Log]\nLevel = \"Info\"\n"); + + REQUIRE(cfg.Load(file.path)); + CHECK(cfg.GetBulkMaximum() == 200ul); + CHECK(cfg.GetBulkFlushInterval() == 10); + CHECK(cfg.GetMaxWorkerMessagesPerInterval() == 1000000ul); + CHECK(cfg.GetStartupScheduleMax() == 0); +} + +TEST_CASE("Configuration reads bulk settings and queue membership") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[Bulk] +Maximum = 50 +FlushInterval = 3 +Queues = ["HostStatus", "ServiceCheck"] + +[Scheduler] +StartupScheduleMax = 30 + +[Worker] +MaxWorkerMessagesPerInterval = 42 +)"); + + REQUIRE(cfg.Load(file.path)); + CHECK(cfg.GetBulkMaximum() == 50ul); + CHECK(cfg.GetBulkFlushInterval() == 3); + CHECK(cfg.GetStartupScheduleMax() == 30); + CHECK(cfg.GetMaxWorkerMessagesPerInterval() == 42ul); + CHECK(cfg.IsBulkQueue(Queue::HostStatus)); + CHECK(cfg.IsBulkQueue(Queue::ServiceCheck)); + CHECK_FALSE(cfg.IsBulkQueue(Queue::LogData)); +} + +TEST_CASE("Rabbitmq needs a hostname") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[[Rabbitmq]] +Username = "statusengine" +)"); + + CHECK_FALSE(cfg.Load(file.path)); + CHECK(FakeStatusengine::Logged("specify a hostname")); +} + +TEST_CASE("Rabbitmq queues and exchange are durable by default") { + // RabbitMQ 4 refuses to declare a queue that is neither durable nor exclusive, so this + // default is what keeps the broker able to connect at all. + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[[Rabbitmq]] +Hostname = "localhost" +Password = "statusengine" +HostStatus = "statusngin_hoststatus" +)"); + + REQUIRE(cfg.Load(file.path)); + auto rabbits = cfg.GetRabbitmqConfiguration(); + REQUIRE(rabbits->size() == 1); + CHECK(rabbits->at(0)->DurableQueues); + CHECK(rabbits->at(0)->DurableExchange); +} + +TEST_CASE("Rabbitmq durability can be turned off explicitly") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[[Rabbitmq]] +Hostname = "localhost" +Password = "statusengine" +DurableQueues = false +DurableExchange = false +)"); + + REQUIRE(cfg.Load(file.path)); + auto rabbits = cfg.GetRabbitmqConfiguration(); + REQUIRE(rabbits->size() == 1); + CHECK_FALSE(rabbits->at(0)->DurableQueues); + CHECK_FALSE(rabbits->at(0)->DurableExchange); +} + +TEST_CASE("Rabbitmq applies connection defaults") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[[Rabbitmq]] +Hostname = "rabbit" +Password = "statusengine" +)"); + + REQUIRE(cfg.Load(file.path)); + auto rfg = cfg.GetRabbitmqConfiguration()->at(0); + CHECK(rfg->Port == 5672); + CHECK(rfg->Vhost == "/"); + CHECK(rfg->Username == "statusengine"); + CHECK(rfg->Exchange == "statusengine"); + CHECK(rfg->Timeout.tv_sec == 30); + CHECK_FALSE(rfg->SSL); + CHECK(rfg->SSLVerify); +} + +TEST_CASE("Multiple connections of the same type are all loaded") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file(R"( +[[Gearman]] +URL = "a:4730" +HostStatus = "q1" + +[[Gearman]] +URL = "b:4730" +HostStatus = "q2" +)"); + + REQUIRE(cfg.Load(file.path)); + REQUIRE(cfg.GetGearmanConfiguration()->size() == 2); + CHECK(cfg.GetGearmanConfiguration()->at(0)->URL == "a:4730"); + CHECK(cfg.GetGearmanConfiguration()->at(1)->URL == "b:4730"); +} + +TEST_CASE("The shipped example configuration parses") { + // statusengine.toml is what users copy, so it has to stay loadable. This is also the + // regression guard for the toml11 v1 -> v4 migration. + FakeStatusengine se; + Configuration cfg(se); + + REQUIRE(cfg.Load(STATUSENGINE_EXAMPLE_CONFIG)); + + // The example enables gearman with a set of queues and marks several of them bulk. + REQUIRE(cfg.GetGearmanConfiguration()->size() == 1); + auto queues = cfg.GetGearmanConfiguration()->at(0)->GetQueueNames(); + CHECK(queues->at(Queue::HostStatus) == "statusngin_hoststatus"); + CHECK(cfg.IsBulkQueue(Queue::HostStatus)); + CHECK(cfg.GetStartupScheduleMax() == 30); +} + +TEST_CASE("The developer environment configuration parses") { + FakeStatusengine se; + Configuration cfg(se); + + REQUIRE(cfg.Load(STATUSENGINE_DEVENV_CONFIG)); + CHECK(cfg.GetGearmanConfiguration()->size() == 1); + CHECK(cfg.GetRabbitmqConfiguration()->size() == 1); +} diff --git a/tests/test_encoding.cpp b/tests/test_encoding.cpp new file mode 100644 index 0000000..cb3c53e --- /dev/null +++ b/tests/test_encoding.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include + +#include "Encoding.h" + +using statusengine::Encoder; + +namespace { + /// The same text as UTF-8 and as single byte latin1, built byte-wise so that no + /// greedy hex escape in a string literal can merge two bytes into one. + const std::string kUtf8 = + "Die Verbindung zum Server ist fehlgeschlagen. Bitte prüfen Sie die Konfiguration " + "und überprüfen Sie, ob der Dienst läuft. Größe überschritten."; + + std::string ToLatin1(const std::string &utf8) { + std::string out; + for (size_t i = 0; i < utf8.size();) { + unsigned char c = static_cast(utf8[i]); + if (c < 0x80) { + out += static_cast(c); + i += 1; + } + else { + unsigned cp = ((c & 0x1fu) << 6) | (static_cast(utf8[i + 1]) & 0x3fu); + out += static_cast(cp); + i += 2; + } + } + return out; + } +} // namespace + +TEST_CASE("Encoder converts latin1 to utf8") { + Encoder encoder; + const std::string latin1 = ToLatin1(kUtf8); + CHECK(latin1.size() < kUtf8.size()); // the fixture really is single byte + + CHECK(encoder.ToUtf8(latin1.c_str()) == kUtf8); +} + +TEST_CASE("Encoder passes utf8 through unchanged") { + Encoder encoder; + CHECK(encoder.ToUtf8(kUtf8.c_str()) == kUtf8); +} + +TEST_CASE("Encoder does not modify the caller's buffer") { + Encoder encoder; + const std::string latin1 = ToLatin1(kUtf8); + std::vector buffer(latin1.begin(), latin1.end()); + buffer.push_back('\0'); + + encoder.ToUtf8(buffer.data()); + + CHECK(std::string(buffer.data()) == latin1); +} + +TEST_CASE("Encoder handles trivial input") { + Encoder encoder; + CHECK(encoder.ToUtf8(nullptr).empty()); + CHECK(encoder.ToUtf8("").empty()); + CHECK(encoder.ToUtf8("OK - everything fine") == "OK - everything fine"); +} + +TEST_CASE("Encoder can be reused across calls") { + Encoder encoder; + const std::string latin1 = ToLatin1(kUtf8); + for (int i = 0; i < 5; ++i) { + CHECK(encoder.ToUtf8(latin1.c_str()) == kUtf8); + CHECK(encoder.ToUtf8("plain ascii") == "plain ascii"); + } +} + +TEST_CASE("Encoder keeps embedded newlines and pipes") { + Encoder encoder; + CHECK(encoder.ToUtf8("first line\nsecond line|perf=1") == "first line\nsecond line|perf=1"); +} diff --git a/tests/test_logstream.cpp b/tests/test_logstream.cpp new file mode 100644 index 0000000..83bc5b0 --- /dev/null +++ b/tests/test_logstream.cpp @@ -0,0 +1,101 @@ +#include + +#include "test_support.h" + +#include "LogStream.h" + +using statusengine::LogLevel; +using statusengine::LogStream; + +namespace { + /// Streams one message of each severity and reports which ones were written out. + struct Emitted { + bool info; + bool warning; + bool error; + }; + + Emitted EmitAll(LogLevel configured) { + LogStream ls; + ls.SetLogLevel(configured); + + capturedLogs.clear(); + ls << "an info message" << LogLevel::Info; + bool info = !capturedLogs.empty(); + + capturedLogs.clear(); + ls << "a warning message" << LogLevel::Warning; + bool warning = !capturedLogs.empty(); + + capturedLogs.clear(); + ls << "an error message" << LogLevel::Error; + bool error = !capturedLogs.empty(); + + return Emitted{info, warning, error}; + } +} // namespace + +TEST_CASE("Info level emits everything") { + auto e = EmitAll(LogLevel::Info); + CHECK(e.info); + CHECK(e.warning); // used to be dropped: the most verbose level hid all warnings + CHECK(e.error); +} + +TEST_CASE("Warning level drops info only") { + auto e = EmitAll(LogLevel::Warning); + CHECK_FALSE(e.info); + CHECK(e.warning); + CHECK(e.error); +} + +TEST_CASE("Error level drops info and warning") { + auto e = EmitAll(LogLevel::Error); + CHECK_FALSE(e.info); + CHECK_FALSE(e.warning); // used to be emitted even at the strictest level + CHECK(e.error); +} + +TEST_CASE("LogStream starts at info level") { + // Startup diagnostics are logged before the configured level is known, so the initial + // level has to be the most verbose one or they would be lost. + LogStream ls; + CHECK(ls.GetLogLevel() == LogLevel::Info); +} + +TEST_CASE("LogStream prefixes messages and formats mixed types") { + LogStream ls; + ls.SetLogLevel(LogLevel::Info); + capturedLogs.clear(); + + ls << "host " << std::string("localhost") << " had " << 3 << " attempts, latency " << 1.5 + << ", flapping " << true << LogLevel::Info; + + REQUIRE(capturedLogs.size() == 1); + CHECK(capturedLogs[0] == "Statusengine: host localhost had 3 attempts, latency 1.5, flapping 1"); +} + +TEST_CASE("The buffer is cleared between messages") { + LogStream ls; + ls.SetLogLevel(LogLevel::Info); + + capturedLogs.clear(); + ls << "first" << LogLevel::Info; + ls << "second" << LogLevel::Info; + + REQUIRE(capturedLogs.size() == 2); + CHECK(capturedLogs[0] == "Statusengine: first"); + CHECK(capturedLogs[1] == "Statusengine: second"); +} + +TEST_CASE("A dropped message does not leak into the next one") { + LogStream ls; + ls.SetLogLevel(LogLevel::Error); + + capturedLogs.clear(); + ls << "suppressed info" << LogLevel::Info; + ls << "real error" << LogLevel::Error; + + REQUIRE(capturedLogs.size() == 1); + CHECK(capturedLogs[0] == "Statusengine: real error"); +} diff --git a/tests/test_queue.cpp b/tests/test_queue.cpp new file mode 100644 index 0000000..9ebb18d --- /dev/null +++ b/tests/test_queue.cpp @@ -0,0 +1,70 @@ +#include + +#include +#include + +#include "Queue.h" + +using statusengine::Queue; +using statusengine::QueueNameHandler; +using statusengine::WorkerQueue; + +TEST_CASE("Queue names and ids are complete and consistent") { + const auto &byName = QueueNameHandler::Instance().QueueNames(); + const auto &byId = QueueNameHandler::Instance().QueueIds(); + + // Both directions have to describe the same set, otherwise a queue can be configured + // but never resolved back to its name (or the other way around). + REQUIRE(byName.size() == byId.size()); + CHECK(byId.size() == 23); + + for (const auto &entry : byName) { + REQUIRE(byId.count(entry.second) == 1); + CHECK(byId.at(entry.second) == entry.first); + } + for (const auto &entry : byId) { + REQUIRE(byName.count(entry.second) == 1); + CHECK(byName.at(entry.second) == entry.first); + } +} + +TEST_CASE("Worker queue names and ids are complete and consistent") { + const auto &byName = QueueNameHandler::Instance().WorkerQueueNames(); + const auto &byId = QueueNameHandler::Instance().WorkerQueueIds(); + + REQUIRE(byName.size() == byId.size()); + CHECK(byId.size() == 3); + + for (const auto &entry : byName) { + REQUIRE(byId.count(entry.second) == 1); + CHECK(byId.at(entry.second) == entry.first); + } +} + +TEST_CASE("Known identifiers keep their spelling") { + // These strings are the public configuration format, they must not drift. + const auto &byId = QueueNameHandler::Instance().QueueIds(); + CHECK(byId.at(Queue::HostStatus) == "HostStatus"); + CHECK(byId.at(Queue::ServicePerfData) == "ServicePerfData"); + CHECK(byId.at(Queue::ContactNotificationMethodData) == "ContactNotificationMethodData"); + CHECK(byId.at(Queue::OCSP) == "OCSP"); + CHECK(byId.at(Queue::OCHP) == "OCHP"); + + const auto &workerById = QueueNameHandler::Instance().WorkerQueueIds(); + CHECK(workerById.at(WorkerQueue::Command) == "WorkerCommand"); + CHECK(workerById.at(WorkerQueue::OCSP) == "WorkerOCSP"); + CHECK(workerById.at(WorkerQueue::OCHP) == "WorkerOCHP"); +} + +TEST_CASE("Queue and worker queue namespaces are separate") { + // OCSP exists in both enums with different identifiers; mixing them up would silently + // route messages to the wrong queue. + const auto &queueNames = QueueNameHandler::Instance().QueueNames(); + const auto &workerNames = QueueNameHandler::Instance().WorkerQueueNames(); + + for (const auto &entry : workerNames) { + CHECK(queueNames.count(entry.first) == 0); + } + CHECK(queueNames.count("OCSP") == 1); + CHECK(workerNames.count("WorkerOCSP") == 1); +} diff --git a/tests/test_support.h b/tests/test_support.h new file mode 100644 index 0000000..1dd99da --- /dev/null +++ b/tests/test_support.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include "IStatusengine.h" + +/// Everything nm_log() was called with, in order. Cleared by FakeStatusengine's constructor. +extern std::vector capturedLogs; + +namespace statusengine { + + /** + * IStatusengine that only provides logging. Enough for everything that just needs to + * report configuration problems. + */ + class FakeStatusengine : public IStatusengine { + public: + FakeStatusengine() : ls() { + capturedLogs.clear(); + ls.SetLogLevel(LogLevel::Info); + } + + LogStream &Log() override { + return ls; + } + void FlushBulkQueue() override {} + IMessageHandlerList *GetMessageHandler() const override { + return nullptr; + } + void InitEventCallbacks() override {} + time_t GetStartupScheduleMax() const override { + return 0; + } + + /// True if any log line contains the given text. + static bool Logged(const std::string &needle) { + for (const auto &line : capturedLogs) { + if (line.find(needle) != std::string::npos) { + return true; + } + } + return false; + } + + private: + LogStream ls; + }; +} // namespace statusengine From b778ce78fe8433e356d5211a279a97f76606f99c Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 19:15:56 +0200 Subject: [PATCH 06/28] Pin the message format against recorded broker output The JSON the broker emits is its contract with the worker, and nothing guarded it: any renamed or dropped key would have gone unnoticed until the worker choked on it in production. Adds the recorded messages from the worker repository (.claude/specs) as fixtures and builds each message type from hand made nebstruct values, asserting that the produced JSON carries exactly the same keys as the recording. Values are not compared, the fixtures come from a different host - it is the shape that is the contract. All 13 recorded shapes match what the code produces today, so this commit pins the current behaviour rather than changing it. That includes the long_output duplication on state changes, which the recordings confirm has been shipping for a long time and which is documented in the README. Two of the cases cover the notification filters, which are easy to break by accident because they work by producing an empty object, and one covers a latin1 plugin output end to end: json-c cannot build a string from invalid utf8, so a successful parse of the rendered message is what proves the encoder ran. Linking Nebmodule.cpp would have pulled the whole naemon scheduling and downtime surface into the test binary, so the stubs define Nebmodule::EncodeString directly - the encoder is the only part of it that NagiosObject uses. Verified against mutation: dropping a field and renaming one each make the corresponding case fail. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- tests/fixtures/README.md | 11 + .../fixtures/statusngin_acknowledgements.json | 18 ++ .../statusngin_contactnotificationmethod.json | 21 ++ tests/fixtures/statusngin_core_restart.json | 4 + tests/fixtures/statusngin_downtimes.json | 21 ++ tests/fixtures/statusngin_hostchecks.json | 32 ++ tests/fixtures/statusngin_hoststatus.json | 109 +++++++ tests/fixtures/statusngin_logentries.json | 41 +++ tests/fixtures/statusngin_notifications.json | 49 +++ .../fixtures/statusngin_service_perfdata.json | 44 +++ tests/fixtures/statusngin_servicechecks.json | 61 ++++ tests/fixtures/statusngin_servicestatus.json | 113 +++++++ tests/fixtures/statusngin_statechanges.json | 45 +++ tests/meson.build | 3 +- tests/naemon_stubs.cpp | 44 +++ tests/test_messageformat.cpp | 306 ++++++++++++++++++ 16 files changed, 921 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/README.md create mode 100644 tests/fixtures/statusngin_acknowledgements.json create mode 100644 tests/fixtures/statusngin_contactnotificationmethod.json create mode 100644 tests/fixtures/statusngin_core_restart.json create mode 100644 tests/fixtures/statusngin_downtimes.json create mode 100644 tests/fixtures/statusngin_hostchecks.json create mode 100644 tests/fixtures/statusngin_hoststatus.json create mode 100644 tests/fixtures/statusngin_logentries.json create mode 100644 tests/fixtures/statusngin_notifications.json create mode 100644 tests/fixtures/statusngin_service_perfdata.json create mode 100644 tests/fixtures/statusngin_servicechecks.json create mode 100644 tests/fixtures/statusngin_servicestatus.json create mode 100644 tests/fixtures/statusngin_statechanges.json create mode 100644 tests/test_messageformat.cpp diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 0000000..f451ed0 --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,11 @@ +# Message format fixtures + +Captured output of the broker, taken from the statusengine-worker repository +(`.claude/specs/`). They are the contract between the broker and the worker: the +worker reads these fields, so a renamed or dropped key is a breaking change. + +`test_messageformat.cpp` builds each message from hand made `nebstruct_*` values +and asserts that the produced JSON carries exactly the same keys as the fixture. +Only the shape is compared, the recorded values are not. + +Update a fixture only together with a deliberate, documented format change. diff --git a/tests/fixtures/statusngin_acknowledgements.json b/tests/fixtures/statusngin_acknowledgements.json new file mode 100644 index 0000000..d46d38c --- /dev/null +++ b/tests/fixtures/statusngin_acknowledgements.json @@ -0,0 +1,18 @@ +{ + "type": 1700, + "flags": 0, + "attr": 0, + "timestamp": 1785516972, + "timestamp_usec": 732213, + "acknowledgement": { + "host_name": "localhost", + "service_description": "Swap Usage", + "author_name": "Daniel Z", + "comment_data": "I will take a look at this", + "acknowledgement_type": 1, + "state": 2, + "is_sticky": 0, + "persistent_comment": 1, + "notify_contacts": 1 + } +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_contactnotificationmethod.json b/tests/fixtures/statusngin_contactnotificationmethod.json new file mode 100644 index 0000000..37b81e9 --- /dev/null +++ b/tests/fixtures/statusngin_contactnotificationmethod.json @@ -0,0 +1,21 @@ +{ + "type": 605, + "flags": 0, + "attr": 0, + "timestamp": 1785517089, + "timestamp_usec": 927284, + "contactnotificationmethod": { + "host_name": "localhost", + "service_description": "Swap Usage", + "output": "CRITICAL: Agent error: Error response from daemon: client version 1.51 is too new. Maximum supported API version is 1.46", + "ack_author": "Daniel Z", + "ack_data": "Test notification", + "contact_name": "286a2700-006a-4d7d-a6fe-6df5b3533ad2", + "command_name": "52c9c3e2-4a00-40a4-8e75-db4649eba846", + "command_args": null, + "reason_type": 8, + "state": 2, + "start_time": 1785517089, + "end_time": 1785517089 + } +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_core_restart.json b/tests/fixtures/statusngin_core_restart.json new file mode 100644 index 0000000..0f8f407 --- /dev/null +++ b/tests/fixtures/statusngin_core_restart.json @@ -0,0 +1,4 @@ +{ + "object_type": 102, + "timestamp": 0 +} diff --git a/tests/fixtures/statusngin_downtimes.json b/tests/fixtures/statusngin_downtimes.json new file mode 100644 index 0000000..466808c --- /dev/null +++ b/tests/fixtures/statusngin_downtimes.json @@ -0,0 +1,21 @@ +{ + "type": 1100, + "flags": 0, + "attr": 0, + "timestamp": 1785517315, + "timestamp_usec": 224439, + "downtime": { + "host_name": "efb91fa3-2bd4-47a4-a89b-1ffe9ca719b5", + "service_description": "66d42eec-650f-4a98-aeaf-8ee51beaac62", + "author_name": "Daniel Z", + "comment_data": "In maintenance", + "downtime_type": 1, + "entry_time": 1785517314, + "start_time": 1785517380, + "end_time": 1785517500, + "triggered_by": 0, + "downtime_id": 1, + "fixed": 1, + "duration": 120 + } +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_hostchecks.json b/tests/fixtures/statusngin_hostchecks.json new file mode 100644 index 0000000..d9d786c --- /dev/null +++ b/tests/fixtures/statusngin_hostchecks.json @@ -0,0 +1,32 @@ +{ + "messages": [ + { + "type": 801, + "flags": 0, + "attr": 0, + "timestamp": 1785470668, + "timestamp_usec": 701779, + "hostcheck": { + "host_name": "localhost", + "command_line": "$USER1$\/check_ping -H $HOSTADDRESS$ -w 3000.0,80% -c 5000.0,100% -p 5", + "command_name": "check-host-alive", + "output": "PING OK - Packet loss = 0%, RTA = 0.08 ms", + "long_output": "", + "perf_data": "rta=0.078000ms;3000.000000;5000.000000;0.000000 pl=0%;80;100;0", + "check_type": 0, + "current_attempt": 1, + "max_attempts": 10, + "state_type": 1, + "state": 0, + "timeout": 30, + "start_time": 1785470664, + "end_time": 1785470668, + "early_timeout": 0, + "execution_time": 4.0977930000000002, + "latency": 0.60372000932693481, + "return_code": 0 + } + } + ], + "format": "none" +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_hoststatus.json b/tests/fixtures/statusngin_hoststatus.json new file mode 100644 index 0000000..22f9a8b --- /dev/null +++ b/tests/fixtures/statusngin_hoststatus.json @@ -0,0 +1,109 @@ +{ + "messages": [ + { + "type": 1201, + "flags": 0, + "attr": 0, + "timestamp": 1785470659, + "timestamp_usec": 594705, + "hoststatus": { + "name": "demo.statusengine.org", + "plugin_output": "PING OK - Packet loss = 0%, RTA = 0.08 ms", + "long_plugin_output": "", + "event_handler": null, + "perf_data": "rta=0.077000ms;3000.000000;5000.000000;0.000000 pl=0%;80;100;0", + "check_command": "check-host-alive", + "check_period": "24x7", + "current_state": 0, + "has_been_checked": 1, + "should_be_scheduled": 1, + "current_attempt": 1, + "max_attempts": 10, + "last_check": 1785470653, + "next_check": 1785470953, + "check_type": 0, + "last_state_change": 1762725251, + "last_hard_state_change": 1711613888, + "last_hard_state": 0, + "last_time_up": 1785470657, + "last_time_down": 1762725251, + "last_time_unreachable": 0, + "state_type": 1, + "last_notification": 0, + "next_notification": 0, + "no_more_notifications": 0, + "notifications_enabled": 1, + "problem_has_been_acknowledged": 0, + "acknowledgement_type": 0, + "current_notification_number": 0, + "accept_passive_checks": 1, + "event_handler_enabled": 1, + "checks_enabled": 1, + "flap_detection_enabled": 1, + "is_flapping": 0, + "percent_state_change": 0.0, + "latency": 0.25900000000000001, + "execution_time": 4.1130000000000004, + "scheduled_downtime_depth": 0, + "process_performance_data": 1, + "obsess": 1, + "modified_attributes": 31, + "check_interval": 5.0, + "retry_interval": 1.0 + } + }, + { + "type": 1201, + "flags": 0, + "attr": 0, + "timestamp": 1785470668, + "timestamp_usec": 701758, + "hoststatus": { + "name": "localhost", + "plugin_output": "PING OK - Packet loss = 0%, RTA = 0.08 ms", + "long_plugin_output": "", + "event_handler": null, + "perf_data": "rta=0.078000ms;3000.000000;5000.000000;0.000000 pl=0%;80;100;0", + "check_command": "check-host-alive", + "check_period": "24x7", + "current_state": 0, + "has_been_checked": 1, + "should_be_scheduled": 1, + "current_attempt": 1, + "max_attempts": 10, + "last_check": 1785470664, + "next_check": 1785470726, + "check_type": 0, + "last_state_change": 1667502634, + "last_hard_state_change": 1667502634, + "last_hard_state": 0, + "last_time_up": 1785470668, + "last_time_down": 1667502634, + "last_time_unreachable": 0, + "state_type": 1, + "last_notification": 0, + "next_notification": 0, + "no_more_notifications": 0, + "notifications_enabled": 1, + "problem_has_been_acknowledged": 0, + "acknowledgement_type": 0, + "current_notification_number": 0, + "accept_passive_checks": 1, + "event_handler_enabled": 0, + "checks_enabled": 1, + "flap_detection_enabled": 0, + "is_flapping": 0, + "percent_state_change": 0.0, + "latency": 0.60372000932693481, + "execution_time": 4.0977930000000002, + "scheduled_downtime_depth": 0, + "process_performance_data": 1, + "obsess": 1, + "modified_attributes": 31, + "check_interval": 5.0, + "retry_interval": 1.0 + } + } + ], + "format": "none" +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_logentries.json b/tests/fixtures/statusngin_logentries.json new file mode 100644 index 0000000..5591d99 --- /dev/null +++ b/tests/fixtures/statusngin_logentries.json @@ -0,0 +1,41 @@ +{ + "messages": [ + { + "type": 300, + "flags": 0, + "attr": 0, + "timestamp": 1785470659, + "timestamp_usec": 589318, + "logentry": { + "entry_time": 1785470659, + "data_type": 262144, + "data": "Event broker module '\/opt\/statusengine\/lib\/libstatusengine.so' initialized successfully." + } + }, + { + "type": 300, + "flags": 0, + "attr": 0, + "timestamp": 1785470659, + "timestamp_usec": 600279, + "logentry": { + "entry_time": 1785470659, + "data_type": 262144, + "data": "Successfully launched command file worker with pid 1490327" + } + }, + { + "type": 300, + "flags": 0, + "attr": 0, + "timestamp": 1785470666, + "timestamp_usec": 600929, + "logentry": { + "entry_time": 1785470666, + "data_type": 1048576, + "data": "SERVICE NOTIFICATION SUPPRESSED: localhost;Swap Usage;Notifications are temporarily disabled for this object by an external command." + } + } + ], + "format": "none" +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_notifications.json b/tests/fixtures/statusngin_notifications.json new file mode 100644 index 0000000..abcca8c --- /dev/null +++ b/tests/fixtures/statusngin_notifications.json @@ -0,0 +1,49 @@ +{ + "messages": [ + { + "type": 601, + "flags": 0, + "attr": 0, + "timestamp": 1785517089, + "timestamp_usec": 927750, + "notification_data": { + "host_name": "localhost", + "service_description": "Swap Usage", + "output": "CRITICAL: Agent error: Error response from daemon: client version 1.51 is too new. Maximum supported API version is 1.46", + "long_output": "CRITICAL: Agent error: Error response from daemon: client version 1.51 is too new. Maximum supported API version is 1.46", + "ack_author": "Daniel Z", + "ack_data": "Test notification", + "notification_type": 1, + "start_time": 1785517089, + "end_time": 1785517089, + "reason_type": 8, + "state": 2, + "escalated": 0, + "contacts_notified": 1 + } + }, + { + "type": 601, + "flags": 0, + "attr": 0, + "timestamp": 1785776162, + "timestamp_usec": 789235, + "notification_data": { + "host_name": "b19e585b-b439-4150-8d0c-5f3b1ab9faf9", + "service_description": null, + "output": "OK: Agent version 3.6.1 running on ubuntu (24.04). Last update: 2026-08-03 16:55:20", + "long_output": "OK: Agent version 3.6.1 running on ubuntu (24.04). Last update: 2026-08-03 16:55:20", + "ack_author": "Daniel Z", + "ack_data": "Test notification", + "notification_type": 0, + "start_time": 1785776162, + "end_time": 1785776162, + "reason_type": 8, + "state": 0, + "escalated": 0, + "contacts_notified": 1 + } + } + ], + "format": "none" +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_service_perfdata.json b/tests/fixtures/statusngin_service_perfdata.json new file mode 100644 index 0000000..b5e9cff --- /dev/null +++ b/tests/fixtures/statusngin_service_perfdata.json @@ -0,0 +1,44 @@ +{ + "messages": [ + { + "type": 701, + "flags": 0, + "attr": 0, + "timestamp": 1785470660, + "timestamp_usec": 603182, + "servicecheck": { + "host_name": "localhost", + "service_description": "Current Users", + "perf_data": "users=0;20;50;0", + "start_time": 1785470660 + } + }, + { + "type": 701, + "flags": 0, + "attr": 0, + "timestamp": 1785470666, + "timestamp_usec": 600936, + "servicecheck": { + "host_name": "localhost", + "service_description": "Swap Usage", + "perf_data": "swap=0MB;0;0;0;0", + "start_time": 1785470666 + } + }, + { + "type": 701, + "flags": 0, + "attr": 0, + "timestamp": 1785470668, + "timestamp_usec": 700601, + "servicecheck": { + "host_name": "demo.statusengine.org", + "service_description": "PING", + "perf_data": "rta=0.084000ms;100.000000;500.000000;0.000000 pl=0%;20;60;0", + "start_time": 1785470664 + } + } + ], + "format": "none" +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_servicechecks.json b/tests/fixtures/statusngin_servicechecks.json new file mode 100644 index 0000000..dd014c1 --- /dev/null +++ b/tests/fixtures/statusngin_servicechecks.json @@ -0,0 +1,61 @@ +{ + "messages": [ + { + "type": 701, + "flags": 0, + "attr": 0, + "timestamp": 1785470660, + "timestamp_usec": 603182, + "servicecheck": { + "host_name": "localhost", + "service_description": "Current Users", + "command_line": "$USER1$\/check_users -w $ARG1$ -c $ARG2$", + "command_name": "check_local_users!20!50", + "output": "USERS OK - 0 users currently logged in", + "long_output": "", + "perf_data": "users=0;20;50;0", + "check_type": 0, + "current_attempt": 1, + "max_attempts": 4, + "state_type": 1, + "state": 0, + "timeout": 60, + "start_time": 1785470660, + "end_time": 1785470660, + "early_timeout": 0, + "execution_time": 0.002062, + "latency": 0.60077601671218872, + "return_code": 0 + } + }, + { + "type": 701, + "flags": 0, + "attr": 0, + "timestamp": 1785470668, + "timestamp_usec": 700601, + "servicecheck": { + "host_name": "demo.statusengine.org", + "service_description": "PING", + "command_line": "$USER1$\/check_ping -H $HOSTADDRESS$ -w $ARG1$ -c $ARG2$ -p 5", + "command_name": "check_ping!100.0,20%!500.0,60%", + "output": "PING OK - Packet loss = 0%, RTA = 0.08 ms", + "long_output": "", + "perf_data": "rta=0.084000ms;100.000000;500.000000;0.000000 pl=0%;20;60;0", + "check_type": 0, + "current_attempt": 1, + "max_attempts": 4, + "state_type": 1, + "state": 0, + "timeout": 60, + "start_time": 1785470664, + "end_time": 1785470668, + "early_timeout": 0, + "execution_time": 4.0999400000000001, + "latency": 0.60019499063491821, + "return_code": 0 + } + } + ], + "format": "none" +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_servicestatus.json b/tests/fixtures/statusngin_servicestatus.json new file mode 100644 index 0000000..063b6ab --- /dev/null +++ b/tests/fixtures/statusngin_servicestatus.json @@ -0,0 +1,113 @@ +{ + "messages": [ + { + "type": 1202, + "flags": 0, + "attr": 0, + "timestamp": 1785470669, + "timestamp_usec": 643108, + "servicestatus": { + "host_name": "foobar", + "description": "Swap Usage", + "plugin_output": "SWAP CRITICAL - 0% free (0 MB out of 0 MB)", + "long_plugin_output": "", + "event_handler": null, + "perf_data": "swap=0MB;0;0;0;0", + "check_command": "check_local_swap!20!10", + "check_period": "24x7", + "current_state": 2, + "has_been_checked": 1, + "should_be_scheduled": 1, + "current_attempt": 4, + "max_attempts": 4, + "last_check": 1785470619, + "next_check": 1785470729, + "check_type": 0, + "last_state_change": 1670265137, + "last_hard_state_change": 1670265715, + "last_hard_state": 2, + "last_time_ok": 1670265137, + "last_time_warning": 0, + "last_time_critical": 1785470619, + "last_time_unknown": 0, + "state_type": 1, + "last_notification": 1676018776, + "next_notification": 1676022376, + "no_more_notifications": 0, + "notifications_enabled": 1, + "problem_has_been_acknowledged": 1, + "acknowledgement_type": 2, + "current_notification_number": 1599, + "accept_passive_checks": 1, + "event_handler_enabled": 1, + "checks_enabled": 1, + "flap_detection_enabled": 1, + "is_flapping": 0, + "percent_state_change": 0.0, + "latency": 0.94099999999999995, + "execution_time": 0.002, + "scheduled_downtime_depth": 0, + "process_performance_data": 1, + "obsess": 1, + "modified_attributes": 19, + "check_interval": 1.0, + "retry_interval": 1.0 + } + }, + { + "type": 1202, + "flags": 0, + "attr": 0, + "timestamp": 1785470673, + "timestamp_usec": 597616, + "servicestatus": { + "host_name": "foobar", + "description": "Root Partition", + "plugin_output": "DISK CRITICAL - free space: \/ 18296 MB (2% inode=78%):", + "long_plugin_output": "", + "event_handler": null, + "perf_data": "\/=599355MB;515145;579538;0;643932", + "check_command": "check_local_disk!20%!10%!\/", + "check_period": "24x7", + "current_state": 2, + "has_been_checked": 1, + "should_be_scheduled": 1, + "current_attempt": 4, + "max_attempts": 4, + "last_check": 1785470624, + "next_check": 1785470733, + "check_type": 0, + "last_state_change": 1778326043, + "last_hard_state_change": 1778326043, + "last_hard_state": 2, + "last_time_ok": 1769439648, + "last_time_warning": 1778326043, + "last_time_critical": 1785470624, + "last_time_unknown": 0, + "state_type": 1, + "last_notification": 1785470264, + "next_notification": 1785473864, + "no_more_notifications": 0, + "notifications_enabled": 1, + "problem_has_been_acknowledged": 0, + "acknowledgement_type": 0, + "current_notification_number": 3846, + "accept_passive_checks": 1, + "event_handler_enabled": 1, + "checks_enabled": 1, + "flap_detection_enabled": 1, + "is_flapping": 0, + "percent_state_change": 0.0, + "latency": 0.91400000000000003, + "execution_time": 0.0030000000000000001, + "scheduled_downtime_depth": 0, + "process_performance_data": 1, + "obsess": 1, + "modified_attributes": 0, + "check_interval": 1.0, + "retry_interval": 1.0 + } + } + ], + "format": "none" +} \ No newline at end of file diff --git a/tests/fixtures/statusngin_statechanges.json b/tests/fixtures/statusngin_statechanges.json new file mode 100644 index 0000000..7c2a36b --- /dev/null +++ b/tests/fixtures/statusngin_statechanges.json @@ -0,0 +1,45 @@ +{ + "messages": [ + { + "type": 1801, + "flags": 0, + "attr": 0, + "timestamp": 1785470683, + "timestamp_usec": 632628, + "statechange": { + "host_name": "localhost", + "service_description": "Flapping", + "output": "Your random value is: 158", + "long_output": "Your random value is: 158", + "statechange_type": 1, + "state": 0, + "state_type": 1, + "current_attempt": 4, + "max_attempts": 4, + "last_state": 1, + "last_hard_state": 1 + } + }, + { + "type": 1801, + "flags": 0, + "attr": 0, + "timestamp": 1785470686, + "timestamp_usec": 630353, + "statechange": { + "host_name": "demo.statusengine.org", + "service_description": "Flapping", + "output": "Your random value is: 87", + "long_output": "Your random value is: 87", + "statechange_type": 1, + "state": 0, + "state_type": 0, + "current_attempt": 4, + "max_attempts": 4, + "last_state": 1, + "last_hard_state": 0 + } + } + ], + "format": "none" +} \ No newline at end of file diff --git a/tests/meson.build b/tests/meson.build index b5f9137..28ea18d 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -4,6 +4,7 @@ test_deps = [doctest_dep, uchardet_dep, jsonc_dep, toml_dep] test_cpp_args = statusengine_cpp_args + [ '-DSTATUSENGINE_EXAMPLE_CONFIG="' + meson.project_source_root() / 'statusengine.toml' + '"', '-DSTATUSENGINE_DEVENV_CONFIG="' + meson.project_source_root() / 'devtools/buildenv/statusengine.toml' + '"', + '-DSTATUSENGINE_FIXTURE_DIR="' + meson.current_source_dir() / 'fixtures' + '"', ] if get_option('nagios') @@ -20,7 +21,7 @@ endif test_incdirs = include_directories('.', '../src') statusengine_tests = executable('statusengine-tests', - ['main.cpp', 'naemon_stubs.cpp', 'test_encoding.cpp', 'test_configuration.cpp', 'test_logstream.cpp', 'test_queue.cpp', + ['main.cpp', 'naemon_stubs.cpp', 'test_encoding.cpp', 'test_configuration.cpp', 'test_logstream.cpp', 'test_queue.cpp', 'test_messageformat.cpp', '../src/Encoding.cpp'], include_directories: test_incdirs, dependencies: test_deps, diff --git a/tests/naemon_stubs.cpp b/tests/naemon_stubs.cpp index 1e253e1..6c58681 100644 --- a/tests/naemon_stubs.cpp +++ b/tests/naemon_stubs.cpp @@ -6,8 +6,11 @@ #include #include +#include #include +#include "Nebmodule.h" + std::vector capturedLogs; // Signature has to match naemon/logging.h exactly. @@ -19,3 +22,44 @@ extern "C" void nm_log(int, const char *fmt, ...) { va_end(args); capturedLogs.emplace_back(buffer); } + +// --- symbols referenced by NagiosObject.h ----------------------------------- + +namespace { + nagios_macros testMacros; +} + +extern "C" nagios_macros *get_global_macros(void) { + return &testMacros; +} + +extern "C" int get_raw_command_line_r(nagios_macros *, command *, char *cmd, char **full_command, int) { + // naemon hands back a malloc'ed string that the caller frees. + *full_command = (cmd == nullptr) ? nullptr : strdup(cmd); + return 0; +} + +extern "C" int clear_volatile_macros_r(nagios_macros *) { + return 0; +} + +extern "C" struct comment *find_service_comment(unsigned long) { + return nullptr; +} + +extern "C" struct comment *find_host_comment(unsigned long) { + return nullptr; +} + +extern "C" const char *get_program_version(void) { + return "test"; +} + +namespace statusengine { + // Defined here instead of linking Nebmodule.cpp, which would pull in the whole naemon + // scheduling and downtime surface for no benefit: the encoder is the only part of + // Nebmodule that NagiosObject uses. + std::string Nebmodule::EncodeString(const char *inputData) { + return encoder.ToUtf8(inputData); + } +} // namespace statusengine diff --git a/tests/test_messageformat.cpp b/tests/test_messageformat.cpp new file mode 100644 index 0000000..029f33b --- /dev/null +++ b/tests/test_messageformat.cpp @@ -0,0 +1,306 @@ +#include + +#include +#include +#include +#include +#include + +#include "test_support.h" + +#include "NagiosObject.h" + +using namespace statusengine; + +namespace { + + /// Reads a recorded broker message from tests/fixtures. + json_object *LoadFixture(const std::string &name) { + std::ifstream in(std::string(STATUSENGINE_FIXTURE_DIR) + "/" + name); + REQUIRE_MESSAGE(in.good(), "missing fixture: ", name); + std::stringstream buffer; + buffer << in.rdbuf(); + json_object *obj = json_tokener_parse(buffer.str().c_str()); + REQUIRE_MESSAGE(obj != nullptr, "fixture is not valid json: ", name); + return obj; + } + + std::set KeysOf(json_object *obj) { + std::set keys; + REQUIRE(json_object_is_type(obj, json_type_object)); + json_object_object_foreach(obj, key, value) { + (void)value; + keys.insert(key); + } + return keys; + } + + /// Unwraps a fixture down to the single recorded message, bulk or not. + json_object *FirstMessage(json_object *fixture) { + json_object *messages; + if (json_object_object_get_ex(fixture, "messages", &messages)) { + REQUIRE(json_object_array_length(messages) > 0); + return json_object_array_get_idx(messages, 0); + } + return fixture; + } + + std::string Rendered(NagiosObject &obj) { + return obj.ToString(); + } + + /** + * Compares a freshly built message against a recorded one: the outer envelope and the + * named sub object have to carry exactly the same keys. Values are not compared, the + * fixtures were recorded from a different host. + */ + void CheckShape(NagiosObject &produced, const std::string &fixtureName, const char *subObject) { + json_object *fixture = LoadFixture(fixtureName); + json_object *expected = FirstMessage(fixture); + + json_object *actual = json_tokener_parse(Rendered(produced).c_str()); + REQUIRE(actual != nullptr); + + CHECK(KeysOf(actual) == KeysOf(expected)); + + if (subObject != nullptr) { + json_object *actualSub = nullptr; + json_object *expectedSub = nullptr; + REQUIRE(json_object_object_get_ex(actual, subObject, &actualSub)); + REQUIRE(json_object_object_get_ex(expected, subObject, &expectedSub)); + CHECK(KeysOf(actualSub) == KeysOf(expectedSub)); + } + + json_object_put(actual); + json_object_put(fixture); + } + + /// Fills the fields every nebstruct shares. + template + void FillHeader(T &data, int type) { + std::memset(&data, 0, sizeof(data)); + data.type = type; + data.flags = 0; + data.attr = 0; + data.timestamp.tv_sec = 1785470668; + data.timestamp.tv_usec = 701779; + } + + char *Str(const char *s) { + return const_cast(s); + } + +} // namespace + +TEST_CASE("host check message keeps its shape") { + host nagHost; + std::memset(&nagHost, 0, sizeof(nagHost)); + nagHost.name = Str("localhost"); + nagHost.check_command = Str("check-host-alive"); + + nebstruct_host_check_data data; + FillHeader(data, NEBTYPE_HOSTCHECK_PROCESSED); + data.host_name = Str("localhost"); + data.object_ptr = &nagHost; + data.output = Str("PING OK - Packet loss = 0%, RTA = 0.08 ms"); + data.long_output = Str(""); + data.perf_data = Str("rta=0.078000ms;3000.000000;5000.000000;0.000000"); + + NagiosHostCheckData msg(&data); + CheckShape(msg, "statusngin_hostchecks.json", "hostcheck"); +} + +TEST_CASE("service check message keeps its shape") { + service nagService; + std::memset(&nagService, 0, sizeof(nagService)); + nagService.host_name = Str("localhost"); + nagService.description = Str("Uptime"); + nagService.check_command = Str("check_uptime"); + + nebstruct_service_check_data data; + FillHeader(data, NEBTYPE_SERVICECHECK_PROCESSED); + data.host_name = Str("localhost"); + data.service_description = Str("Uptime"); + data.object_ptr = &nagService; + data.output = Str("OK - up 3 days"); + data.long_output = Str(""); + data.perf_data = Str("uptime=3"); + + NagiosServiceCheckData msg(&data); + CheckShape(msg, "statusngin_servicechecks.json", "servicecheck"); +} + +TEST_CASE("service perfdata message keeps its shape") { + nebstruct_service_check_data data; + FillHeader(data, NEBTYPE_SERVICECHECK_PROCESSED); + data.host_name = Str("localhost"); + data.service_description = Str("Uptime"); + data.perf_data = Str("uptime=3"); + + NagiosServiceCheckPerfData msg(&data); + CheckShape(msg, "statusngin_service_perfdata.json", "servicecheck"); +} + +TEST_CASE("host status message keeps its shape") { + host nagHost; + std::memset(&nagHost, 0, sizeof(nagHost)); + nagHost.name = Str("localhost"); + nagHost.plugin_output = Str("PING OK"); + nagHost.check_command = Str("check-host-alive"); + + nebstruct_host_status_data data; + FillHeader(data, NEBTYPE_HOSTSTATUS_UPDATE); + data.object_ptr = &nagHost; + + NagiosHostStatusData msg(&data); + CheckShape(msg, "statusngin_hoststatus.json", "hoststatus"); +} + +TEST_CASE("service status message keeps its shape") { + service nagService; + std::memset(&nagService, 0, sizeof(nagService)); + nagService.host_name = Str("localhost"); + nagService.description = Str("Uptime"); + nagService.plugin_output = Str("OK"); + + nebstruct_service_status_data data; + FillHeader(data, NEBTYPE_SERVICESTATUS_UPDATE); + data.object_ptr = &nagService; + + NagiosServiceStatusData msg(&data); + CheckShape(msg, "statusngin_servicestatus.json", "servicestatus"); +} + +TEST_CASE("state change message keeps its shape") { + service nagService; + std::memset(&nagService, 0, sizeof(nagService)); + nagService.last_state = 1; + nagService.last_hard_state = 1; + + nebstruct_statechange_data data; + FillHeader(data, NEBTYPE_STATECHANGE_END); + data.host_name = Str("localhost"); + data.service_description = Str("Flapping"); + data.statechange_type = SERVICE_STATECHANGE; + data.object_ptr = &nagService; + data.output = Str("Your random value is: 158"); + + NagiosStateChangeData msg(&data); + CheckShape(msg, "statusngin_statechanges.json", "statechange"); +} + +TEST_CASE("log entry message keeps its shape") { + nebstruct_log_data data; + FillHeader(data, NEBTYPE_LOG_DATA); + data.entry_time = 1785470668; + data.data_type = 262144; + data.data = Str("Event broker module initialized successfully."); + + NagiosLogData msg(&data); + CheckShape(msg, "statusngin_logentries.json", "logentry"); +} + +TEST_CASE("notification message keeps its shape") { + nebstruct_notification_data data; + FillHeader(data, NEBTYPE_NOTIFICATION_END); + data.host_name = Str("localhost"); + data.service_description = Str("Uptime"); + data.output = Str("CRITICAL"); + data.contacts_notified = 1; + + NagiosNotificationData msg(&data); + CheckShape(msg, "statusngin_notifications.json", "notification_data"); +} + +TEST_CASE("acknowledgement message keeps its shape") { + nebstruct_acknowledgement_data data; + FillHeader(data, NEBTYPE_ACKNOWLEDGEMENT_ADD); + data.host_name = Str("localhost"); + data.author_name = Str("admin"); + data.comment_data = Str("looking into it"); + + NagiosAcknowledgementData msg(&data); + CheckShape(msg, "statusngin_acknowledgements.json", "acknowledgement"); +} + +TEST_CASE("downtime message keeps its shape") { + nebstruct_downtime_data data; + FillHeader(data, NEBTYPE_DOWNTIME_ADD); + data.host_name = Str("localhost"); + data.author_name = Str("admin"); + data.comment_data = Str("maintenance"); + + NagiosDowntimeData msg(&data); + CheckShape(msg, "statusngin_downtimes.json", "downtime"); +} + +TEST_CASE("contact notification method message keeps its shape") { + nebstruct_contact_notification_method_data data; + FillHeader(data, NEBTYPE_CONTACTNOTIFICATIONMETHOD_END); + data.host_name = Str("localhost"); + data.contact_name = Str("admin"); + data.command_name = Str("notify-by-email"); + data.output = Str("CRITICAL"); + + NagiosContactNotificationMethodData msg(&data); + CheckShape(msg, "statusngin_contactnotificationmethod.json", "contactnotificationmethod"); +} + +TEST_CASE("notifications without notified contacts stay empty") { + // The broker drops these on purpose, see NagiosNotificationData. An empty object is + // how MessageQueueHandler recognises a filtered event. + nebstruct_notification_data data; + FillHeader(data, NEBTYPE_NOTIFICATION_END); + data.host_name = Str("localhost"); + data.contacts_notified = 0; + + NagiosNotificationData msg(&data); + CHECK(msg.isEmpty()); +} + +TEST_CASE("notification start events are dropped") { + nebstruct_notification_data data; + FillHeader(data, 600); // NEBTYPE_NOTIFICATION_START + data.host_name = Str("localhost"); + data.contacts_notified = 5; + + NagiosNotificationData msg(&data); + CHECK(msg.isEmpty()); +} + +TEST_CASE("a populated message is not empty") { + nebstruct_log_data data; + FillHeader(data, NEBTYPE_LOG_DATA); + data.data = Str("something happened"); + + NagiosLogData msg(&data); + CHECK_FALSE(msg.isEmpty()); +} + +TEST_CASE("non utf8 plugin output is converted in the message") { + // The whole point of the encoder: a latin1 check output has to reach the queue as + // valid utf8, otherwise the JSON is undecodable for the worker. + const std::string latin1 = "Gr\xf6\xdf" "e des Puffers \xfc" "berschritten, Dienst l\xe4uft nicht"; + + nebstruct_service_check_data data; + FillHeader(data, NEBTYPE_SERVICECHECK_PROCESSED); + data.host_name = Str("localhost"); + data.service_description = Str("Uptime"); + data.perf_data = const_cast(latin1.c_str()); + + NagiosServiceCheckPerfData msg(&data); + std::string rendered = Rendered(msg); + + // json-c refuses to build a string from invalid utf8, so a successful round trip + // through the parser is what proves the conversion happened. + json_object *parsed = json_tokener_parse(rendered.c_str()); + REQUIRE(parsed != nullptr); + + json_object *sub = nullptr; + REQUIRE(json_object_object_get_ex(parsed, "servicecheck", &sub)); + json_object *perf = nullptr; + REQUIRE(json_object_object_get_ex(sub, "perf_data", &perf)); + CHECK(std::string(json_object_get_string(perf)) == "Größe des Puffers überschritten, Dienst läuft nicht"); + + json_object_put(parsed); +} From d0c7f9b4dc1c5b4e7369f97118980cc91228a9d4 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 19:28:02 +0200 Subject: [PATCH 07/28] Send the restart timestamp in the core restart message The RestartData message only carried object_type, so the worker had no way to know when the restart actually happened and fell back to its own wall clock as the cutoff for stale rows. The field was already part of the documented format - statusngin_core_restart.json records it as 0 - and the worker already treats a missing value or a 0 as "not set", so populating it is backwards compatible in both directions. The value is naemon's own event timestamp from the nebstruct rather than a fresh time() call, which is both more accurate and consistent with every other message type. Verified against a running naemon 1.5.2: the emitted message is {"object_type": 102, "timestamp": 1787678804}, matching container startup. Building the message moves into a NagiosRestartData class, like every other message type in NagiosObject.h. It was the one message assembled inline in the callback, which is also why it was the only recorded shape without a golden test - the produced shape and the recording disagreed. Both now match and it is covered like the rest. Note for the worker: its copy of the spec and the comment in newCoreRestartHandler still say naemon does not send this field. The fallback stays correct and is still needed for older brokers, but the comment is now out of date. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- README.md | 12 +++++++++ src/NagiosObject.h | 11 ++++++++ src/NebmoduleCallback.h | 3 +-- tests/fixtures/statusngin_core_restart.json | 2 +- tests/test_messageformat.cpp | 30 +++++++++++++++++++++ 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 02f97b6..3fa979c 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,18 @@ The reason is that the underlying naemon/nagios `nebstruct_*` structs simply hav duplication is kept for backwards compatibility, so that existing consumers do not break. Do not read a separate long plugin output out of these five event types. +### `timestamp` on core restart + +The `RestartData` message carries the unix timestamp of the restart: + +```json +{"object_type": 102, "timestamp": 1787678804} +``` + +The value comes from naemon's own event time. Consumers should keep treating a +missing field or a `0` as "not set" and fall back to their own clock - older +brokers did not populate it. + ## Developer build + test If you want to build and test the broker, you can use the docker-compose configuration: diff --git a/src/NagiosObject.h b/src/NagiosObject.h index 39a4356..522ae81 100644 --- a/src/NagiosObject.h +++ b/src/NagiosObject.h @@ -143,6 +143,17 @@ namespace statusengine { } }; + class NagiosRestartData : public NagiosObject { + public: + explicit NagiosRestartData(const nebstruct_process_data *processData) { + SetData("object_type", static_cast(NEBTYPE_PROCESS_RESTART)); + // Unix timestamp of the restart itself. The worker treats a missing value or a + // 0 as "not set" and falls back to its own wall clock as the stale row cutoff, + // so sending naemon's own event time gives it an accurate one instead. + SetData("timestamp", processData->timestamp.tv_sec); + } + }; + class NagiosAcknowledgementData : public NagiosObject { public: explicit NagiosAcknowledgementData(const nebstruct_acknowledgement_data *acknowledgementData) { diff --git a/src/NebmoduleCallback.h b/src/NebmoduleCallback.h index cc36f2b..a7fd681 100644 --- a/src/NebmoduleCallback.h +++ b/src/NebmoduleCallback.h @@ -133,8 +133,7 @@ namespace statusengine { if (data->type == NEBTYPE_PROCESS_START) { se.InitEventCallbacks(); if (restartData) { - NagiosObject msgObj; - msgObj.SetData("object_type", static_cast(NEBTYPE_PROCESS_RESTART)); + NagiosRestartData msgObj(data); restartHandler->SendMessage(msgObj); } } diff --git a/tests/fixtures/statusngin_core_restart.json b/tests/fixtures/statusngin_core_restart.json index 0f8f407..c500ad5 100644 --- a/tests/fixtures/statusngin_core_restart.json +++ b/tests/fixtures/statusngin_core_restart.json @@ -1,4 +1,4 @@ { "object_type": 102, - "timestamp": 0 + "timestamp": 1787678804 } diff --git a/tests/test_messageformat.cpp b/tests/test_messageformat.cpp index 029f33b..ed41633 100644 --- a/tests/test_messageformat.cpp +++ b/tests/test_messageformat.cpp @@ -304,3 +304,33 @@ TEST_CASE("non utf8 plugin output is converted in the message") { json_object_put(parsed); } + +TEST_CASE("core restart message keeps its shape") { + // Until this was added the broker only sent object_type, so the recorded shape and the + // produced one disagreed and this was the one message type without a golden test. + nebstruct_process_data data; + FillHeader(data, NEBTYPE_PROCESS_START); + + NagiosRestartData msg(&data); + CheckShape(msg, "statusngin_core_restart.json", nullptr); +} + +TEST_CASE("core restart carries naemon's event time") { + nebstruct_process_data data; + FillHeader(data, NEBTYPE_PROCESS_START); + data.timestamp.tv_sec = 1785470668; + + NagiosRestartData msg(&data); + json_object *parsed = json_tokener_parse(Rendered(msg).c_str()); + REQUIRE(parsed != nullptr); + + json_object *value = nullptr; + REQUIRE(json_object_object_get_ex(parsed, "object_type", &value)); + CHECK(json_object_get_int(value) == NEBTYPE_PROCESS_RESTART); + + REQUIRE(json_object_object_get_ex(parsed, "timestamp", &value)); + // The worker reads 0 as "not set", so a real restart must never report 0. + CHECK(json_object_get_int64(value) == 1785470668); + + json_object_put(parsed); +} From 48b9f3ccb9f1a7b7f4029b876ed8eb3fa581c01a Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 20:00:44 +0200 Subject: [PATCH 08/28] Bound the gearman worker poll and the worker loop MessageHandlerList::Worker() could not be bounded by its own message limit. A handler may report that it has more work without having processed anything - the gearman worker does exactly that on GEARMAN_IO_WAIT - and only a processed message advances the counter that the loop checks. A handler in that state therefore span the loop forever, inside naemon's event loop, which stops the monitoring core from scheduling anything at all. The loop now also ends when a full round over all handlers made no progress, so no handler can wedge it, and the loop body moved into a static RunWorkers() that tests can drive with fakes. The gearman side is bounded rather than removed. gearman_worker_wait() polls with gearman_universal_st's timeout, which defaults to -1 - an unbounded poll() in the event loop - and the timeout was never set. It is now capped at 10ms. Removing the wait entirely, which is what this looked like it needed, breaks the worker completely: gearman_wait() is not a sleep, it runs the poll() that refreshes the connection's readiness. Without it libgearman never learns the socket became writable and gearman_worker_work() returns GEARMAN_IO_WAIT on every tick forever, so the worker never completes its PRE_SLEEP handshake, never registers as available and never picks up a job. Verified against a running naemon: with the wait removed, gearadmin reported 0 available workers for statusngin_cmd and a submitted job sat in the queue unprocessed; with the bounded wait it reports 1 and the job is applied. 10ms was chosen by measurement: against a healthy local job server the wait completes in 3 to 119 microseconds, so the cap is only ever reached when something is wrong, and a timed out poll just retries on the next tick. The timeout only affects that poll - connection.cc does not read it, and the other user is the blocking grab path that GEARMAN_WORKER_NON_BLOCKING never reaches. The regression test is the important part: a fake handler that always asks for more without progressing hangs the loop without this change, confirmed by running it against the old code under a timeout. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- src/MessageHandler/GearmanClient.cpp | 17 +++++ src/MessageHandler/MessageHandlerList.h | 25 ++++++- tests/meson.build | 2 +- tests/test_support.h | 38 ++++++++++ tests/test_workerloop.cpp | 96 +++++++++++++++++++++++++ 5 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 tests/test_workerloop.cpp diff --git a/src/MessageHandler/GearmanClient.cpp b/src/MessageHandler/GearmanClient.cpp index eb96867..4591ddc 100644 --- a/src/MessageHandler/GearmanClient.cpp +++ b/src/MessageHandler/GearmanClient.cpp @@ -6,6 +6,11 @@ namespace statusengine { + namespace { + /// Upper bound in milliseconds for a single gearman_worker_wait() poll. + const int gearmanPollTimeoutMs = 10; + } // namespace + class GearmanWorkerContext { public: GearmanWorkerContext(WorkerQueue queue, GearmanClient *client) : Queue(queue), Client(client){}; @@ -31,6 +36,13 @@ namespace statusengine { if (!workerQueueNames->empty()) { worker = gearman_worker_create(nullptr); gearman_worker_add_options(worker, GEARMAN_WORKER_NON_BLOCKING); + // gearman_worker_wait() polls with this timeout. libgearman defaults to -1, + // an unbounded poll() - inside naemon's event loop that means an unresponsive + // job server can stall the whole monitoring core. Waiting for a healthy local + // server was measured at 3 to 119 microseconds, so this cap is only ever + // reached when something is wrong, and a timed out poll simply retries on the + // next worker tick. + gearman_worker_set_timeout(worker, gearmanPollTimeoutMs); } } @@ -123,6 +135,11 @@ namespace statusengine { case GEARMAN_NO_JOBS: break; case GEARMAN_IO_WAIT: + // Not just a sleep: gearman_wait() runs the poll() that refreshes the + // connection's readiness. Without it libgearman never learns the socket + // became writable and keeps returning IO_WAIT forever, so the worker + // never finishes its PRE_SLEEP handshake and never picks up a job. + // It is bounded by the timeout set in the constructor. gearman_worker_wait(worker); moreJobs = true; break; diff --git a/src/MessageHandler/MessageHandlerList.h b/src/MessageHandler/MessageHandlerList.h index a39a53d..130446f 100644 --- a/src/MessageHandler/MessageHandlerList.h +++ b/src/MessageHandler/MessageHandlerList.h @@ -54,16 +54,37 @@ namespace statusengine { } void Worker() override { + RunWorkers(allHandlers, maxWorkerMessagesPerInterval); + } + + /** + * Lets every handler drain its queue, until nobody has anything left or + * maxMessages have been processed. + * + * A handler may ask for another round without having processed anything - the + * gearman worker does exactly that when its socket would block. Such a handler + * cannot advance the message counter, so counting messages alone does not bound + * this loop, and it runs inside naemon's event loop. A round that made no progress + * therefore ends it, and the next worker tick picks the work up again. + * + * Static and defined here so it can be exercised with fake handlers. + */ + static void RunWorkers(std::vector> &handlers, + unsigned long maxMessages) { unsigned long counter = 0ul; bool moreMessages; do { moreMessages = false; - for (auto &handler : allHandlers) { + const unsigned long before = counter; + for (auto &handler : handlers) { if (handler->Worker(counter)) { moreMessages = true; } } - } while (moreMessages && (counter < maxWorkerMessagesPerInterval)); + if (counter == before) { + break; + } + } while (moreMessages && (counter < maxMessages)); } diff --git a/tests/meson.build b/tests/meson.build index 28ea18d..f2b6199 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -21,7 +21,7 @@ endif test_incdirs = include_directories('.', '../src') statusengine_tests = executable('statusengine-tests', - ['main.cpp', 'naemon_stubs.cpp', 'test_encoding.cpp', 'test_configuration.cpp', 'test_logstream.cpp', 'test_queue.cpp', 'test_messageformat.cpp', + ['main.cpp', 'naemon_stubs.cpp', 'test_encoding.cpp', 'test_configuration.cpp', 'test_logstream.cpp', 'test_queue.cpp', 'test_messageformat.cpp', 'test_workerloop.cpp', '../src/Encoding.cpp'], include_directories: test_incdirs, dependencies: test_deps, diff --git a/tests/test_support.h b/tests/test_support.h index 1dd99da..1baeccc 100644 --- a/tests/test_support.h +++ b/tests/test_support.h @@ -4,6 +4,7 @@ #include #include "IStatusengine.h" +#include "MessageHandler/IMessageHandler.h" /// Everything nm_log() was called with, in order. Cleared by FakeStatusengine's constructor. extern std::vector capturedLogs; @@ -46,4 +47,41 @@ namespace statusengine { private: LogStream ls; }; + + /** + * IMessageHandler whose Worker() behaviour is scripted, so the worker loop can be + * driven through cases a real gearman or rabbitmq connection would be needed for. + */ + class FakeMessageHandler : public IMessageHandler { + public: + /** + * @param messages how many messages it reports as processed before running dry + * @param keepAskingWhenDry whether it still claims more work once it is dry - what + * the gearman worker does on GEARMAN_IO_WAIT + */ + FakeMessageHandler(unsigned long messages, bool keepAskingWhenDry) + : remaining(messages), keepAsking(keepAskingWhenDry), calls(0) {} + + bool Worker(unsigned long &counter) override { + ++calls; + if (remaining > 0) { + --remaining; + ++counter; + return true; + } + return keepAsking; + } + + bool Connect() override { + return true; + } + void SendMessage(Queue, const std::string &) override {} + void ProcessMessage(WorkerQueue, const std::string &) override {} + void ProcessMessage(WorkerQueue, json_object *) override {} + + unsigned long remaining; + bool keepAsking; + /// How often Worker() was entered - the guard against a spinning loop. + unsigned long calls; + }; } // namespace statusengine diff --git a/tests/test_workerloop.cpp b/tests/test_workerloop.cpp new file mode 100644 index 0000000..1afd443 --- /dev/null +++ b/tests/test_workerloop.cpp @@ -0,0 +1,96 @@ +#include + +#include +#include + +#include "test_support.h" + +#include "MessageHandler/MessageHandlerList.h" + +using statusengine::FakeMessageHandler; +using statusengine::IMessageHandler; +using statusengine::MessageHandlerList; + +namespace { + using HandlerList = std::vector>; + + std::shared_ptr Add(HandlerList &list, unsigned long messages, bool keepAsking) { + auto handler = std::make_shared(messages, keepAsking); + list.push_back(handler); + return handler; + } +} // namespace + +TEST_CASE("a handler that asks for more without progressing does not spin the loop") { + // This is the GEARMAN_IO_WAIT shape: always "there is more", never a processed + // message, so the message counter can never end the loop. Before the loop was bounded + // by progress this ran forever, inside naemon's event loop. + HandlerList handlers; + auto stuck = Add(handlers, 0, true); + + MessageHandlerList::RunWorkers(handlers, 1000000ul); + + CHECK(stuck->calls == 1); +} + +TEST_CASE("a handler drains its queue within one tick") { + HandlerList handlers; + auto busy = Add(handlers, 5, false); + + MessageHandlerList::RunWorkers(handlers, 1000000ul); + + CHECK(busy->remaining == 0); + // five rounds that processed something, plus the one that found nothing left + CHECK(busy->calls == 6); +} + +TEST_CASE("the message limit is respected") { + HandlerList handlers; + auto busy = Add(handlers, 100, false); + + MessageHandlerList::RunWorkers(handlers, 10ul); + + CHECK(busy->calls == 10); + CHECK(busy->remaining == 90); +} + +TEST_CASE("a stuck handler does not stop the others") { + HandlerList handlers; + auto stuck = Add(handlers, 0, true); + auto busy = Add(handlers, 3, false); + + MessageHandlerList::RunWorkers(handlers, 1000000ul); + + // The loop keeps going while anyone makes progress, so the working handler drains... + CHECK(busy->remaining == 0); + // ...and it ends on the first round where nobody did, rather than on the stuck one. + CHECK(stuck->calls == 4); +} + +TEST_CASE("a handler that goes quiet ends the loop") { + HandlerList handlers; + auto first = Add(handlers, 2, false); + auto second = Add(handlers, 1, false); + + MessageHandlerList::RunWorkers(handlers, 1000000ul); + + CHECK(first->remaining == 0); + CHECK(second->remaining == 0); +} + +TEST_CASE("an empty handler list terminates") { + HandlerList handlers; + MessageHandlerList::RunWorkers(handlers, 1000000ul); + CHECK(handlers.empty()); +} + +TEST_CASE("a zero message limit still runs one round") { + // maxMessages is checked after a round, so work already done is never thrown away. + HandlerList handlers; + auto busy = Add(handlers, 5, false); + + MessageHandlerList::RunWorkers(handlers, 0ul); + + CHECK(busy->calls == 1); + CHECK(busy->remaining == 4); +} From 9cff7cc702cb50089076bff6218f27036fe4a33c Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 20:44:09 +0200 Subject: [PATCH 09/28] Add micro benchmarks for the two hot message paths Optimising the broker without numbers would be guesswork, so this measures first. naemon's own process_check_result() is stubbed, which is the point: what is measured is the broker's own share, the only part we can change. Covers the send path (charset handling and building a whole service check message) and the receive path (a single command and a 100 entry OCSP bulk, built from the recorded fixture so the shape is realistic). Sub measurements split the cost into its parts, which is what makes the numbers actionable rather than just a total. Baseline on this machine, release build: Encoder ascii 268 ns/op Encoder utf8 3985 ns/op Encoder latin1 4232 ns/op uchardet detect only ascii 59 ns/op uchardet detect only utf8 3937 ns/op iconv ASCII->UTF-8 only 188 ns/op NagiosServiceCheckData build 3441 ns/op ProcessMessage command (1 result) 2460 ns/op ProcessMessage ocsp bulk (100) 419530 ns/op json_tokener_parse only (bulk) 326304 ns/op payload copy only (bulk) 815 ns/op Two things fall straight out of that. Charset detection, not conversion, dominates the encoder: 3937 of 3985 ns on utf8 input. And 78% of the bulk receive path is json-c parsing, which we cannot do anything about - the broker's own share is the remaining 93 us per 100 results. Needs --buildtype=release, the numbers mean nothing at -O0. It is built with -Dtests=true but not run by meson test; a benchmark that fails a build is useful, one that fails CI on a noisy runner is not. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- tests/bench.cpp | 193 +++++++++++++++++++++++++++++++++++++++++ tests/meson.build | 8 ++ tests/naemon_stubs.cpp | 51 +++++++++++ tests/test_support.h | 3 + 4 files changed, 255 insertions(+) create mode 100644 tests/bench.cpp diff --git a/tests/bench.cpp b/tests/bench.cpp new file mode 100644 index 0000000..ee0e695 --- /dev/null +++ b/tests/bench.cpp @@ -0,0 +1,193 @@ +/** + * Micro benchmarks for the two paths that carry the message volume. + * + * naemon's own process_check_result() is stubbed out, so what is measured here is the + * broker's share of the work, which is the only part we can do anything about. + * + * Build with --buildtype=release, the numbers are meaningless at -O0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_support.h" + +#include "MessageHandler/MessageHandler.h" +#include "NagiosObject.h" + +using namespace statusengine; + +namespace { + + /// MessageHandler is abstract; the benchmark only needs its ProcessMessage. + class BenchHandler : public MessageHandler { + public: + explicit BenchHandler(IStatusengine *se) : MessageHandler(se) {} + bool Connect() override { + return true; + } + bool Worker(unsigned long &) override { + return false; + } + void SendMessage(Queue, const std::string &) override {} + }; + + std::string ReadFixture(const std::string &name) { + std::ifstream in(std::string(STATUSENGINE_FIXTURE_DIR) + "/" + name); + std::stringstream buffer; + buffer << in.rdbuf(); + return buffer.str(); + } + + /// Builds an OCSP worker payload with `count` service checks, the shape the broker + /// receives from another instance: {"messages":[{...,"servicecheck":{...}}, ...]}. + std::string BulkServiceChecks(size_t count) { + json_object *fixture = json_tokener_parse(ReadFixture("statusngin_servicechecks.json").c_str()); + json_object *messages = nullptr; + json_object_object_get_ex(fixture, "messages", &messages); + json_object *one = json_object_array_get_idx(messages, 0); + + json_object *arr = json_object_new_array(); + for (size_t i = 0; i < count; ++i) { + json_object_array_add(arr, json_object_get(one)); + } + json_object *root = json_object_new_object(); + json_object_object_add(root, "messages", arr); + + std::string result(json_object_to_json_string(root)); + json_object_put(root); + json_object_put(fixture); + return result; + } + + std::string SingleCommand() { + return R"({"Command":"check_result","Data":{"host_name":"localhost",)" + R"("service_description":"Uptime","output":"OK - up 3 days, load average 0.14",)" + R"("long_output":"","perf_data":"uptime=3;;;0 load=0.14;;;0","check_type":1,)" + R"("return_code":0,"start_time":1785470664,"end_time":1785470668,)" + R"("early_timeout":0,"latency":0.6,"exited_ok":1}})"; + } + + long long sink = 0; + + template + void Bench(const char *name, unsigned long iterations, F body) { + body(); // warm up caches and any one time allocation + auto start = std::chrono::steady_clock::now(); + for (unsigned long i = 0; i < iterations; ++i) { + body(); + } + auto ns = std::chrono::duration_cast( + std::chrono::steady_clock::now() - start) + .count(); + double perOp = static_cast(ns) / static_cast(iterations); + std::cout << std::left << std::setw(38) << name << std::right << std::setw(12) << std::fixed + << std::setprecision(0) << perOp << " ns/op" << std::setw(14) << std::setprecision(0) + << (perOp > 0 ? 1e9 / perOp : 0) << " ops/s" << std::endl; + } + +} // namespace + +int main() { + FakeStatusengine se; + BenchHandler handler(&se); + + const std::string ascii = "OK - up 3 days, load average 0.14, 4 users, all services responding"; + const std::string utf8 = "Größe des Puffers überschritten, Dienst läuft nicht mehr korrekt"; + std::string latin1; + for (size_t i = 0; i < utf8.size();) { + unsigned char c = static_cast(utf8[i]); + if (c < 0x80) { latin1 += static_cast(c); i += 1; } + else { latin1 += static_cast(((c & 0x1fu) << 6) | (static_cast(utf8[i + 1]) & 0x3fu)); i += 2; } + } + + Encoder encoder; + std::cout << "--- send path: charset handling ---" << std::endl; + Bench("Encoder ascii", 200000, [&] { sink += encoder.ToUtf8(ascii.c_str()).size(); }); + Bench("Encoder utf8", 200000, [&] { sink += encoder.ToUtf8(utf8.c_str()).size(); }); + Bench("Encoder latin1", 200000, [&] { sink += encoder.ToUtf8(latin1.c_str()).size(); }); + + // Split the encoder cost: how much is uchardet's detection, how much is the conversion? + { + uchardet_t uc = uchardet_new(); + Bench(" uchardet detect only ascii", 200000, [&] { + uchardet_handle_data(uc, ascii.c_str(), ascii.size()); + uchardet_data_end(uc); + sink += uchardet_get_charset(uc)[0]; + uchardet_reset(uc); + }); + Bench(" uchardet detect only utf8", 200000, [&] { + uchardet_handle_data(uc, utf8.c_str(), utf8.size()); + uchardet_data_end(uc); + sink += uchardet_get_charset(uc)[0]; + uchardet_reset(uc); + }); + uchardet_delete(uc); + + Bench(" iconv ASCII->UTF-8 only", 200000, [&] { + iconv_t cd = iconv_open("UTF-8", "ASCII"); + std::string out(ascii.size() * 4, '\0'); + char *in = const_cast(ascii.c_str()); + size_t inLeft = ascii.size(); + char *outCur = &out[0]; + size_t outLeft = out.size(); + iconv(cd, &in, &inLeft, &outCur, &outLeft); + iconv_close(cd); + sink += outCur - &out[0]; + }); + } + + std::cout << "\n--- send path: whole message ---" << std::endl; + service nagService; + std::memset(&nagService, 0, sizeof(nagService)); + nagService.host_name = const_cast("localhost"); + nagService.description = const_cast("Uptime"); + nagService.check_command = const_cast("check_uptime"); + + nebstruct_service_check_data scd; + std::memset(&scd, 0, sizeof(scd)); + scd.type = NEBTYPE_SERVICECHECK_PROCESSED; + scd.host_name = const_cast("localhost"); + scd.service_description = const_cast("Uptime"); + scd.object_ptr = &nagService; + scd.output = const_cast(ascii.c_str()); + scd.long_output = const_cast(""); + scd.perf_data = const_cast("uptime=3;;;0 load=0.14;;;0"); + + Bench("NagiosServiceCheckData build", 100000, [&] { + NagiosServiceCheckData msg(&scd); + sink += static_cast(msg.ToString().size()); + }); + + std::cout << "\n--- receive path ---" << std::endl; + const std::string single = SingleCommand(); + Bench("ProcessMessage command (1 result)", 100000, + [&] { handler.ProcessMessage(WorkerQueue::Command, single); }); + + const std::string bulk100 = BulkServiceChecks(100); + Bench("ProcessMessage ocsp bulk (100)", 2000, + [&] { handler.ProcessMessage(WorkerQueue::OCSP, bulk100); }); + // How much of that is json-c parsing, which we cannot avoid? + Bench(" json_tokener_parse only (bulk)", 2000, [&] { + json_object *o = json_tokener_parse(bulk100.c_str()); + sink += json_object_object_length(o); + json_object_put(o); + }); + // And how much is the std::string copy of the payload that step 4 targets? + Bench(" payload copy only (bulk)", 200000, [&] { + std::string copy(bulk100.data(), bulk100.size()); + sink += static_cast(copy.size()); + }); + + std::cout << "\nbulk payload of 100: " << bulk100.size() << " bytes, " + << "processed check results: " << processedCheckResults << std::endl; + return sink == 0x7fffffff ? 1 : 0; // keep the sink alive +} diff --git a/tests/meson.build b/tests/meson.build index f2b6199..a2fedba 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -30,3 +30,11 @@ statusengine_tests = executable('statusengine-tests', install: false) test('unit', statusengine_tests, protocol: 'exitcode', timeout: 120) + +statusengine_bench = executable('statusengine-bench', + ['bench.cpp', 'naemon_stubs.cpp', '../src/Encoding.cpp'], + include_directories: test_incdirs, + dependencies: test_deps, + cpp_args: test_cpp_args, + build_by_default: true, + install: false) diff --git a/tests/naemon_stubs.cpp b/tests/naemon_stubs.cpp index 6c58681..b5d650f 100644 --- a/tests/naemon_stubs.cpp +++ b/tests/naemon_stubs.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -63,3 +64,53 @@ namespace statusengine { return encoder.ToUtf8(inputData); } } // namespace statusengine + +// --- check_result handling, referenced by MessageHandler::ParseCheckResult ------------- +// +// free_check_result() really frees, so that the benchmark and any future test see the same +// allocation pattern as production and sanitizers can still catch a mismatch. + +extern "C" int init_check_result(check_result *cr) { + std::memset(cr, 0, sizeof(*cr)); + return 0; +} + +extern "C" int free_check_result(check_result *cr) { + free(cr->host_name); + free(cr->service_description); + free(cr->output); + free(cr->output_file); + cr->host_name = nullptr; + cr->service_description = nullptr; + cr->output = nullptr; + cr->output_file = nullptr; + return 0; +} + +/// Counts calls so a caller can assert the result actually reached naemon. +unsigned long processedCheckResults = 0; + +extern "C" int process_check_result(check_result *) { + ++processedCheckResults; + return 0; +} + +// --- reached from ProcessMessage's other command branches -------------------------------- + +extern "C" host *find_host(const char *) { + return nullptr; +} + +extern "C" service *find_service(const char *, const char *) { + return nullptr; +} + +extern "C" int process_external_command1(char *) { + return 0; +} + +namespace statusengine { + void Nebmodule::ScheduleHostCheckFixed(host *, time_t) {} + void Nebmodule::ScheduleServiceCheckFixed(service *, time_t) {} + void Nebmodule::DeleteDowntime(const char *, const char *, time_t, time_t, const char *) {} +} // namespace statusengine diff --git a/tests/test_support.h b/tests/test_support.h index 1baeccc..487813f 100644 --- a/tests/test_support.h +++ b/tests/test_support.h @@ -9,6 +9,9 @@ /// Everything nm_log() was called with, in order. Cleared by FakeStatusengine's constructor. extern std::vector capturedLogs; +/// How often the stubbed process_check_result() was called. +extern unsigned long processedCheckResults; + namespace statusengine { /** From 9e7a51bf3db64c1515a27f99536589cbca4f13d3 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 20:44:21 +0200 Subject: [PATCH 10/28] Skip charset detection when the input already is UTF-8 Encoder::ToUtf8 ran uchardet's statistical detection over every plugin output, long output and perf data - three times per service check event. The benchmark puts that at 3937 ns of the 3985 ns an utf8 string cost, so detection, not conversion, was where the time went. Pure ASCII took a different but equally pointless route: uchardet reports the charset as "ASCII", which is neither empty nor "UTF-8", so the string went through a full iconv ASCII->UTF-8 conversion plus a buffer of four times the input length, to produce the very same bytes back. That conversion was 188 of its 268 ns. A UTF-8 validity scan up front skips both. ASCII is a subset of UTF-8, so one check covers both cases, and anything that really needs converting still falls through to uchardet exactly as before: Encoder ascii 268 -> 48 ns/op (5.6x) Encoder utf8 3985 -> 51 ns/op (78x) Encoder latin1 4232 -> 4335 ns/op (2% slower, the scan now runs first and fails) whole message 3441 -> 2750 ns/op (-20%) The validator rejects overlong encodings, surrogate halves, truncated sequences and out of range code points, so nothing it passes through unconverted is anything but well formed UTF-8. Tests cover each of those, and the malformed cases assert that they still reach the detection path. One behavioural nuance worth recording: a latin1 string whose bytes happen to also be valid UTF-8 is now passed through instead of being converted. That conversion produced mojibake, so the new result is the better one, but it is a difference. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- src/Encoding.cpp | 68 +++++++++++++++++++++++++++++++++++++++++ tests/test_encoding.cpp | 40 ++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/src/Encoding.cpp b/src/Encoding.cpp index b5d202e..9d5bde2 100644 --- a/src/Encoding.cpp +++ b/src/Encoding.cpp @@ -7,6 +7,68 @@ namespace statusengine { + namespace { + /** + * True if the bytes already are valid UTF-8. Pure ASCII is a subset of UTF-8, so + * this one check covers both cases that need no conversion at all. + * + * Worth having because uchardet's statistical detection costs around 4us on a + * typical plugin output, while this scan is a few nanoseconds per byte - and + * virtually all real plugin output is ASCII or valid UTF-8 already. + */ + bool IsValidUtf8(const char *data, size_t length) { + const unsigned char *p = reinterpret_cast(data); + for (size_t i = 0; i < length;) { + const unsigned char c = p[i]; + if (c < 0x80) { + i += 1; + continue; + } + + size_t continuation; + unsigned int codepoint; + if ((c & 0xe0) == 0xc0) { + continuation = 1; + codepoint = c & 0x1fu; + } + else if ((c & 0xf0) == 0xe0) { + continuation = 2; + codepoint = c & 0x0fu; + } + else if ((c & 0xf8) == 0xf0) { + continuation = 3; + codepoint = c & 0x07u; + } + else { + return false; // continuation byte in leading position, or 5/6 byte form + } + + if (i + continuation >= length) { + return false; // truncated sequence + } + for (size_t k = 1; k <= continuation; ++k) { + if ((p[i + k] & 0xc0) != 0x80) { + return false; + } + codepoint = (codepoint << 6) | (p[i + k] & 0x3fu); + } + + // Reject the encodings a strict decoder rejects, so that anything this + // function passes through really is well formed UTF-8. + if ((continuation == 1 && codepoint < 0x80) || (continuation == 2 && codepoint < 0x800) || + (continuation == 3 && codepoint < 0x10000)) { + return false; // overlong + } + if (codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF)) { + return false; // out of range or surrogate half + } + + i += continuation + 1; + } + return true; + } + } // namespace + Encoder::Encoder() : uc(uchardet_new()), warn() {} Encoder::~Encoder() { @@ -28,6 +90,12 @@ namespace statusengine { return std::string(); } + // Fast path: nothing to detect and nothing to convert if it already is UTF-8. + // Skipping uchardet here is what makes the common case cheap. + if (IsValidUtf8(inputData, inputLength)) { + return std::string(inputData, inputLength); + } + uchardet_handle_data(uc, inputData, inputLength); //TODO error handling uchardet_data_end(uc); // uchardet owns the returned string, it must not be freed. Copy it before the reset diff --git a/tests/test_encoding.cpp b/tests/test_encoding.cpp index cb3c53e..c0afc8e 100644 --- a/tests/test_encoding.cpp +++ b/tests/test_encoding.cpp @@ -76,3 +76,43 @@ TEST_CASE("Encoder keeps embedded newlines and pipes") { Encoder encoder; CHECK(encoder.ToUtf8("first line\nsecond line|perf=1") == "first line\nsecond line|perf=1"); } + +TEST_CASE("valid multi byte utf8 passes through untouched") { + Encoder encoder; + // two, three and four byte sequences + const std::string samples[] = {"ü ö ä ß", "€ ✓ →", "\xf0\x9f\x91\x8d ok", "日本語のテキスト"}; + for (const auto &sample : samples) { + CHECK(encoder.ToUtf8(sample.c_str()) == sample); + } +} + +TEST_CASE("malformed utf8 is not mistaken for valid") { + // These must fall through to charset detection rather than being passed through as + // "already utf8", otherwise broken bytes would reach the queue. + Encoder encoder; + + SUBCASE("lone continuation byte") { + const char input[] = "ok \x80 tail"; + CHECK(encoder.ToUtf8(input) != std::string(input)); + } + SUBCASE("truncated two byte sequence at end") { + const char input[] = "ok \xc3"; + CHECK(encoder.ToUtf8(input) != std::string(input)); + } + SUBCASE("overlong encoding of '/'") { + const char input[] = "ok \xc0\xaf tail"; + CHECK(encoder.ToUtf8(input) != std::string(input)); + } + SUBCASE("surrogate half") { + const char input[] = "ok \xed\xa0\x80 tail"; + CHECK(encoder.ToUtf8(input) != std::string(input)); + } +} + +TEST_CASE("ascii output is returned byte for byte") { + // ASCII is a subset of UTF-8, so the fast path returns it unchanged where the old code + // ran it through an iconv ASCII->UTF-8 conversion. The bytes have to be identical. + Encoder encoder; + const std::string sample = "OK - up 3 days, load 0.14 | uptime=3;;;0 load=0.14;;;0"; + CHECK(encoder.ToUtf8(sample.c_str()) == sample); +} From 1fd5f1615068731ad0c836c08c22bef8517bcd43 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 20:44:33 +0200 Subject: [PATCH 11/28] Look up check result fields instead of scanning every key ParseCheckResult iterated the JSON object and ran each key down a chain of up to twelve string comparisons, copying it into a std::string first - around 78 comparisons per check result, plus a heap allocation for service_description, which at 19 characters does not fit libstdc++'s small string buffer. json-c keeps object members in a hash table, so asking for the twelve known fields directly is one lookup each and the nesting disappears with it: ProcessMessage command (1 result) 2460 -> 2151 ns/op (-13%) ProcessMessage ocsp bulk (100) 419530 -> 352246 ns/op (-16%) The headline numbers understate it, because most of the bulk path is json-c parsing that neither version can avoid. Subtracting it, the broker's own share per check result drops from 932 to 312 ns, so the part we control got 67% cheaper. Behaviour is unchanged: same fields, same types, a missing field is still left at the value init_check_result gave it, and a JSON null still yields a null pointer. Verified against a running naemon by submitting a check_result over gearman and confirming both plugin output and perf data arrive. The same pattern remains in the three other parse functions, which handle far lower volumes and were left alone. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- src/MessageHandler/MessageHandler.h | 77 +++++++++++++++-------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/src/MessageHandler/MessageHandler.h b/src/MessageHandler/MessageHandler.h index 5c7afe7..75bded9 100644 --- a/src/MessageHandler/MessageHandler.h +++ b/src/MessageHandler/MessageHandler.h @@ -224,44 +224,45 @@ namespace statusengine { free(perfData); }); - json_object_object_foreach(obj, cKey, jsonValue) { - std::string jsonKey(cKey); - if (jsonKey.compare("host_name") == 0) { - cr.host_name = get_json_string_c(jsonValue); - } - else if (jsonKey.compare("service_description") == 0) { - cr.service_description = get_json_string_c(jsonValue); - } - else if (jsonKey.compare("output") == 0) { - output = get_json_string_c(jsonValue); - } - else if (jsonKey.compare("long_output") == 0) { - longOutput = get_json_string_c(jsonValue); - } - else if (jsonKey.compare("perf_data") == 0) { - perfData = get_json_string_c(jsonValue); - } - else if (jsonKey.compare("check_type") == 0) { - cr.check_type = json_object_get_int64(jsonValue); - } - else if (jsonKey.compare("return_code") == 0) { - cr.return_code = json_object_get_int64(jsonValue); - } - else if (jsonKey.compare("start_time") == 0) { - cr.start_time.tv_sec = json_object_get_int64(jsonValue); - } - else if (jsonKey.compare("end_time") == 0) { - cr.finish_time.tv_sec = json_object_get_int64(jsonValue); - } - else if (jsonKey.compare("early_timeout") == 0) { - cr.early_timeout = json_object_get_int64(jsonValue); - } - else if (jsonKey.compare("latency") == 0) { - cr.latency = json_object_get_double(jsonValue); - } - else if (jsonKey.compare("exited_ok") == 0) { - cr.exited_ok = json_object_get_int64(jsonValue); - } + // Direct lookups rather than iterating every key and running it down a chain + // of string comparisons: json-c keeps the members in a hash table, so this is + // one lookup per field instead of comparisons proportional to keys times fields. + json_object *value = nullptr; + if (json_object_object_get_ex(obj, "host_name", &value)) { + cr.host_name = get_json_string_c(value); + } + if (json_object_object_get_ex(obj, "service_description", &value)) { + cr.service_description = get_json_string_c(value); + } + if (json_object_object_get_ex(obj, "output", &value)) { + output = get_json_string_c(value); + } + if (json_object_object_get_ex(obj, "long_output", &value)) { + longOutput = get_json_string_c(value); + } + if (json_object_object_get_ex(obj, "perf_data", &value)) { + perfData = get_json_string_c(value); + } + if (json_object_object_get_ex(obj, "check_type", &value)) { + cr.check_type = json_object_get_int64(value); + } + if (json_object_object_get_ex(obj, "return_code", &value)) { + cr.return_code = json_object_get_int64(value); + } + if (json_object_object_get_ex(obj, "start_time", &value)) { + cr.start_time.tv_sec = json_object_get_int64(value); + } + if (json_object_object_get_ex(obj, "end_time", &value)) { + cr.finish_time.tv_sec = json_object_get_int64(value); + } + if (json_object_object_get_ex(obj, "early_timeout", &value)) { + cr.early_timeout = json_object_get_int64(value); + } + if (json_object_object_get_ex(obj, "latency", &value)) { + cr.latency = json_object_get_double(value); + } + if (json_object_object_get_ex(obj, "exited_ok", &value)) { + cr.exited_ok = json_object_get_int64(value); } cr.output = BuildCheckOutput(output, longOutput, perfData); From 4aeb9dbcf34012a9bc47c4307347c07bc76754d1 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 20:54:22 +0200 Subject: [PATCH 12/28] Measure the incidental costs from the plan's point 6 Numbers before deciding anything, and in this case they mostly argue against acting: QueueIds() copied (auto) 357 ns/op QueueIds() by reference 1 ns/op Log line, level discards it 46 ns/op Log line, level emits it 106 ns/op ToString SPACED (current) 1192 ns/op 571 B ToString PLAIN 1130 ns/op 519 B (9.1% smaller) bulk of 100 results 63318 B -> 58014 B (8.4%, 5304 B saved) The map copy and the discarded log line looked like waste, and they are, but both sit in FlushBulkQueue, which runs once per bulk flush - by default every ten seconds or every 200 messages, not per message. 357 ns and 46 ns at that rate are nothing. Neither is worth changing for performance; the map copy is still worth a const reference on its own merits, as plain tidiness. Serialisation is the only one with a real effect, and it is more about size than time: 5 percent less CPU, but 8 to 9 percent fewer bytes on every message the broker sends. That one changes the wire format, so it is left alone here. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- tests/bench.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/bench.cpp b/tests/bench.cpp index ee0e695..8035d9f 100644 --- a/tests/bench.cpp +++ b/tests/bench.cpp @@ -187,6 +187,80 @@ int main() { sink += static_cast(copy.size()); }); + std::cout << "\n--- point 6: incidental costs ---" << std::endl; + + // The map copy that FlushBulkQueue does on every flush, only to log a queue name. + Bench("QueueIds() copied (auto)", 200000, [&] { + auto QueueId = QueueNameHandler::Instance().QueueIds(); + sink += static_cast(QueueId.size()); + }); + Bench("QueueIds() by reference", 200000, [&] { + const auto &QueueId = QueueNameHandler::Instance().QueueIds(); + sink += static_cast(QueueId.size()); + }); + + // LogStream formats into its stringstream before it knows whether the level keeps the + // message, so a suppressed Info line still costs the full formatting. + { + LogStream discarding; + discarding.SetLogLevel(LogLevel::Warning); // Info is dropped + LogStream emitting; + emitting.SetLogLevel(LogLevel::Info); + Bench("Log line, level discards it", 200000, [&] { + discarding << "Sent bulk message (" << 200ul << ") for queue " + << std::string("statusngin_servicechecks") << LogLevel::Info; + }); + Bench("Log line, level emits it", 200000, [&] { + capturedLogs.clear(); + emitting << "Sent bulk message (" << 200ul << ") for queue " + << std::string("statusngin_servicechecks") << LogLevel::Info; + }); + } + + // Serialisation: what json-c's default SPACED costs against PLAIN. + { + NagiosServiceCheckData msg(&scd); + json_object *raw = msg.GetDataCopy(); + const char *spaced = json_object_to_json_string_ext(raw, JSON_C_TO_STRING_SPACED); + size_t spacedLen = std::strlen(spaced); + const char *plain = json_object_to_json_string_ext(raw, JSON_C_TO_STRING_PLAIN); + size_t plainLen = std::strlen(plain); + + Bench("ToString SPACED (current)", 200000, [&] { + sink += static_cast(std::strlen(json_object_to_json_string_ext(raw, JSON_C_TO_STRING_SPACED))); + }); + Bench("ToString PLAIN", 200000, [&] { + sink += static_cast(std::strlen(json_object_to_json_string_ext(raw, JSON_C_TO_STRING_PLAIN))); + }); + std::cout << " serialised size: SPACED " << spacedLen << " B, PLAIN " << plainLen << " B (" + << std::setprecision(1) << (100.0 - 100.0 * static_cast(plainLen) / + static_cast(spacedLen)) + << "% smaller)" << std::endl; + json_object_put(raw); + } + + // The same comparison on a realistic bulk payload, which is what actually travels. + { + json_object *fixture = json_tokener_parse(ReadFixture("statusngin_servicechecks.json").c_str()); + json_object *messages = nullptr; + json_object_object_get_ex(fixture, "messages", &messages); + json_object *one = json_object_array_get_idx(messages, 0); + json_object *arr = json_object_new_array(); + for (int i = 0; i < 100; ++i) { + json_object_array_add(arr, json_object_get(one)); + } + json_object *root = json_object_new_object(); + json_object_object_add(root, "messages", arr); + size_t sp = std::strlen(json_object_to_json_string_ext(root, JSON_C_TO_STRING_SPACED)); + size_t pl = std::strlen(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PLAIN)); + std::cout << " bulk of 100: SPACED " << sp << " B, PLAIN " << pl << " B (" + << std::setprecision(1) << (100.0 - 100.0 * static_cast(pl) / + static_cast(sp)) + << "% smaller, " << (sp - pl) << " B saved)" << std::endl; + json_object_put(root); + json_object_put(fixture); + } + std::cout << "\nbulk payload of 100: " << bulk100.size() << " bytes, " << "processed check results: " << processedCheckResults << std::endl; return sink == 0x7fffffff ? 1 : 0; // keep the sink alive From ea4c2232200a3c896709a05acd1fd1e71bb3398d Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 21:03:58 +0200 Subject: [PATCH 13/28] Serialise messages without insignificant whitespace json_object_to_json_string() defaults to JSON_C_TO_STRING_SPACED, which puts a space after every colon and comma. Switching to PLAIN produces the same JSON with 8 to 9 percent fewer bytes: a single service check message goes from 571 to 519 bytes, a bulk of 100 check results from 63318 to 58014. Size is the point rather than CPU here - serialising is only 5 percent cheaper - but those bytes are network, queue memory, and parsing work on the consumer side, where 78 percent of the receive path is json-c parsing. Safe for the existing consumers: this is a whitespace only difference and both the Go worker (encoding/json) and the PHP worker (json_decode) parse rather than match on the raw text, and no tests depend on the exact bytes. Confirmed with the project maintainer before making the change. Verified against a running naemon: messages on the queue are compact, still parse as JSON, and a check_result submitted over gearman is still applied. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- README.md | 7 +++++++ src/NagiosObject.h | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3fa979c..acfa440 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,13 @@ The reason is that the underlying naemon/nagios `nebstruct_*` structs simply hav duplication is kept for backwards compatibility, so that existing consumers do not break. Do not read a separate long plugin output out of these five event types. +### Compact JSON + +Messages are serialised without insignificant whitespace (`{"a":1}` rather than +`{"a": 1, ...}`), which is 8 to 9 percent fewer bytes per message. This is a +whitespace only difference - the JSON is unchanged and any conformant parser +reads it identically. Do not match on the raw payload text. + ### `timestamp` on core restart The `RestartData` message carries the unix timestamp of the restart: diff --git a/src/NagiosObject.h b/src/NagiosObject.h index 522ae81..0df1b78 100644 --- a/src/NagiosObject.h +++ b/src/NagiosObject.h @@ -30,7 +30,10 @@ namespace statusengine { } std::string ToString() { - return std::string(json_object_to_json_string(data)); + // PLAIN rather than json_object_to_json_string()'s SPACED default: same JSON, + // 8 to 9 percent fewer bytes on every message the broker sends, which is + // network, queue memory and parsing work on the consumer side. + return std::string(json_object_to_json_string_ext(data, JSON_C_TO_STRING_PLAIN)); } /** From 60de63076627658234a7bde2e6835f3485c0e952 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 21:05:19 +0200 Subject: [PATCH 14/28] Take the queue name tables by reference instead of copying them QueueNameHandler::QueueIds() and QueueNames() return a const reference, but four call sites captured the result with plain auto and copied the whole 23 entry map of strings - 357 ns against 1 ns for a reference. This is tidiness, not performance. The measurement in the previous commit shows why: the only one on a repeating path sits in FlushBulkQueue, which runs once per bulk flush, by default every ten seconds or every 200 messages. Two of the others are error paths that never run in normal operation, and the fourth runs at startup. None of it is worth a change on its own, but leaving a needless copy in place once it is known is worse than the one word it takes to fix. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- src/Configuration.h | 2 +- src/MessageHandler/GearmanClient.cpp | 2 +- src/MessageHandler/MessageHandler.h | 2 +- src/MessageHandler/RabbitmqClient.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Configuration.h b/src/Configuration.h index 654bd43..e7548e4 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -34,7 +34,7 @@ namespace statusengine { bool InitLoad(const toml::value &tbl) { for (auto &tableEntry : tbl.as_table()) { - auto QueueName = QueueNameHandler::Instance().QueueNames(); + const auto &QueueName = QueueNameHandler::Instance().QueueNames(); auto qName = QueueName.find(tableEntry.first); if (qName != QueueName.end()) { try { diff --git a/src/MessageHandler/GearmanClient.cpp b/src/MessageHandler/GearmanClient.cpp index 4591ddc..9a7c45a 100644 --- a/src/MessageHandler/GearmanClient.cpp +++ b/src/MessageHandler/GearmanClient.cpp @@ -104,7 +104,7 @@ namespace statusengine { void GearmanClient::SendMessage(Queue queue, const std::string &message) { auto queueNameIt = queueNames->find(queue); if (queueNameIt == queueNames->end()) { - auto QueueId = QueueNameHandler::Instance().QueueIds(); + const auto &QueueId = QueueNameHandler::Instance().QueueIds(); se->Log() << "No gearman queue configured for " << QueueId.at(queue) << ", dropping message" << LogLevel::Error; return; diff --git a/src/MessageHandler/MessageHandler.h b/src/MessageHandler/MessageHandler.h index 75bded9..2360926 100644 --- a/src/MessageHandler/MessageHandler.h +++ b/src/MessageHandler/MessageHandler.h @@ -436,7 +436,7 @@ namespace statusengine { handler->SendMessage(queue, msg); } - auto QueueId = QueueNameHandler::Instance().QueueIds(); + const auto &QueueId = QueueNameHandler::Instance().QueueIds(); se.Log() << "Sent bulk message (" << bulkMessages.size() << ") for queue " << QueueId.at(queue) << LogLevel::Info; diff --git a/src/MessageHandler/RabbitmqClient.cpp b/src/MessageHandler/RabbitmqClient.cpp index ea40178..7842596 100644 --- a/src/MessageHandler/RabbitmqClient.cpp +++ b/src/MessageHandler/RabbitmqClient.cpp @@ -230,7 +230,7 @@ namespace statusengine { void RabbitmqClient::SendMessage(Queue queue, const std::string &message) { auto queueNameIt = queueNames->find(queue); if (queueNameIt == queueNames->end()) { - auto QueueId = QueueNameHandler::Instance().QueueIds(); + const auto &QueueId = QueueNameHandler::Instance().QueueIds(); se->Log() << "No rabbitmq queue configured for " << QueueId.at(queue) << ", dropping message" << LogLevel::Error; return; From 01be7e83f45c2d951cf19a9823ce1ec9035a9378 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 21:14:19 +0200 Subject: [PATCH 15/28] Document the performance work in the README The measurements only existed in commit messages, which is no use to somebody deciding whether to upgrade. This puts the before and after numbers where they can be found, together with what they do and do not mean. The before/after pair was measured again for this, directly back to back on one machine, rather than assembled from the individual commits. That corrected one figure along the way: Latin-1 input is unchanged at 4298 -> 4248 ns, not the two percent slower an earlier commit message claimed - that was measurement noise. The caveats are part of the point, not a disclaimer: single runs that vary by a few percent, one machine and compiler, an encoder gain that only applies when the output already is UTF-8, and roughly 320 of the remaining 352 us in the bulk receive path being json-c parsing that none of this touched. A section claiming 83x without saying when it applies would not survive contact with a sceptical reader. The benchmark ships with the sources, so the numbers can be checked rather than believed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/README.md b/README.md index acfa440..c8a5a10 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,65 @@ The value comes from naemon's own event time. Consumers should keep treating a missing field or a `0` as "not set" and fall back to their own clock - older brokers did not populate it. +## Performance + +The numbers below are the broker's own share of the work - the part that runs inside +naemon's single threaded event loop, where every microsecond is one the core does not +spend scheduling checks. naemon's own processing is not included. + +Measured on the same machine directly before and after the changes, `--buildtype=release`, +GCC 11.4 on x86-64: + +| | before | after | | +|---|---|---|---| +| Encoder, ASCII output | 286 ns | 48 ns | 6.0x | +| Encoder, UTF-8 output | 3997 ns | 48 ns | 83x | +| Encoder, Latin-1 output | 4298 ns | 4248 ns | unchanged | +| Building a service check message | 3449 ns | 2662 ns | -23% | +| Receiving one check result | 2550 ns | 2112 ns | -17% | +| Receiving a bulk of 100 | 435 us | 352 us | -19% | + +Messages also got smaller, which is network traffic, queue memory and parsing work on the +consumer side: + +| | before | after | | +|---|---|---|---| +| One service check message | 571 B | 519 B | -9.1% | +| Bulk of 100 check results | 63318 B | 58014 B | -8.4% | + +Where this comes from: + +* Charset detection is skipped when the output already is valid UTF-8, which ASCII is a + subset of. That covers virtually all real plugin output; previously every string went + through uchardet's statistical detection. +* The fields of an incoming check result are looked up directly instead of iterating every + key and running it down a chain of string comparisons. +* JSON is serialised without insignificant whitespace. + +### What these numbers do not say + +* They are micro benchmarks from one machine and one compiler. The ratios are the useful + part, the absolute values will differ on your hardware. +* Each timing is a single run. Repeated runs vary by a few percent, so read the timings as + the order of magnitude they are, not as exact figures. The message sizes are + deterministic and do reproduce exactly. +* The encoder gain depends on the input. Output that genuinely is not UTF-8 still goes + through detection and conversion and is no faster - as the Latin-1 row shows. +* On the receive path most of what remains is json-c parsing, roughly 320 us of the 352 us + in the bulk case. That is not broker code and none of this changed it. + +### Reproducing + +The benchmark ships with the sources: + +```bash +meson setup --buildtype=release -Dtests=true build +ninja -C build +./build/tests/statusengine-bench +``` + +`--buildtype=release` is required; at `-O0` the numbers are meaningless. + ## Developer build + test If you want to build and test the broker, you can use the docker-compose configuration: From 9185a3617a2da747aaa9aeb8f6c90f7cab034d1b Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 21:19:58 +0200 Subject: [PATCH 16/28] Add a changelog There was none, so the only way to find out what changed between releases was reading the commit log. This covers everything since v4.0.4, grouped by what it means for someone deciding whether to upgrade rather than by which commit it came from. Kept under [Unreleased]: which version number this becomes is a release decision, and renaming the heading is a one line change when that is made. The correctness fixes lead, because they are the stronger argument than the performance work - RabbitMQ 4 support in particular, where the broker previously could not connect at all, and a set of memory errors in a module that runs inside the monitoring core's address space. Every figure was checked against its measurement rather than copied from memory, which corrected one date along the way: cmake was dropped in 2020, not 2019. The claim that the old CI had not run since is gone too - the configuration is demonstrably stale, but its run history is not something this repository knows. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- CHANGELOG.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 ++ 2 files changed, 79 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5777b2a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,77 @@ +# Changelog + +All notable changes to the Statusengine broker module are recorded here. The format +follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +## [Unreleased] + +Everything since v4.0.4. The headline reasons to upgrade are the RabbitMQ 4 support and a +set of memory errors that could take the monitoring core down with them. + +### Fixed + +- **RabbitMQ 4 support.** Queues and the exchange are now declared durable by default. + RabbitMQ 4 refuses to declare a queue that is neither durable nor exclusive, so the + broker previously failed to connect at all against a 4.x server; 3.13 warns about it. + The options existed before but defaulted to off. This stores the queue and exchange + *definitions* on disk, not the events - messages are still published transient, so the + queues buffer in RAM and a broker restart still empties them, as before. +- **Character set conversion did the opposite of its job.** An inverted comparison meant + non-UTF-8 plugin output was passed through unconverted, while output that already was + UTF-8 went through a conversion. The same function also freed a string owned by + uchardet, freed a buffer through a pointer iconv had advanced past its start, and used + iconv's return value as if it were a length. Any of those can corrupt the heap of the + monitoring core. +- **Mismatched allocators on incoming check results.** Strings handed to naemon were + allocated with `new[]` but released by naemon with `free()`. Also fixes a leak of + `perf_data` when a message carried no `output`. +- **RabbitMQ connection leak.** Every failed reconnect leaked roughly 9 kB. Since a send + retries the connection for every message, this grew without bound whenever the broker + was unreachable. +- **Crash on startup failure.** If one message handler failed to connect, destroying the + remaining ones dereferenced a connection that had never been opened. +- **The worker loop could freeze naemon.** A handler reporting more work without having + processed anything - which the gearman worker does when its socket would block - spun + the loop forever inside naemon's event loop, so the core stopped scheduling entirely. + The gearman poll that goes with it was also unbounded and is now capped. +- **Log levels were wrong in both directions.** At `Level = Error` warnings were still + written, and at `Level = Info` - the most verbose setting - warnings were dropped. +- The module title was overwritten by the copyright string in the naemon module info. + +### Changed + +- **Messages are serialised without insignificant whitespace**, which is 8 to 9 percent + fewer bytes. This is a whitespace only difference; the JSON is unchanged and any + conformant parser reads it identically. Do not match on the raw payload text. +- **`RestartData` now carries the restart timestamp.** Consumers should keep treating a + missing value or a `0` as "not set", as older brokers do not send it. +- **The module installs into `/lib`** again. The move to meson had changed this to + a multiarch subdirectory, where naemon would not find it at the documented path. +- Built as C++17, and the vendored toml11 was updated from the 2018 version to 4.4.0. +- The reported module version now comes from the build instead of a hardcoded string. + Version numbers had drifted apart across four places. +- Nagios support is a deprecation candidate. It still builds and is still tested in CI; if + you rely on it, please say so in an issue. + +### Added + +- A unit test suite, run in CI both plain and under the address and undefined behaviour + sanitizers. The recorded message formats are pinned against fixtures, so an accidental + change to the wire format fails the build. +- Micro benchmarks for the message paths, see the Performance section in the README. +- A GitHub Actions workflow covering current Ubuntu and Debian releases and all build + options. The configuration it replaces still invoked cmake, which the project dropped in + 2020, was GitLab CI in a repository hosted on GitHub, and targeted distributions that + have all reached end of life. + +### Performance + +Measured with the included benchmark; see the README for the caveats that go with these +numbers. + +- Character set handling on output that already is UTF-8 or ASCII: 6x to 83x faster, + because detection is skipped entirely. Output that genuinely is not UTF-8 is unchanged. +- Building a service check message: 23 percent faster. +- Receiving check results: 17 to 19 percent faster overall. Most of what remains is JSON + parsing inside json-c. +- Messages are 8 to 9 percent smaller. diff --git a/README.md b/README.md index c8a5a10..e03729c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ It will grab all status information, encode them as JSON, and put them into the Visit the [documentation](https://statusengine.org/broker/) for more information about Statusengine C++ Broker Module +See [CHANGELOG.md](CHANGELOG.md) for what changed between releases. + ## Build and Installation Please compile and install the newest version of naemon or nagios. We need the header files for building the broker. From 65441ef9dbccd066711da528606491d5b3ba104a Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 21:51:55 +0200 Subject: [PATCH 17/28] Report a gearman outage on an interval instead of per message An unreachable job server made SendMessage log one line per message. On the demo installation that was 254 lines in 60 seconds; the repeat interval added later measured 352 failed sends in five minutes. It scales with the number of checks, so a real installation would bury its own log. An outage is now reported when it starts, again at most every five minutes while it lasts, and once when it recovers, each with the number of failed attempts. Worker side failures get the same treatment and are named through gearman_strerror() rather than printed as a bare number - the old message read "Unknown gearman worker error: 12" for what is GEARMAN_GETADDRINFO, and the GEARMAN_NO_ACTIVE_FDS case it did name is only one of several connection errors. The interval matters more than it looks. Logging only the first failure and then staying silent until recovery was the first attempt, and it was wrong: it would have hidden an outage that never ends. Which is not hypothetical - see below. The recovery line counts "failed send(s)", not "messages lost", deliberately. What is counted is the attempts libgearman reported an error for, which is not necessarily everything that failed to reach the job server, and a number that looks authoritative should be one. Found while testing this, not caused by it: the gearman client never recovers from a job server restart. gearman_client_add_servers() runs once at startup and SendMessage() has no reconnect path, unlike RabbitmqClient::SendMessage which calls Connect(). After the server came back the worker reconnected but the client kept failing every send, with no messages delivered. Left alone here because it is a separate change; the five minute repeat at least keeps it visible. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- CHANGELOG.md | 6 +++ src/MessageHandler/GearmanClient.cpp | 72 +++++++++++++++++++++++++--- src/MessageHandler/GearmanClient.h | 16 +++++++ 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5777b2a..1aef7f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,12 @@ set of memory errors that could take the monitoring core down with them. - **Log levels were wrong in both directions.** At `Level = Error` warnings were still written, and at `Level = Info` - the most verbose setting - warnings were dropped. - The module title was overwritten by the copyright string in the naemon module info. +- **An unreachable gearman job server no longer floods the log.** Every failed message + produced its own line - measured at over 350 in five minutes on a small test + installation, and it scales with the number of checks. An outage is now reported when it + starts and then at most every five minutes while it lasts, with a count of failed + attempts, and once more when it recovers. Worker side errors are named via + `gearman_strerror()` instead of being reported as "Unknown gearman worker error: 12". ### Changed diff --git a/src/MessageHandler/GearmanClient.cpp b/src/MessageHandler/GearmanClient.cpp index 9a7c45a..f75b022 100644 --- a/src/MessageHandler/GearmanClient.cpp +++ b/src/MessageHandler/GearmanClient.cpp @@ -9,6 +9,21 @@ namespace statusengine { namespace { /// Upper bound in milliseconds for a single gearman_worker_wait() poll. const int gearmanPollTimeoutMs = 10; + + /// How often an ongoing outage is repeated in the log. Often enough that a broken + /// job server cannot go unnoticed, rarely enough that it does not drown the log: + /// once per message was around 4 lines a second even on a tiny installation. + const time_t outageReportIntervalSeconds = 300; + + /// True if this condition should be written to the log now. + bool ShouldReport(time_t &lastReport, unsigned long occurrences) { + const time_t now = std::time(nullptr); + if (occurrences == 1 || now - lastReport >= outageReportIntervalSeconds) { + lastReport = now; + return true; + } + return false; + } } // namespace class GearmanWorkerContext { @@ -25,7 +40,9 @@ namespace statusengine { } GearmanClient::GearmanClient(IStatusengine *se, std::shared_ptr cfg) - : MessageHandler(se), cfg(cfg), client(nullptr), worker(nullptr) { + : MessageHandler(se), cfg(cfg), client(nullptr), worker(nullptr), failedSends(0), + failedWorkerCalls(0), lastWorkerError(GEARMAN_SUCCESS), lastSendErrorReport(0), + lastWorkerErrorReport(0) { queueNames = cfg->GetQueueNames(); if (!queueNames->empty()) { @@ -112,8 +129,49 @@ namespace statusengine { auto ret = gearman_client_do_background(client, queueNameIt->second.c_str(), nullptr, message.c_str(), message.length(), nullptr); if (!gearman_success(ret)) { - se->Log() << "Could not write message to gearman queue: " << gearman_client_error(client) + // An unreachable job server fails every single message. Logging each one buries + // the rest of the log and can outpace the events themselves, so report the + // outage once and count what it costs. + if (ShouldReport(lastSendErrorReport, ++failedSends)) { + auto error = gearman_client_error(client); + se->Log() << "Could not write message to gearman queue (" << failedSends + << " failed send(s) so far): " << (error != nullptr ? error : "unknown error") + << LogLevel::Error; + } + } + else if (failedSends > 0) { + // Deliberately "failed send(s)" and not "messages lost": this counts the + // attempts libgearman reported an error for, which is not necessarily + // everything that failed to reach the job server. + se->Log() << "Gearman queue is writable again after " << failedSends << " failed send(s)" + << LogLevel::Error; + failedSends = 0; + lastSendErrorReport = 0; + } + } + + void GearmanClient::ReportWorkerError(gearman_return_t ret) { + ++failedWorkerCalls; + // A job server that stays down would otherwise produce a line per worker tick, + // around the clock. Report a new cause immediately, and an ongoing one on an + // interval so it cannot be silently forgotten. + if (ret != lastWorkerError) { + failedWorkerCalls = 1; + lastWorkerError = ret; + } + if (ShouldReport(lastWorkerErrorReport, failedWorkerCalls)) { + se->Log() << "Gearman worker error (" << failedWorkerCalls << " failed attempt(s) so far): " + << gearman_strerror(ret) << LogLevel::Error; + } + } + + void GearmanClient::ReportWorkerRecovered() { + if (failedWorkerCalls > 0) { + se->Log() << "Gearman worker is working again after " << failedWorkerCalls << " failed attempt(s)" << LogLevel::Error; + failedWorkerCalls = 0; + lastWorkerError = GEARMAN_SUCCESS; + lastWorkerErrorReport = 0; } } @@ -131,8 +189,10 @@ namespace statusengine { case GEARMAN_SUCCESS: ++counter; moreJobs = true; + ReportWorkerRecovered(); break; case GEARMAN_NO_JOBS: + ReportWorkerRecovered(); break; case GEARMAN_IO_WAIT: // Not just a sleep: gearman_wait() runs the poll() that refreshes the @@ -143,11 +203,11 @@ namespace statusengine { gearman_worker_wait(worker); moreJobs = true; break; - case GEARMAN_NO_ACTIVE_FDS: - se->Log() << "Gearman worker is not connected to server" << LogLevel::Error; - break; default: - se->Log() << "Unknown gearman worker error: " << ret << LogLevel::Error; + // Every remaining code means the worker could not do its job - + // no servers, name resolution, a lost connection. gearman_strerror() + // names them, which the previous "Unknown ... : 12" did not. + ReportWorkerError(ret); } } return moreJobs; diff --git a/src/MessageHandler/GearmanClient.h b/src/MessageHandler/GearmanClient.h index 513b5e4..eff2978 100644 --- a/src/MessageHandler/GearmanClient.h +++ b/src/MessageHandler/GearmanClient.h @@ -2,7 +2,10 @@ #include "MessageHandler.h" +#include + #include "libgearman-1.0/gearman.h" +#include "libgearman-1.0/strerror.h" namespace statusengine { class GearmanConfiguration; @@ -29,5 +32,18 @@ namespace statusengine { std::shared_ptr> queueNames; std::shared_ptr> workerQueueNames; std::map> workerContexts; + + // An unreachable job server is one condition, not one condition per message. These + // track it so an outage is reported once at each end instead of on every attempt. + unsigned long failedSends; + unsigned long failedWorkerCalls; + gearman_return_t lastWorkerError; + time_t lastSendErrorReport; + time_t lastWorkerErrorReport; + + /// Reports a worker error once per distinct cause, counting the rest. + void ReportWorkerError(gearman_return_t ret); + /// Reports that the worker is healthy again, if it previously was not. + void ReportWorkerRecovered(); }; } // namespace statusengine From 2ae328137130906e06f0539b990afd0415bd3e20 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 22:01:01 +0200 Subject: [PATCH 18/28] Reconnect the gearman client after a job server restart The client never recovered from one. gearman_client_add_servers() ran once during Connect() and SendMessage() had no recovery path at all, so after the job server had been restarted every send kept failing and the broker delivered nothing to gearman until naemon itself was restarted. The worker side was fine, which is what made this easy to miss and why it was only noticed while testing the logging change: libgearman resets the worker's connection internally on a connect error, but nothing does that for the client and there is no public reset for it either. Removing and re-adding the server list is what forces a fresh connection. Rate limited to once every five seconds, so a job server that stays down does not get a reconnect attempt per message. Measured on a running stack, stopping and restarting the gearman container: before no recovery at all, still zero messages delivered after three minutes after recovered 11 seconds after the server returned, "writable again after 12 failed send(s)", and the queues started filling again Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- CHANGELOG.md | 6 ++++++ src/MessageHandler/GearmanClient.cpp | 27 ++++++++++++++++++++++++++- src/MessageHandler/GearmanClient.h | 4 ++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aef7f4..6a74799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,12 @@ set of memory errors that could take the monitoring core down with them. - **Log levels were wrong in both directions.** At `Level = Error` warnings were still written, and at `Level = Info` - the most verbose setting - warnings were dropped. - The module title was overwritten by the copyright string in the naemon module info. +- **The gearman client now reconnects after a job server restart.** It never did: + `gearman_client_add_servers()` ran once at startup and a failed send had no recovery + path, so once the job server had been restarted the broker stopped delivering to gearman + until naemon itself was restarted. The worker side was unaffected, which made this easy + to miss - libgearman resets the worker's connection internally but does nothing for the + client. - **An unreachable gearman job server no longer floods the log.** Every failed message produced its own line - measured at over 350 in five minutes on a small test installation, and it scales with the number of checks. An outage is now reported when it diff --git a/src/MessageHandler/GearmanClient.cpp b/src/MessageHandler/GearmanClient.cpp index f75b022..0e646ec 100644 --- a/src/MessageHandler/GearmanClient.cpp +++ b/src/MessageHandler/GearmanClient.cpp @@ -15,6 +15,10 @@ namespace statusengine { /// once per message was around 4 lines a second even on a tiny installation. const time_t outageReportIntervalSeconds = 300; + /// How often a broken client connection is rebuilt. Short enough to recover from a + /// job server restart quickly, long enough not to do it for every single message. + const time_t clientReconnectIntervalSeconds = 5; + /// True if this condition should be written to the log now. bool ShouldReport(time_t &lastReport, unsigned long occurrences) { const time_t now = std::time(nullptr); @@ -42,7 +46,7 @@ namespace statusengine { GearmanClient::GearmanClient(IStatusengine *se, std::shared_ptr cfg) : MessageHandler(se), cfg(cfg), client(nullptr), worker(nullptr), failedSends(0), failedWorkerCalls(0), lastWorkerError(GEARMAN_SUCCESS), lastSendErrorReport(0), - lastWorkerErrorReport(0) { + lastWorkerErrorReport(0), lastClientReconnect(0) { queueNames = cfg->GetQueueNames(); if (!queueNames->empty()) { @@ -132,6 +136,7 @@ namespace statusengine { // An unreachable job server fails every single message. Logging each one buries // the rest of the log and can outpace the events themselves, so report the // outage once and count what it costs. + ReconnectClient(); if (ShouldReport(lastSendErrorReport, ++failedSends)) { auto error = gearman_client_error(client); se->Log() << "Could not write message to gearman queue (" << failedSends @@ -150,6 +155,26 @@ namespace statusengine { } } + void GearmanClient::ReconnectClient() { + const time_t now = std::time(nullptr); + if (now - lastClientReconnect < clientReconnectIntervalSeconds) { + return; + } + lastClientReconnect = now; + + // libgearman leaves a failed client connection failed. The worker recovers because + // libgearman resets its universal internally on a connect error (worker.cc), but + // nothing does that for the client, so without this the broker stops delivering to + // gearman until naemon is restarted. Dropping and re-adding the server list forces + // a fresh connection on the next send. + gearman_client_remove_servers(client); + auto ret = gearman_client_add_servers(client, cfg->URL.c_str()); + if (!gearman_success(ret)) { + se->Log() << "Could not re-add gearman server " << cfg->URL << ": " << gearman_client_error(client) + << LogLevel::Error; + } + } + void GearmanClient::ReportWorkerError(gearman_return_t ret) { ++failedWorkerCalls; // A job server that stays down would otherwise produce a line per worker tick, diff --git a/src/MessageHandler/GearmanClient.h b/src/MessageHandler/GearmanClient.h index eff2978..855a531 100644 --- a/src/MessageHandler/GearmanClient.h +++ b/src/MessageHandler/GearmanClient.h @@ -40,10 +40,14 @@ namespace statusengine { gearman_return_t lastWorkerError; time_t lastSendErrorReport; time_t lastWorkerErrorReport; + time_t lastClientReconnect; /// Reports a worker error once per distinct cause, counting the rest. void ReportWorkerError(gearman_return_t ret); /// Reports that the worker is healthy again, if it previously was not. void ReportWorkerRecovered(); + /// Rebuilds the client's server connection after a failed send, at most now and + /// then. libgearman does not do this on its own for the client. + void ReconnectClient(); }; } // namespace statusengine From 72aa758e600176bda664bc43b93bd8ed470c714a Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 22:23:15 +0200 Subject: [PATCH 19/28] Update the vendored naemon and nagios headers The headers that let the module build without an installed monitoring core were six years old: naemon 1.0.6 from 2019 and nagios 4.4.5. They are what CI builds against and what the test suite compiles with, so they were quietly defining which API the project was verified against. Now naemon 1.5.2 and nagios 4.5.14, taken from a real build of each rather than copied by hand - the nagios set in particular needs configure to have run, since locations.h, iobroker.h and snprintf.h are generated. naemon.pc is updated to match. Nothing in the broker broke, which is the interesting part: six years of API drift and every build variant still compiles warning free. The module only touches the NEB structures and those stayed compatible. One rename shows up in the file list, nagios lib/pqueue.h became prqueue.h, and nagios gained the configure generated config.h, config_pwd.h and ignored_config.h. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- devtools/ci/naemon.pc | 2 +- devtools/ci/naemon/broker.h | 9 +- devtools/ci/naemon/buildopts.h | 12 +- devtools/ci/naemon/checks.h | 3 + devtools/ci/naemon/checks_host.h | 3 + devtools/ci/naemon/checks_service.h | 3 + devtools/ci/naemon/commands.h | 4 + devtools/ci/naemon/comments.h | 25 +- devtools/ci/naemon/common.h | 12 +- devtools/ci/naemon/defaults.h | 6 +- devtools/ci/naemon/downtime.h | 2 +- devtools/ci/naemon/events.h | 1 + devtools/ci/naemon/globals.h | 12 +- devtools/ci/naemon/lib/kvvec.h | 2 +- devtools/ci/naemon/lib/lnae-utils.h | 5 + devtools/ci/naemon/lib/nsutils.h | 52 +++ devtools/ci/naemon/lib/runcmd.h | 5 +- devtools/ci/naemon/lib/worker.h | 1 + devtools/ci/naemon/logging.h | 56 +-- devtools/ci/naemon/macros.h | 12 +- devtools/ci/naemon/naemon.h | 10 - devtools/ci/naemon/nebcallbacks.h | 1 + devtools/ci/naemon/nebmods.h | 2 +- devtools/ci/naemon/nebmodules.h | 2 +- devtools/ci/naemon/nebstructs.h | 11 + devtools/ci/naemon/notifications.h | 3 +- devtools/ci/naemon/objectlist.h | 9 + devtools/ci/naemon/objects_host.h | 15 +- devtools/ci/naemon/objects_service.h | 19 +- devtools/ci/naemon/objects_servicegroup.h | 4 +- devtools/ci/naemon/objects_timeperiod.h | 3 +- devtools/ci/naemon/query-handler.h | 1 + devtools/ci/naemon/shared.h | 5 +- devtools/ci/naemon/utils.h | 1 + devtools/ci/naemon/workers.h | 8 +- devtools/ci/nagios/archiveutils.h | 2 +- devtools/ci/nagios/cgiutils.h | 20 +- devtools/ci/nagios/common.h | 7 +- devtools/ci/nagios/config.h | 362 ++++++++++++++++++ devtools/ci/nagios/config_pwd.h | 23 ++ devtools/ci/nagios/downtime.h | 6 +- devtools/ci/nagios/ignored_config.h | 287 ++++++++++++++ devtools/ci/nagios/lib/fanout.h | 2 +- devtools/ci/nagios/lib/iocache.h | 2 +- devtools/ci/nagios/lib/libnagios.h | 2 +- .../ci/nagios/lib/{pqueue.h => prqueue.h} | 68 ++-- devtools/ci/nagios/lib/squeue.h | 10 +- devtools/ci/nagios/nagios.h | 51 +-- devtools/ci/nagios/netutils.h | 12 + devtools/ci/nagios/objects.h | 10 +- devtools/ci/nagios/shared.h | 2 + 51 files changed, 997 insertions(+), 190 deletions(-) create mode 100644 devtools/ci/nagios/config.h create mode 100644 devtools/ci/nagios/config_pwd.h create mode 100644 devtools/ci/nagios/ignored_config.h rename devtools/ci/nagios/lib/{pqueue.h => prqueue.h} (69%) diff --git a/devtools/ci/naemon.pc b/devtools/ci/naemon.pc index 5ace274..b85d149 100644 --- a/devtools/ci/naemon.pc +++ b/devtools/ci/naemon.pc @@ -10,6 +10,6 @@ naemon_group=naemon Name: Naemon Description: Naemon network monitoring -Version: 1.0.6-source +Version: 1.5.2 Libs: -L${libdir} -lnaemon -ldl -lm -lglib-2.0 Cflags: -I${includedir} -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include \ No newline at end of file diff --git a/devtools/ci/naemon/broker.h b/devtools/ci/naemon/broker.h index 94b05fc..08a833c 100644 --- a/devtools/ci/naemon/broker.h +++ b/devtools/ci/naemon/broker.h @@ -10,6 +10,7 @@ #include "objects_contact.h" #include "objects_service.h" #include "nebmods.h" +#include "macros.h" /*************** EVENT BROKER OPTIONS *****************/ @@ -27,7 +28,7 @@ #define BROKER_COMMENT_DATA 256 /* DONE */ #define BROKER_DOWNTIME_DATA 512 /* DONE */ #define BROKER_SYSTEM_COMMANDS 1024 /* DONE */ -#define BROKER_OCP_DATA_UNUSED 2048 /* reusable */ +#define BROKER_VAULT_MACROS 2048 /* DONE */ #define BROKER_STATUS_DATA 4096 /* DONE */ #define BROKER_ADAPTIVE_DATA 8192 /* DONE */ #define BROKER_EXTERNALCOMMAND_DATA 16384 /* DONE */ @@ -136,7 +137,7 @@ /****** EVENT FLAGS ************************/ #define NEBFLAG_NONE 0 -#define NEBFLAG_PROCESS_INITIATED 1 /* event was initiated by Nagios process */ +#define NEBFLAG_PROCESS_INITIATED 1 /* event was initiated by Naemon process */ #define NEBFLAG_USER_INITIATED 2 /* event was initiated by a user request */ #define NEBFLAG_MODULE_INITIATED 3 /* event was initiated by an event broker module */ @@ -166,6 +167,7 @@ NAGIOS_BEGIN_DECL +struct kvvec *get_global_store(void); void broker_program_state(int, int, int); void broker_log_data(int, int, int, char *, unsigned long, time_t); int broker_event_handler(int, int, int, int, void *, int, int, struct timeval, struct timeval, double, int, int, int, char *, char *, char *); @@ -189,8 +191,9 @@ void broker_adaptive_contact_data(int, int, int, contact *, int, unsigned long, int broker_external_command(int, int, int, int, time_t, char *, char *); void broker_aggregated_status_data(int, int, int); void broker_retention_data(int, int, int); -void broker_acknowledgement_data(int, int, int, int, void *, char *, char *, int, int, int); +void broker_acknowledgement_data(int, int, int, int, void *, char *, char *, int, int, int, time_t); void broker_statechange_data(int, int, int, int, void *, int, int, int, int); +int broker_vault_macro(char *, char **, int *, nagios_macros *); NAGIOS_END_DECL #endif diff --git a/devtools/ci/naemon/buildopts.h b/devtools/ci/naemon/buildopts.h index e9021f8..dc97243 100644 --- a/devtools/ci/naemon/buildopts.h +++ b/devtools/ci/naemon/buildopts.h @@ -1,9 +1,9 @@ #ifndef NAEMON_BUILDOPTS_H__ #define NAEMON_BUILDOPTS_H__ -#define NAEMON_SYSCONFDIR "/opt/statusengine/etc/naemon" -#define NAEMON_LOCALSTATEDIR "/opt/statusengine/var" -#define NAEMON_LOGDIR "/opt/statusengine/var/log/naemon" -#define NAEMON_LOCKFILE "/opt/statusengine/var/cache/naemon/naemon.pid" -#define NAEMON_GROUP "jdrummer" -#define NAEMON_USER "jdrummer" +#define NAEMON_SYSCONFDIR "/opt/naemon/etc/naemon" +#define NAEMON_LOCALSTATEDIR "/opt/naemon/var" +#define NAEMON_LOGDIR "/opt/naemon/var/log/naemon" +#define NAEMON_LOCKFILE "/opt/naemon/var/cache/naemon/naemon.pid" +#define NAEMON_GROUP "naemon" +#define NAEMON_USER "naemon" #endif diff --git a/devtools/ci/naemon/checks.h b/devtools/ci/naemon/checks.h index 3ccb194..7a5d273 100644 --- a/devtools/ci/naemon/checks.h +++ b/devtools/ci/naemon/checks.h @@ -6,6 +6,7 @@ #endif #include "lib/lnae-utils.h" +#include "objects_timeperiod.h" #include #include @@ -62,6 +63,7 @@ typedef struct check_result { double latency; struct timeval start_time; /* time the service check was initiated */ struct timeval finish_time; /* time the service check was completed */ + int timeout; /* timeout used in this check */ int early_timeout; /* did the service check timeout? */ int exited_ok; /* did the plugin check return okay? */ int return_code; /* plugin return code */ @@ -88,6 +90,7 @@ int process_check_result(check_result *); int delete_check_result_file(char *); int init_check_result(check_result *); int free_check_result(check_result *); /* frees memory associated with a host/service check result */ +time_t get_random_next_timeperiod_slot(time_t, const timeperiod *); NAGIOS_END_DECL diff --git a/devtools/ci/naemon/checks_host.h b/devtools/ci/naemon/checks_host.h index 2fa9308..0424d11 100644 --- a/devtools/ci/naemon/checks_host.h +++ b/devtools/ci/naemon/checks_host.h @@ -28,6 +28,9 @@ int check_host_dependencies(host *hst, int dependency_type); /* adjusts current host check attempt when a check is processed */ int adjust_host_check_attempt(host *hst, int is_active); +/* move next check into a valid check period slot */ +void delay_host_check_till_next_timeperiod_slot(host *); + NAGIOS_END_DECL #endif diff --git a/devtools/ci/naemon/checks_service.h b/devtools/ci/naemon/checks_service.h index a781c6f..3281b61 100644 --- a/devtools/ci/naemon/checks_service.h +++ b/devtools/ci/naemon/checks_service.h @@ -24,6 +24,9 @@ int handle_async_service_check_result(service *, check_result *); /* Immutable, check if service is reachable */ int check_service_dependencies(service *, int); +/* move next check into a valid check period slot */ +void delay_service_check_till_next_timeperiod_slot(service *); + NAGIOS_END_DECL #endif diff --git a/devtools/ci/naemon/commands.h b/devtools/ci/naemon/commands.h index b7539d9..21db023 100644 --- a/devtools/ci/naemon/commands.h +++ b/devtools/ci/naemon/commands.h @@ -10,6 +10,7 @@ #include "objects_contact.h" #include "objects_host.h" #include "objects_service.h" +#include "events.h" NAGIOS_BEGIN_DECL @@ -220,6 +221,9 @@ int shutdown_command_file_worker(void); int disconnect_command_file_worker(void); int command_worker_get_pid(void); +void handle_host_acknowledgement_expire_event(struct nm_event_execution_properties *evprop); /* removes an expired host acknowledgement */ +void handle_service_acknowledgement_expire_event(struct nm_event_execution_properties *evprop); /* removes an expired service acknowledgement */ + NAGIOS_END_DECL #endif diff --git a/devtools/ci/naemon/comments.h b/devtools/ci/naemon/comments.h index ea176a7..0e3bffb 100644 --- a/devtools/ci/naemon/comments.h +++ b/devtools/ci/naemon/comments.h @@ -26,11 +26,6 @@ #define FLAPPING_COMMENT 3 #define ACKNOWLEDGEMENT_COMMENT 4 - -/*************************** CHAINED HASH LIMITS ***************************/ -#define COMMENT_HASHSLOTS 1024 - - /**************************** DATA STRUCTURES ******************************/ NAGIOS_BEGIN_DECL @@ -49,11 +44,11 @@ typedef struct comment { char *service_description; char *author; char *comment_data; + struct comment *prev; struct comment *next; - struct comment *nexthash; } comment; -extern struct comment *comment_list; +extern GHashTable *comment_hashtable; int initialize_comment_data(void); /* initializes comment data */ int add_new_comment(int, int, char *, char *, time_t, char *, char *, int, int, int, time_t, unsigned long *); /* adds a new host or service comment */ @@ -62,29 +57,23 @@ int add_new_service_comment(int, char *, char *, time_t, char *, char *, int, in int delete_comment(int, unsigned long); /* deletes a host or service comment */ int delete_host_comment(unsigned long); /* deletes a host comment */ int delete_service_comment(unsigned long); /* deletes a service comment */ -int delete_all_comments(int, char *, char *); /* deletes all comments for a particular host or service */ -int delete_all_host_comments(char *); /* deletes all comments for a specific host */ +int delete_all_host_comments(struct host *); /* deletes all comments for a specific host */ int delete_host_acknowledgement_comments(struct host *); /* deletes all non-persistent ack comments for a specific host */ -int delete_all_service_comments(char *, char *); /* deletes all comments for a specific service */ +int delete_all_service_comments(struct service *); /* deletes all comments for a specific service */ int delete_service_acknowledgement_comments(struct service *); /* deletes all non-persistent ack comments for a specific service */ struct comment *find_comment(unsigned long, int); /* finds a specific comment */ struct comment *find_service_comment(unsigned long); /* finds a specific service comment */ struct comment *find_host_comment(unsigned long); /* finds a specific host comment */ -struct comment *get_first_comment_by_host(char *); -struct comment *get_next_comment_by_host(char *, struct comment *); - -int number_of_host_comments(char *); /* returns the number of comments associated with a particular host */ -int number_of_service_comments(char *, char *); /* returns the number of comments associated with a particular service */ +int number_of_host_comments(char *); /* returns the number of comments associated with a particular host */ +int number_of_service_comments(char *, char *); /* returns the number of comments associated with a particular service */ +int number_of_comments(void); int add_comment(int, int, char *, char *, time_t, char *, char *, unsigned long, int, int, time_t, int); /* adds a comment (host or service) */ -int sort_comments(void); int add_host_comment(int, char *, time_t, char *, char *, unsigned long, int, int, time_t, int); /* adds a host comment */ int add_service_comment(int, char *, char *, time_t, char *, char *, unsigned long, int, int, time_t, int); /* adds a service comment */ -int add_comment_to_hashlist(struct comment *); - void free_comment_data(void); /* frees memory allocated to the comment list */ NAGIOS_END_DECL diff --git a/devtools/ci/naemon/common.h b/devtools/ci/naemon/common.h index a96c5eb..a19d779 100644 --- a/devtools/ci/naemon/common.h +++ b/devtools/ci/naemon/common.h @@ -19,8 +19,6 @@ extern char illegal_output_char_map[256]; extern int log_rotation_method; extern int check_external_commands; -/* set this if you're going to add a ton of comments at once */ -extern int defer_comment_sorting; extern unsigned long next_downtime_id; extern char *object_cache_file; @@ -46,6 +44,7 @@ extern int obsess_over_hosts; extern int enable_timing_point; extern char *config_file_dir; +extern char *config_rel_path; #ifdef HAVE_TZNAME #ifdef CYGWIN @@ -322,6 +321,11 @@ NAGIOS_END_DECL #define CMD_DEL_DOWNTIME_BY_HOSTGROUP_NAME 171 #define CMD_DEL_DOWNTIME_BY_START_TIME_COMMENT 172 +#define CMD_ACKNOWLEDGE_HOST_PROBLEM_EXPIRE 173 +#define CMD_ACKNOWLEDGE_SVC_PROBLEM_EXPIRE 174 + +#define CMD_LOG 175 + /* custom command introduced in Nagios 3.x */ #define CMD_CUSTOM_COMMAND 999 @@ -470,9 +474,6 @@ NAGIOS_END_DECL #define MAX_FILENAME_LENGTH 256 /* max length of path/filename that Nagios will process */ #define MAX_INPUT_BUFFER 1024 /* size in bytes of max. input buffer (for reading files, misc stuff) */ -#define MAX_COMMAND_BUFFER 8192 /* max length of raw or processed command line */ -#define MAX_EXTERNAL_COMMAND_LENGTH 8192 /* max length of an external command */ - #define MAX_DATETIME_LENGTH 48 @@ -496,4 +497,5 @@ NAGIOS_END_DECL #define MODATTR_CHECK_TIMEPERIOD 16384 #define MODATTR_CUSTOM_VARIABLE 32768 #define MODATTR_NOTIFICATION_TIMEPERIOD 65536 +#define MODATTR_NOTIFICATION_HANDLER_COMMAND 131072 #endif /* INCLUDE_COMMON_H */ diff --git a/devtools/ci/naemon/defaults.h b/devtools/ci/naemon/defaults.h index 749d6a2..2e2efa5 100644 --- a/devtools/ci/naemon/defaults.h +++ b/devtools/ci/naemon/defaults.h @@ -47,6 +47,7 @@ #define DEFAULT_LOG_CURRENT_STATES 1 /* log current service and host states after rotating log */ #define DEFAULT_LOG_EXTERNAL_COMMANDS 1 /* log external commands */ #define DEFAULT_LOG_PASSIVE_CHECKS 1 /* log passive service checks */ +#define DEFAULT_LOG_GLOBAL_NOTIFICATIONS 1 /* log global notifications */ #define DEFAULT_DEBUG_LEVEL 0 /* don't log any debugging information */ #define DEFAULT_DEBUG_VERBOSITY 1 @@ -86,8 +87,11 @@ #define UPDATE_CHECK_RETRY_INTERVAL_WOBBLE 60*60*3 /* 3 hour wobble on top of base retry interval */ #define DEFAULT_ALLOW_EMPTY_HOSTGROUP_ASSIGNMENT 2 /* Allow assigning to empty hostgroups by default, but warn about it */ -#define DEFAULT_ALLOW_CIRCULAR_DEPENDENCIES 0 /* Allow circular depdendencies */ +#define DEFAULT_ALLOW_CIRCULAR_DEPENDENCIES 0 /* Allow circular dependencies */ #define DEFAULT_HOST_DOWN_DISABLE_SERVICE_CHECKS 0 /* run service checks if the host is down */ +#define DEFAULT_SKIP_CHECK_STATUS -1 /* do not change status by default */ +#define SKIP_KEEP_RUNNING_WHEN_UP -2 /* run service checks as long as the host and service is up (ok/warning) */ +#define DEFAULT_SERVICE_PARENTS_DISABLE_SERVICE_CHECKS 0 /* run service checks if service parents are down */ #define DEFAULT_HOST_PERFDATA_FILE_TEMPLATE "[HOSTPERFDATA]\t$TIMET$\t$HOSTNAME$\t$HOSTEXECUTIONTIME$\t$HOSTOUTPUT$\t$HOSTPERFDATA$" #define DEFAULT_SERVICE_PERFDATA_FILE_TEMPLATE "[SERVICEPERFDATA]\t$TIMET$\t$HOSTNAME$\t$SERVICEDESC$\t$SERVICEEXECUTIONTIME$\t$SERVICELATENCY$\t$SERVICEOUTPUT$\t$SERVICEPERFDATA$" diff --git a/devtools/ci/naemon/downtime.h b/devtools/ci/naemon/downtime.h index c7cfa23..fd39e53 100644 --- a/devtools/ci/naemon/downtime.h +++ b/devtools/ci/naemon/downtime.h @@ -30,7 +30,6 @@ typedef struct scheduled_downtime { char *comment; unsigned long comment_id; int start_flex_downtime; - int incremented_pending_downtime; /* UNUSED */ struct scheduled_downtime *next; struct timed_event *start_event, *stop_event; struct scheduled_downtime *prev; @@ -74,6 +73,7 @@ int sort_downtime(void); struct scheduled_downtime *find_downtime(int, unsigned long); struct scheduled_downtime *find_host_downtime(unsigned long); struct scheduled_downtime *find_service_downtime(unsigned long); +int number_of_downtimes(void); void free_downtime_data(void); /* frees memory allocated to scheduled downtime list */ diff --git a/devtools/ci/naemon/events.h b/devtools/ci/naemon/events.h index 3868261..b12dba2 100644 --- a/devtools/ci/naemon/events.h +++ b/devtools/ci/naemon/events.h @@ -58,6 +58,7 @@ long get_timed_event_time_left_ms(timed_event *ev); /* Main function */ void init_event_queue(void); /* creates the queue nagios_squeue */ int event_poll(void); /* main monitoring/event handler loop */ +void clear_event_queue(void); /* remove all events from the event queue */ void destroy_event_queue(void); /* destroys the queue nagios_squeue */ NAGIOS_END_DECL diff --git a/devtools/ci/naemon/globals.h b/devtools/ci/naemon/globals.h index 768f236..1b96e78 100644 --- a/devtools/ci/naemon/globals.h +++ b/devtools/ci/naemon/globals.h @@ -46,6 +46,11 @@ extern char *global_service_event_handler; extern command *global_host_event_handler_ptr; extern command *global_service_event_handler_ptr; +extern char *global_host_notification_handler; +extern char *global_service_notification_handler; +extern command *global_host_notification_handler_ptr; +extern command *global_service_notification_handler_ptr; + extern int use_regexp_matches; extern int use_true_regexp_matching; @@ -59,6 +64,7 @@ extern int log_host_retries; extern int log_event_handlers; extern int log_external_commands; extern int log_passive_checks; +extern int log_global_notifications; extern unsigned long logging_options; extern unsigned long syslog_options; @@ -146,6 +152,10 @@ extern unsigned long max_debug_file_size; extern int allow_empty_hostgroup_assignment; extern int allow_circular_dependencies; extern int host_down_disable_service_checks; +extern int service_parents_disable_service_checks; +extern int service_skip_check_dependency_status; +extern int service_skip_check_host_down_status; +extern int host_skip_check_dependency_status; extern time_t last_program_stop; extern time_t event_start; @@ -155,9 +165,7 @@ extern int currently_running_service_checks; extern int currently_running_host_checks; extern unsigned long next_event_id; -extern unsigned long next_problem_id; extern unsigned long next_comment_id; -extern unsigned long next_notification_id; extern unsigned long modified_process_attributes; extern unsigned long modified_host_process_attributes; diff --git a/devtools/ci/naemon/lib/kvvec.h b/devtools/ci/naemon/lib/kvvec.h index 9437448..1285d02 100644 --- a/devtools/ci/naemon/lib/kvvec.h +++ b/devtools/ci/naemon/lib/kvvec.h @@ -211,7 +211,7 @@ extern int kvvec_destroy(struct kvvec *kvv, int flags); /** * Free key/value pairs associated with a key/value vector * @param kvv The key/value vector to operate on - * @param flags flags or'ed combination of KVVEC_FREE_{KEYS,VALUES}, or KVVEC_FREE_ALL + * @param flags or'ed combination of KVVEC_FREE_{KEYS,VALUES}, or KVVEC_FREE_ALL */ void kvvec_free_kvpairs(struct kvvec *kvv, int flags); diff --git a/devtools/ci/naemon/lib/lnae-utils.h b/devtools/ci/naemon/lib/lnae-utils.h index 5965e03..a8aac42 100644 --- a/devtools/ci/naemon/lib/lnae-utils.h +++ b/devtools/ci/naemon/lib/lnae-utils.h @@ -88,6 +88,11 @@ # define veclen ARRAY_SIZE #endif +/* sets a limit for max open files if ulimit is set to unlimited or a unusual high value */ +#ifndef MAX_FD_LIMIT +#define MAX_FD_LIMIT 100000 +#endif + #ifndef offsetof /** standard offsetof macro */ # define offsetof(t, f) ((unsigned long)&((t *)0)->f) diff --git a/devtools/ci/naemon/lib/nsutils.h b/devtools/ci/naemon/lib/nsutils.h index 7b610b2..907a9e8 100644 --- a/devtools/ci/naemon/lib/nsutils.h +++ b/devtools/ci/naemon/lib/nsutils.h @@ -6,6 +6,8 @@ #endif #include +#include +#include NAGIOS_BEGIN_DECL @@ -97,6 +99,14 @@ extern int online_cpus(void); extern const char *mkstr(const char *fmt, ...) __attribute__((__format__(__printf__, 1, 2))); +/** + * format duration seconds into human readable string. + * @note The returned string must *not* be free()'d! + * @param[in] duration The duration in seconds + * @return A pointer to the formatted string on success. Undefined on errors + */ +extern const char *duration_string(unsigned long); + /** * Calculate the millisecond delta between two timeval structs * @param[in] start The start time @@ -114,6 +124,48 @@ extern int tv_delta_msec(const struct timeval *start, const struct timeval *stop */ extern float tv_delta_f(const struct timeval *start, const struct timeval *stop); +/** + * clone source timestamp to destination timeval + * @param tv1 Destination timeval + * @param tv2 Source timeval + * @return nothing + */ +static inline void tv_clone(struct timeval *dst, struct timeval *src) +{ + dst->tv_sec = src->tv_sec; + dst->tv_usec = src->tv_usec; +} + +/** + * set timestamp to target timeval + * @param tv Target timeval + * @return nothing + */ +static inline void tv_set(struct timeval *timestamp) +{ + gettimeofday(timestamp, NULL); +} + +/** + * Convert timeval to str + * @param tv Source timeval + * @return A pointer to the formatted string on success. + */ +const char* tv_str(struct timeval *tv); + +/** + * Convert string to timeval + * @param str The timeval string (sec.usec) + * @param tv The target timeval + * @return 0 on success, -1 on errors + */ +extern int str2timeval(char *str, struct timeval *tv); + +/** + * close and reopen stdin, stdout and stderr to /dev/null + */ +void close_standard_fds(void); + NAGIOS_END_DECL /** @} */ diff --git a/devtools/ci/naemon/lib/runcmd.h b/devtools/ci/naemon/lib/runcmd.h index fa2ef1d..894aaf7 100644 --- a/devtools/ci/naemon/lib/runcmd.h +++ b/devtools/ci/naemon/lib/runcmd.h @@ -66,9 +66,8 @@ extern const char *runcmd_strerror(int code); * @param[in] cmdstring The command to launch * @param[out] pfd Child's stdout filedescriptor * @param[out] pfderr Child's stderr filedescriptor - * @param[in] env Currently ignored for portability */ -extern int runcmd_open(const char *cmdstring, int *pfd, int *pfderr, char **env) +extern int runcmd_open(const char *cmdstring, int *pfd, int *pfderr) __attribute__((__nonnull__(1, 2, 3))); /** @@ -95,7 +94,7 @@ extern int runcmd_close(int fd); * representing f.e. unclosed quotes, job control or output redirection. * See the RUNCMD_HAS_* and their ilk to find out about the flag. */ -extern int runcmd_cmd2strv(const char *str, int *out_argc, char **out_argv); +extern int runcmd_cmd2strv(const char *str, int *out_argc, char **out_argv, int *out_envc, char **out_env); NAGIOS_END_DECL diff --git a/devtools/ci/naemon/lib/worker.h b/devtools/ci/naemon/lib/worker.h index 16eba69..6c3a9d8 100644 --- a/devtools/ci/naemon/lib/worker.h +++ b/devtools/ci/naemon/lib/worker.h @@ -8,6 +8,7 @@ #include "lnae-utils.h" #include "kvvec.h" #include "bufferqueue.h" +#include "nsutils.h" /** * @file worker.h diff --git a/devtools/ci/naemon/logging.h b/devtools/ci/naemon/logging.h index 087e411..e0280da 100644 --- a/devtools/ci/naemon/logging.h +++ b/devtools/ci/naemon/logging.h @@ -10,41 +10,42 @@ /******************* LOGGING TYPES ********************/ -#define NSLOG_RUNTIME_ERROR 1 -#define NSLOG_RUNTIME_WARNING 2 +#define NSLOG_RUNTIME_ERROR 1 +#define NSLOG_RUNTIME_WARNING 2 -#define NSLOG_VERIFICATION_ERROR 4 -#define NSLOG_VERIFICATION_WARNING 8 +#define NSLOG_VERIFICATION_ERROR 4 +#define NSLOG_VERIFICATION_WARNING 8 -#define NSLOG_CONFIG_ERROR 16 -#define NSLOG_CONFIG_WARNING 32 +#define NSLOG_CONFIG_ERROR 16 +#define NSLOG_CONFIG_WARNING 32 -#define NSLOG_PROCESS_INFO 64 -#define NSLOG_EVENT_HANDLER 128 -/*#define NSLOG_NOTIFICATION 256*/ /* NOT USED ANYMORE - CAN BE REUSED */ -#define NSLOG_EXTERNAL_COMMAND 512 +#define NSLOG_PROCESS_INFO 64 +#define NSLOG_EVENT_HANDLER 128 +/*#define NSLOG_NOTIFICATION 256*/ /* NOT USED ANYMORE - CAN BE REUSED */ +#define NSLOG_EXTERNAL_COMMAND 512 -#define NSLOG_HOST_UP 1024 -#define NSLOG_HOST_DOWN 2048 -#define NSLOG_HOST_UNREACHABLE 4096 +#define NSLOG_HOST_UP 1024 +#define NSLOG_HOST_DOWN 2048 +#define NSLOG_HOST_UNREACHABLE 4096 -#define NSLOG_SERVICE_OK 8192 -#define NSLOG_SERVICE_UNKNOWN 16384 -#define NSLOG_SERVICE_WARNING 32768 -#define NSLOG_SERVICE_CRITICAL 65536 +#define NSLOG_SERVICE_OK 8192 +#define NSLOG_SERVICE_UNKNOWN 16384 +#define NSLOG_SERVICE_WARNING 32768 +#define NSLOG_SERVICE_CRITICAL 65536 -#define NSLOG_PASSIVE_CHECK 131072 +#define NSLOG_PASSIVE_CHECK 131072 -#define NSLOG_INFO_MESSAGE 262144 +#define NSLOG_INFO_MESSAGE 262144 -#define NSLOG_HOST_NOTIFICATION 524288 -#define NSLOG_SERVICE_NOTIFICATION 1048576 +#define NSLOG_HOST_NOTIFICATION 524288 +#define NSLOG_SERVICE_NOTIFICATION 1048576 +#define NSLOG_EXT_CUSTOM 2097152 /***************** DEBUGGING LEVELS *******************/ -#define DEBUGL_ALL -1 +#define DEBUGL_ALL -1 #define DEBUGL_NONE 0 -#define DEBUGL_CONFIG 2 +#define DEBUGL_CONFIG 2 #define DEBUGL_PROCESS 4 #define DEBUGL_STATUSDATA 4 #define DEBUGL_RETENTIONDATA 4 @@ -64,7 +65,7 @@ #define DEBUGL_SCHEDULING 8192 #define DEBUGV_BASIC 0 -#define DEBUGV_MORE 1 +#define DEBUGV_MORE 1 #define DEBUGV_MOST 2 @@ -85,8 +86,8 @@ __attribute__((__format__(__printf__, 2, 3))); int log_debug_info(int, int, const char *, ...) __attribute__((__format__(__printf__, 3, 4))); -int rotate_log_file(time_t); /* rotates the main log file */ -int write_log_file_info(time_t *); /* records log file/version info */ +int rotate_log_file(time_t); /* rotates the main log file */ +int write_log_file_info(time_t *); /* records log file/version info */ int open_debug_log(void); int close_debug_log(void); int close_log_file(void); @@ -95,8 +96,7 @@ int close_log_file(void); * corresponding Naemon levels. Only intended for use as a regular handler, * don't invoke directly through application code*/ extern guint nm_g_log_handler_id; -void nm_g_log_handler(const gchar *domain, GLogLevelFlags log_level, - const gchar *message, gpointer udata); +void nm_g_log_handler(const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer udata); NAGIOS_END_DECL #endif diff --git a/devtools/ci/naemon/macros.h b/devtools/ci/naemon/macros.h index a4f8f40..a8621f0 100644 --- a/devtools/ci/naemon/macros.h +++ b/devtools/ci/naemon/macros.h @@ -22,7 +22,7 @@ /****************** MACRO DEFINITIONS *****************/ #define MACRO_ENV_VAR_PREFIX "NAGIOS_" #define MAX_USER_MACROS 256 /* max $USERx$ macros */ -#define MACRO_X_COUNT 156 /* size of macro_x[] array */ +#define MACRO_X_COUNT 164 /* size of macro_x[] array */ NAGIOS_BEGIN_DECL @@ -201,7 +201,15 @@ typedef struct nagios_macros nagios_macros; #define MACRO_HOSTVALUE 153 #define MACRO_SERVICEVALUE 154 #define MACRO_PROBLEMVALUE 155 - +#define MACRO_HOSTPROBLEMSTART 156 +#define MACRO_HOSTPROBLEMEND 157 +#define MACRO_HOSTPROBLEMDURATIONSEC 158 +#define MACRO_HOSTPROBLEMDURATION 159 +#define MACRO_SERVICEPROBLEMSTART 160 +#define MACRO_SERVICEPROBLEMEND 161 +#define MACRO_SERVICEPROBLEMDURATIONSEC 162 +#define MACRO_SERVICEPROBLEMDURATION 163 +/* NOTE: update MACRO_X_COUNT above to highest macro + 1 */ /************* MACRO CLEANING OPTIONS *****************/ #define STRIP_ILLEGAL_MACRO_CHARS 1 diff --git a/devtools/ci/naemon/naemon.h b/devtools/ci/naemon/naemon.h index 02410b3..e2daf28 100644 --- a/devtools/ci/naemon/naemon.h +++ b/devtools/ci/naemon/naemon.h @@ -65,16 +65,6 @@ * header before use. */ -/************* MISC LENGTH/SIZE DEFINITIONS ***********/ - -/* - NOTE: Plugin length is artificially capped at 8k to prevent runaway plugins from returning MBs/GBs of data - back to Nagios. If you increase the 8k cap by modifying this value, make sure you also increase the value - of MAX_EXTERNAL_COMMAND_LENGTH in common.h to allow for passive checks results received through the external - command file. EG 10/19/07 - */ -#define MAX_PLUGIN_OUTPUT_LENGTH 8192 /* max length of plugin output (including perf data) */ - /*********** ROUTE CHECK PROPAGATION TYPES ************/ #define PROPAGATE_TO_PARENT_HOSTS 1 diff --git a/devtools/ci/naemon/nebcallbacks.h b/devtools/ci/naemon/nebcallbacks.h index 2b79c5b..8ed700c 100644 --- a/devtools/ci/naemon/nebcallbacks.h +++ b/devtools/ci/naemon/nebcallbacks.h @@ -36,6 +36,7 @@ enum NEBCallbackType { NEBCALLBACK_STATE_CHANGE_DATA, NEBCALLBACK_CONTACT_STATUS_DATA, NEBCALLBACK_ADAPTIVE_CONTACT_DATA, + NEBCALLBACK_VAULT_MACRO_DATA, NEBCALLBACK_TYPE__COUNT }; diff --git a/devtools/ci/naemon/nebmods.h b/devtools/ci/naemon/nebmods.h index ccb25ab..9d01fb6 100644 --- a/devtools/ci/naemon/nebmods.h +++ b/devtools/ci/naemon/nebmods.h @@ -126,7 +126,7 @@ int neb_cb_result_returncode(neb_cb_result *cb_result); /** * Frees a \p neb_cb_resultset and associated resources. Note * that this also frees all the contained \p neb_cb_results contained - * withing the set. + * within the set. * @param cb_resultset a \p neb_cb_resultset */ void neb_cb_resultset_destroy(neb_cb_resultset *); diff --git a/devtools/ci/naemon/nebmodules.h b/devtools/ci/naemon/nebmodules.h index f1d619b..5096124 100644 --- a/devtools/ci/naemon/nebmodules.h +++ b/devtools/ci/naemon/nebmodules.h @@ -10,7 +10,7 @@ NAGIOS_BEGIN_DECL /***** MODULE VERSION INFORMATION *****/ #define NEB_API_VERSION(x) int __neb_api_version = x; -#define CURRENT_NEB_API_VERSION 5 +#define CURRENT_NEB_API_VERSION 8 /***** MODULE INFORMATION *****/ diff --git a/devtools/ci/naemon/nebstructs.h b/devtools/ci/naemon/nebstructs.h index 86e3ac0..ecf97bd 100644 --- a/devtools/ci/naemon/nebstructs.h +++ b/devtools/ci/naemon/nebstructs.h @@ -6,6 +6,7 @@ #endif #include "common.h" +#include "macros.h" NAGIOS_BEGIN_DECL @@ -250,6 +251,8 @@ typedef struct nebstruct_program_status_struct { unsigned long modified_service_attributes; char *global_host_event_handler; char *global_service_event_handler; + char *global_host_notification_handler; + char *global_service_notification_handler; } nebstruct_program_status_data; @@ -469,6 +472,7 @@ typedef struct nebstruct_acknowledgement_struct { int persistent_comment; int notify_contacts; void *object_ptr; + time_t end_time; } nebstruct_acknowledgement_data; @@ -490,5 +494,12 @@ typedef struct nebstruct_statechange_struct { void *object_ptr; } nebstruct_statechange_data; +/* vault macro retrieve structure */ +typedef struct nebstruct_vault_macro_struct { + char *macro_name; + char *value; + nagios_macros *mac; +} nebstruct_vault_macro_data; + NAGIOS_END_DECL #endif diff --git a/devtools/ci/naemon/notifications.h b/devtools/ci/naemon/notifications.h index ee15f1d..29ea5c4 100644 --- a/devtools/ci/naemon/notifications.h +++ b/devtools/ci/naemon/notifications.h @@ -83,7 +83,7 @@ typedef struct notify_list { struct notify_list *next; } notification; -const char *notification_reason_name(unsigned int reason_type); +const char *notification_reason_name(enum NotificationReason reason_type); int check_service_notification_viability(service *, int, int); /* checks viability of notifying all contacts about a service */ int is_valid_escalation_for_service_notification(service *, serviceescalation *, int); /* checks if an escalation entry is valid for a particular service notification */ int should_service_notification_be_escalated(service *); /* checks if a service notification should be escalated */ @@ -98,6 +98,7 @@ int check_contact_host_notification_viability(contact *, host *, int, int); /* c int notify_contact_of_host(nagios_macros *mac, contact *, host *, int, char *, char *, int, int); /* notify a single contact about a host */ time_t get_next_host_notification_time(host *, time_t); /* calculates next acceptable re-notification time for a host */ time_t get_next_service_notification_time(service *, time_t); /* calculates next acceptable re-notification time for a service */ +void free_notification_suppression_map(void); NAGIOS_END_DECL diff --git a/devtools/ci/naemon/objectlist.h b/devtools/ci/naemon/objectlist.h index 7bd016e..64c01d9 100644 --- a/devtools/ci/naemon/objectlist.h +++ b/devtools/ci/naemon/objectlist.h @@ -56,6 +56,15 @@ int prepend_unique_object_to_objectlist(objectlist **list, void *object_ptr, int * @returns OK if successful, OBJECTLIST_DUPE if the element was already in the list, ERROR otherwise. */ int prepend_unique_object_to_objectlist_ptr(objectlist **list, void *object_ptr, int (*comparator)(const void *a, const void *b, void *user_data), void *user_data); + +/** + * Remove first matching object_ptr from the list. + * @param list An reference to an objectlist. Note that an empty objectlist is just NULL. + * @param object_ptr The object you want to remove from the list. + * @returns OK if successful, ERROR otherwise. + */ +int remove_object_from_objectlist(objectlist **list, void *object_ptr); + /** * Free all the allocated memory of the objectlist. Note: this will completely * orphan any allocated memory inside the objectlist. diff --git a/devtools/ci/naemon/objects_host.h b/devtools/ci/naemon/objects_host.h index 780a77f..ab0d30f 100644 --- a/devtools/ci/naemon/objects_host.h +++ b/devtools/ci/naemon/objects_host.h @@ -33,6 +33,7 @@ struct host { struct servicesmember *services; char *check_command; int initial_state; + int check_timeout; double check_interval; double retry_interval; int max_attempts; @@ -77,6 +78,7 @@ struct host { customvariablesmember *custom_variables; int problem_has_been_acknowledged; int acknowledgement_type; + time_t acknowledgement_end_time; int check_type; int current_state; int last_state; @@ -88,8 +90,10 @@ struct host { int current_attempt; unsigned long current_event_id; unsigned long last_event_id; - unsigned long current_problem_id; - unsigned long last_problem_id; + char *current_problem_id; + char *last_problem_id; + time_t problem_start; + time_t problem_end; double latency; double execution_time; int is_executing; @@ -109,7 +113,7 @@ struct host { int notified_on; int current_notification_number; int no_more_notifications; - unsigned long current_notification_id; + char *current_notification_id; int check_flapping_recovery_notification; int scheduled_downtime_depth; int pending_flex_downtime; /* UNUSED */ @@ -118,6 +122,7 @@ struct host { time_t last_state_history_update; int is_flapping; unsigned long flapping_comment_id; + struct objectlist *comments_list; double percent_state_change; int total_services; unsigned long modified_attributes; @@ -129,7 +134,7 @@ struct host { /* objects we depend upon */ struct objectlist *exec_deps, *notify_deps; struct objectlist *escalation_list; - time_t last_update /* timestamp when object has been updated the last time */; + struct timeval last_update /* timestamp when object has been updated the last time */; struct host *next; struct timed_event *next_check_event; }; @@ -149,7 +154,7 @@ void destroy_objects_host(void); host *create_host(const char *name); /** This is an evil legacy function which you should never, ever use */ -int setup_host_variables(host *new_host, const char *display_name, const char *alias, const char *address, const char *check_period, int initial_state, double check_interval, double retry_interval, int max_attempts, int notification_options, double notification_interval, double first_notification_delay, const char *notification_period, int notifications_enabled, const char *check_command, int checks_enabled, int accept_passive_checks, const char *event_handler, int event_handler_enabled, int flap_detection_enabled, double low_flap_threshold, double high_flap_threshold, int flap_detection_options, int stalking_options, int process_perfdata, int check_freshness, int freshness_threshold, const char *notes, const char *notes_url, const char *action_url, const char *icon_image, const char *icon_image_alt, const char *vrml_image, const char *statusmap_image, int x_2d, int y_2d, int have_2d_coords, double x_3d, double y_3d, double z_3d, int have_3d_coords, int retain_status_information, int retain_nonstatus_information, int obsess_over_host, unsigned int hourly_value); +int setup_host_variables(host *new_host, const char *display_name, const char *alias, const char *address, const char *check_period, int initial_state, int check_timeout, double check_interval, double retry_interval, int max_attempts, int notification_options, double notification_interval, double first_notification_delay, const char *notification_period, int notifications_enabled, const char *check_command, int checks_enabled, int accept_passive_checks, const char *event_handler, int event_handler_enabled, int flap_detection_enabled, double low_flap_threshold, double high_flap_threshold, int flap_detection_options, int stalking_options, int process_perfdata, int check_freshness, int freshness_threshold, const char *notes, const char *notes_url, const char *action_url, const char *icon_image, const char *icon_image_alt, const char *vrml_image, const char *statusmap_image, int x_2d, int y_2d, int have_2d_coords, double x_3d, double y_3d, double z_3d, int have_3d_coords, int retain_status_information, int retain_nonstatus_information, int obsess_over_host, unsigned int hourly_value); int register_host(host *new_host); void destroy_host(host *this_host); diff --git a/devtools/ci/naemon/objects_service.h b/devtools/ci/naemon/objects_service.h index 7756565..36fdb5e 100644 --- a/devtools/ci/naemon/objects_service.h +++ b/devtools/ci/naemon/objects_service.h @@ -32,6 +32,7 @@ struct service { char *check_command; char *event_handler; int initial_state; + int check_timeout; double check_interval; double retry_interval; int max_attempts; @@ -68,6 +69,7 @@ struct service { struct customvariablesmember *custom_variables; int problem_has_been_acknowledged; int acknowledgement_type; + time_t acknowledgement_end_time; int host_problem_at_last_check; int check_type; int current_state; @@ -82,8 +84,10 @@ struct service { int current_attempt; unsigned long current_event_id; unsigned long last_event_id; - unsigned long current_problem_id; - unsigned long last_problem_id; + char *current_problem_id; + char *last_problem_id; + time_t problem_start; + time_t problem_end; time_t last_notification; time_t next_notification; int no_more_notifications; @@ -98,7 +102,7 @@ struct service { int is_being_freshened; unsigned int notified_on; int current_notification_number; - unsigned long current_notification_id; + char *current_notification_id; double latency; double execution_time; int is_executing; @@ -109,6 +113,7 @@ struct service { int state_history_index; int is_flapping; unsigned long flapping_comment_id; + struct objectlist *comments_list; double percent_state_change; unsigned long modified_attributes; struct host *host_ptr; @@ -120,7 +125,7 @@ struct service { struct objectlist *servicegroups_ptr; struct objectlist *exec_deps, *notify_deps; struct objectlist *escalation_list; - time_t last_update /* timestamp when object has been updated the last time */; + struct timeval last_update /* timestamp when object has been updated the last time */; struct service *next; struct timed_event *next_check_event; }; @@ -145,12 +150,12 @@ static const struct flag_map service_flag_map[] = { }; int init_objects_service(int elems); -void destroy_objects_service(void); +void destroy_objects_service(int truncate_lists); service *create_service(host *hst, const char *description); -int setup_service_variables(service *svc, const char *display_name, const char *check_period, const char *check_command, int initial_state, int max_attempts, int accept_passive_checks, double check_interval, double retry_interval, double notification_interval, double first_notification_delay, char *notification_period, int notification_options, int notifications_enabled, int is_volatile, const char *event_handler, int event_handler_enabled, int checks_enabled, int flap_detection_enabled, double low_flap_threshold, double high_flap_threshold, int flap_detection_options, int stalking_options, int process_perfdata, int check_freshness, int freshness_threshold, const char *notes, const char *notes_url, const char *action_url, const char *icon_image, const char *icon_image_alt, int retain_status_information, int retain_nonstatus_information, int obsess, unsigned int hourly_value); +int setup_service_variables(service *svc, const char *display_name, const char *check_period, const char *check_command, int initial_state, int check_timeout, int max_attempts, int accept_passive_checks, double check_interval, double retry_interval, double notification_interval, double first_notification_delay, char *notification_period, int notification_options, int notifications_enabled, int is_volatile, const char *event_handler, int event_handler_enabled, int checks_enabled, int flap_detection_enabled, double low_flap_threshold, double high_flap_threshold, int flap_detection_options, int stalking_options, int process_perfdata, int check_freshness, int freshness_threshold, const char *notes, const char *notes_url, const char *action_url, const char *icon_image, const char *icon_image_alt, int retain_status_information, int retain_nonstatus_information, int obsess, unsigned int hourly_value); int register_service(service *new_service); -void destroy_service(service *svc); +void destroy_service(service *svc, int truncate_lists); struct contactgroupsmember *add_contactgroup_to_service(service *, char *); /* adds a contact group to a service definition */ struct contactsmember *add_contact_to_service(service *, char *); /* adds a contact to a host definition */ diff --git a/devtools/ci/naemon/objects_servicegroup.h b/devtools/ci/naemon/objects_servicegroup.h index 6113f7d..964b8e0 100644 --- a/devtools/ci/naemon/objects_servicegroup.h +++ b/devtools/ci/naemon/objects_servicegroup.h @@ -32,11 +32,11 @@ struct servicegroup { }; int init_objects_servicegroup(int elems); -void destroy_objects_servicegroup(void); +void destroy_objects_servicegroup(int truncate_lists); servicegroup *create_servicegroup(const char *name, const char *alias, const char *notes, const char *notes_url, const char *action_url); int register_servicegroup(servicegroup *this_servicegroup); -void destroy_servicegroup(servicegroup *this_servicegroup); +void destroy_servicegroup(servicegroup *this_servicegroup, int truncate_lists); struct servicesmember *add_service_to_servicegroup(servicegroup *, service *); void remove_service_from_servicegroup(servicegroup *temp_servicegroup, service *svc); diff --git a/devtools/ci/naemon/objects_timeperiod.h b/devtools/ci/naemon/objects_timeperiod.h index b0c380a..b1dceb6 100644 --- a/devtools/ci/naemon/objects_timeperiod.h +++ b/devtools/ci/naemon/objects_timeperiod.h @@ -88,7 +88,8 @@ struct timeperiod *find_timeperiod(const char *); void fcache_timeperiod(FILE *fp, const struct timeperiod *temp_timeperiod); int check_time_against_period(time_t, const timeperiod *); /* check to see if a specific time is covered by a time period */ -void get_next_valid_time(time_t, time_t *, timeperiod *); /* get the next valid time in a time period */ +void get_next_valid_time(time_t, time_t *, const timeperiod *); /* get the next valid time in a time period */ +void get_next_invalid_time(time_t, time_t *, const timeperiod *); /* get the next invalid time in a time period (aka end of the period) */ NAGIOS_END_DECL #endif diff --git a/devtools/ci/naemon/query-handler.h b/devtools/ci/naemon/query-handler.h index e599ff9..7fda0db 100644 --- a/devtools/ci/naemon/query-handler.h +++ b/devtools/ci/naemon/query-handler.h @@ -20,6 +20,7 @@ int qh_init(const char *path); void qh_deinit(const char *path); int qh_register_handler(const char *name, const char *description, unsigned int options, qh_handler handler); const char *qh_strerror(int code); +void qh_close_socket(void); NAGIOS_END_DECL diff --git a/devtools/ci/naemon/shared.h b/devtools/ci/naemon/shared.h index 8db0ead..72e40aa 100644 --- a/devtools/ci/naemon/shared.h +++ b/devtools/ci/naemon/shared.h @@ -48,7 +48,10 @@ int mmap_fclose(mmapfile *temp_mmapfile); char *mmap_fgets(mmapfile *temp_mmapfile); char *mmap_fgets_multiline(mmapfile * temp_mmapfile); void strip(char *buffer); -int hashfunc(const char *name1, const char *name2, int hashslots); +char *rstrip(char *c); +char *lstrip(char *c); +char *trim(char *c); +void noeol_ctime(const time_t *, char *); void get_datetime_string(time_t *raw_time, char *buffer, int buffer_length, int type); void get_time_breakdown(unsigned long raw_time, int *days, int *hours, diff --git a/devtools/ci/naemon/utils.h b/devtools/ci/naemon/utils.h index f7106a8..c9105c6 100644 --- a/devtools/ci/naemon/utils.h +++ b/devtools/ci/naemon/utils.h @@ -37,6 +37,7 @@ void signal_react(void); /* General signal reaction routines */ void handle_sigxfsz(void); /* handle SIGXFSZ */ int signal_parent(int); /* signal parent when daemonizing */ int daemon_init(void); /* switches to daemon mode */ +void close_lockfile_fd(void); /* close lock_file file handle */ int init_check_stats(void); int update_check_stats(int, time_t); diff --git a/devtools/ci/naemon/workers.h b/devtools/ci/naemon/workers.h index 07fcbe5..3f67166 100644 --- a/devtools/ci/naemon/workers.h +++ b/devtools/ci/naemon/workers.h @@ -12,7 +12,11 @@ #define WPROC_FORCE (1 << 0) -NAGIOS_BEGIN_DECL; +#ifndef ETIME +#define ETIME ETIMEDOUT +#endif + +NAGIOS_BEGIN_DECL typedef struct wproc_result { unsigned int job_id; @@ -45,5 +49,5 @@ int init_workers(int desired_workers); int wproc_run_callback(char *cmt, int timeout, void (*cb)(struct wproc_result *, void *, int), void *data, nagios_macros *mac); -NAGIOS_END_DECL; +NAGIOS_END_DECL #endif diff --git a/devtools/ci/nagios/archiveutils.h b/devtools/ci/nagios/archiveutils.h index 1bf4ac3..5ae8939 100644 --- a/devtools/ci/nagios/archiveutils.h +++ b/devtools/ci/nagios/archiveutils.h @@ -114,7 +114,7 @@ AU_NOTIFICATION_SERVICE_FLAPPING_STOP | \ AU_NOTIFICATION_SERVICE_UNKNOWN) -#define AU_NOTFICATION_ALL (AU_NOTFICATION_HOST_ALL | \ +#define AU_NOTIFICATION_ALL (AU_NOTIFICATION_HOST_ALL | \ AU_NOTIFICATION_SERVICE_ALL) typedef struct au_array_struct { diff --git a/devtools/ci/nagios/cgiutils.h b/devtools/ci/nagios/cgiutils.h index c0b0d2b..c46bb1d 100644 --- a/devtools/ci/nagios/cgiutils.h +++ b/devtools/ci/nagios/cgiutils.h @@ -24,6 +24,7 @@ #include "logging.h" #include "objects.h" #include "cgiauth.h" +#include "locations.h" NAGIOS_BEGIN_DECL @@ -35,12 +36,7 @@ NAGIOS_BEGIN_DECL /******************************* CGI NAMES **********************************/ #define STATUS_CGI "status.cgi" -#ifdef LEGACY_GRAPHICAL_CGIS #define STATUSMAP_CGI "statusmap.cgi" -#else -#define STATUSMAP_CGI "../map.php" -#define LEGACY_STATUSMAP_CGI "statusmap.cgi" -#endif #define STATUSWORLD_CGI "statuswrl.cgi" #define COMMAND_CGI "cmd.cgi" #define EXTINFO_CGI "extinfo.cgi" @@ -49,22 +45,12 @@ NAGIOS_BEGIN_DECL #define HISTORY_CGI "history.cgi" #define CONFIG_CGI "config.cgi" #define OUTAGES_CGI "outages.cgi" -#ifdef LEGACY_GRAPHICAL_CGIS #define TRENDS_CGI "trends.cgi" -#else -#define TRENDS_CGI "../trends.html" -#define LEGACY_TRENDS_CGI "trends.cgi" -#endif #define AVAIL_CGI "avail.cgi" #define TAC_CGI "tac.cgi" #define STATUSWML_CGI "statuswml.cgi" #define TRACEROUTE_CGI "traceroute.cgi" -#ifdef LEGACY_GRAPHICAL_CGIS #define HISTOGRAM_CGI "histogram.cgi" -#else -#define HISTOGRAM_CGI "../histogram.html" -#define LEGACY_HISTOGRAM_CGI "histogram.cgi" -#endif #define CHECKSANITY_CGI "checksanity.cgi" #define MINISTATUS_CGI "ministatus.cgi" #define SUMMARY_CGI "summary.cgi" @@ -93,7 +79,7 @@ NAGIOS_BEGIN_DECL #define NAGFUNCS_CSS "nag_funcs.css" /********************************* JAVASCRIPT INCLUDES **********************/ -#define JQUERY_JS "jquery-1.12.4.min.js" +#define JQUERY_JS "jquery-3.7.1.min.js" #define NAGFUNCS_JS "nag_funcs.js" /********************************* ICONS ************************************/ @@ -507,5 +493,7 @@ struct nagios_extcmd* extcmd_get_command_id(int); struct nagios_extcmd* extcmd_get_command_name(const char *); const char *extcmd_get_name(int); +void build_subdir_path(char* path, size_t size, const char* prefix, const char* subdir); + NAGIOS_END_DECL #endif diff --git a/devtools/ci/nagios/common.h b/devtools/ci/nagios/common.h index 821b9c1..d91115c 100644 --- a/devtools/ci/nagios/common.h +++ b/devtools/ci/nagios/common.h @@ -24,8 +24,8 @@ #include "shared.h" -#define PROGRAM_VERSION "4.4.5" -#define PROGRAM_MODIFICATION_DATE "2019-08-20" +#define PROGRAM_VERSION "4.5.14" +#define PROGRAM_MODIFICATION_DATE "2026-08-05" NAGIOS_BEGIN_DECL @@ -350,6 +350,8 @@ NAGIOS_END_DECL /* new commands in Nagios 4.x found below... */ #define CMD_CLEAR_HOST_FLAPPING_STATE 173 #define CMD_CLEAR_SVC_FLAPPING_STATE 174 +#define CMD_CHANGE_HOST_EVENT_HANDLER_TIMEPERIOD 175 +#define CMD_CHANGE_SVC_EVENT_HANDLER_TIMEPERIOD 176 /* custom command introduced in Nagios 3.x */ #define CMD_CUSTOM_COMMAND 999 @@ -533,4 +535,5 @@ extern const char *cmd_error_strerror(int error_code); #define MODATTR_CHECK_TIMEPERIOD 16384 #define MODATTR_CUSTOM_VARIABLE 32768 #define MODATTR_NOTIFICATION_TIMEPERIOD 65536 +#define MODATTR_EVENT_HANDLER_TIMEPERIOD 131072 #endif /* NAGIOS_COMMON_H_INCLUDED */ diff --git a/devtools/ci/nagios/config.h b/devtools/ci/nagios/config.h new file mode 100644 index 0000000..9e13904 --- /dev/null +++ b/devtools/ci/nagios/config.h @@ -0,0 +1,362 @@ +/* include/config.h. Generated from config.h.in by configure. */ +/************************************************************************ + * + * Nagios Config Header File + * Written By: Ethan Galstad (egalstad@nagios.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + ************************************************************************/ + + +/***** NAGIOS STUFF *****/ + +#define DEFAULT_NAGIOS_USER "nagios" +#define DEFAULT_NAGIOS_GROUP "nagios" + +/* stop gcc from bitching about implicit asprintf declarations */ +#define _GNU_SOURCE 1 + +/* Event broker integration */ +#define USE_EVENT_BROKER /**/ + +/* NERD / radio dispatch usage */ +/* #undef ENABLE_NERD */ + +/* check for RLIMIT_PROC */ +#define DETECT_RLIMIT_PROBLEM 1 + +/* commands used by CGIs */ +#define TRACEROUTE_COMMAND "" +/* #undef PING_COMMAND */ +/* #undef PING_PACKETS_FIRST */ + +/* Debugging options */ +/* function entry and exit */ +/* #undef DEBUG0 */ +/* general info messages */ +/* #undef DEBUG1 */ +/* warning messages */ +/* #undef DEBUG2 */ +/* service and host checks, other events */ +/* #undef DEBUG3 */ +/* service and host notifications */ +/* #undef DEBUG4 */ +/* SQL queries (defunct) */ +/* #undef DEBUG5 */ + +/* I/O implementations */ +/* #undef USE_XSDDEFAULT */ +/* #undef USE_XCDDEFAULT */ +/* #undef USE_XRDDEFAULT */ +/* #undef USE_XODTEMPLATE */ +/* #undef USE_XPDDEFAULT */ +/* #undef USE_XDDDEFAULT */ + + +/***** CGI COMPILE OPTIONS *****/ +/* should we compile and use the statusmap CGI? */ +#define USE_STATUSMAP /**/ +/* should we compile and use the statuswrl CGI? */ +#define USE_STATUSWRL /**/ +/* should we compile and use the trends CGI? */ +#define USE_TRENDS /**/ +/* should we compile and use the histogram CGI? */ +#define USE_HISTOGRAM /**/ + + + +/***** FUNCTION DEFINITIONS *****/ + +#define HAVE_SETENV 1 +#define HAVE_UNSETENV 1 +/* #undef HAVE_SOCKET */ +#define HAVE_STRDUP 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOUL 1 +#define HAVE_INITGROUPS 1 +/* #undef HAVE_GETLOADAVG */ +#define HAVE_GDIMAGECREATETRUECOLOR 1 +#define HAVE_SIGACTION 1 + + + +/***** ASPRINTF() AND FRIENDS *****/ + +/* #undef HAVE_VSNPRINTF */ +/* #undef HAVE_SNPRINTF */ +/* #undef HAVE_ASPRINTF */ +/* #undef HAVE_VASPRINTF */ +#define HAVE_C99_VSNPRINTF 1 +#define HAVE_VA_COPY 1 +/* #undef HAVE___VA_COPY */ + + + +/***** MISC DEFINITIONS *****/ + +#define USE_NANOSLEEP /**/ +#define STDC_HEADERS 1 +#define HAVE_TM_ZONE 1 +/* #undef HAVE_TZNAME */ +/* #undef USE_PROC */ +#define SOCKET_SIZE_TYPE size_t +#define GETGROUPS_T gid_t +#define RETSIGTYPE "" + + + +/***** HEADER FILES *****/ + +#include +#include + +/* needed for the time_t structures we use later... */ +/* this include must come before sys/resource.h or we can have problems on some OSes */ +#define HAVE_SYS_TIME_H 1 +#if HAVE_SYS_TIME_H +#include +#include +#else +#include +#endif + +#define HAVE_SYS_RESOURCE_H 1 +#ifdef HAVE_SYS_RESOURCE_H +#include +#endif + +#define HAVE_LIMITS_H 1 +#ifdef HAVE_LIMITS_H +#include +#endif + +#define HAVE_PWD_H 1 +#ifdef HAVE_PWD_H +#include "config_pwd.h" +#endif + +#define HAVE_GRP_H 1 +#ifdef HAVE_GRP_H +#include +#endif + +#define HAVE_STRINGS_H 1 +#ifdef HAVE_STRINGS_H +#include +#endif + +#define HAVE_STRING_H 1 +#ifdef HAVE_STRINGS_H +#include +#endif + +#define HAVE_UNISTD_H 1 +#ifdef HAVE_UNISTD_H +#include +#endif + +#define HAVE_SYSLOG_H 1 +#ifdef HAVE_SYSLOG_H +#include +#endif + +#define HAVE_SIGNAL_H 1 +#ifdef HAVE_SIGNAL_H +#include +#endif + +#define HAVE_SYS_STAT_H 1 +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#define HAVE_SYS_MMAN_H 1 +#ifdef HAVE_SYS_MMAN_H +#include +#endif + +#define HAVE_FCNTL_H 1 +#ifdef HAVE_FCNTL_H +#include +#endif + +#define HAVE_STDARG_H 1 +#ifdef HAVE_STDARG_H +#include +#endif + +/* Another Solarisism: getloadavg() lives in , not , + * so include the former if it exists. This may be true on other systems, or + * this function may be missing altogether (see: + * https://www.gnu.org/software/gnulib/manual/html_node/getloadavg.html). */ +/* #undef HAVE_SYS_LOADAVG_H */ +#ifdef HAVE_SYS_LOADAVG_H +#include +#endif + +#define HAVE_SYS_TYPES_H 1 +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#define HAVE_SYS_WAIT_H 1 +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#define HAVE_ERRNO_H 1 +#ifdef HAVE_ERRNO_H +#include +#endif + +#define HAVE_SYS_TIMEB_H 1 +#if HAVE_SYS_TIMEB_H +#include +#endif + +#define HAVE_SYS_IPC_H 1 +#ifdef HAVE_SYS_IPC_H +#include +#endif + +#define HAVE_SYS_MSG_H 1 +#ifdef HAVE_SYS_MSG_H +#include +#endif + +#define HAVE_MATH_H 1 +#ifdef HAVE_MATH_H +#include +#endif + +#define HAVE_CTYPE_H 1 +#ifdef HAVE_CTYPE_H +#include +#endif + +#define HAVE_DIRENT_H 1 +#ifdef HAVE_DIRENT_H +#include +#endif + +#define HAVE_REGEX_H 1 +#ifdef HAVE_REGEX_H +#include + +#define HAVE_SYS_SOCKET_H 1 +#ifdef HAVE_SYS_SOCKET_H +#include +#endif + +/* #undef HAVE_SOCKET */ +#ifdef HAVE_SOCKET_H +#include +#endif + +#define HAVE_SSL 1 + +#define HAVE_NETINET_IN_H 1 +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#define HAVE_ARPA_INET_H 1 +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#define HAVE_NETDB_H 1 +#ifdef HAVE_NETDB_H +#include +#endif + +#define HAVE_LIBGEN_H 1 +#ifdef HAVE_LIBGEN_H +#include +#endif + +#define HAVE_SYS_UN_H 1 +#ifdef HAVE_SYS_UN_H +#include +#endif + +#define HAVE_POLL_H 1 +#ifdef HAVE_POLL_H +#include +#endif + +#define HAVE_GETOPT_H 1 +#ifdef HAVE_GETOPT_H +#include +#endif + +/* #undef HAVE_LINUX_MODULE_H */ +#ifdef HAVE_LINUX_MODULE_H +#include +#endif + +#define HAVE_LOCALE_H 1 +#ifdef HAVE_LOCALE_H +#include +#endif + +#define HAVE_WCHAR_H 1 +#ifdef HAVE_WCHAR_H +#include +#endif + +#define HAVE_SYS_PRCTL_H 1 +#ifdef HAVE_SYS_PRCTL_H +#include +#endif + +/* configure script should allow user to override ltdl choice, but this will do for now... */ +/* #undef USE_LTDL */ +/* #undef HAVE_LTDL_H */ +#ifdef HAVE_LTDL_H +#define USE_LTDL +#endif + +#ifdef USE_LTDL +#include +#else +#define HAVE_DLFCN_H /**/ +#ifdef HAVE_DLFCN_H +#include +#endif +#endif + + +/* moved to end to prevent AIX compiler warnings */ +#ifndef RTLD_GLOBAL +#define RTLD_GLOBAL 0 +#endif + +#ifndef RTLD_NOW +#define RTLD_NOW 0 +#endif + + +/***** MARO DEFINITIONS *****/ + +/* this needs to come after all system include files, so we don't accidentally attempt to redefine it */ +#ifndef WEXITSTATUS +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +# define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif + + +#endif diff --git a/devtools/ci/nagios/config_pwd.h b/devtools/ci/nagios/config_pwd.h new file mode 100644 index 0000000..20fdbd4 --- /dev/null +++ b/devtools/ci/nagios/config_pwd.h @@ -0,0 +1,23 @@ +#ifndef NAGIOS_CONFIG_PWD_H_INCLUDED +#define NAGIOS_CONFIG_PWD_H_INCLUDED +/** + * @file include/config_pwd.h + * A wrapper header to let us conditionally define and then undefine + * __XOPEN_OR_POSIX to suppress the conflicting 'struct comment' declaration on + * Solaris. We can't do this in config.h.in since the undef will be commented + * out when config.h is generated. + */ + +#if defined(__sun) && defined(__SVR4) && !defined(__XOPEN_OR_POSIX) +#define __XOPEN_OR_POSIX +#define NEED_TO_UNDEF__XOPEN_OR_POSIX +#endif + +#include + +#if defined(NEED_TO_UNDEF__XOPEN_OR_POSIX) +#undef __XOPEN_OR_POSIX +#undef NEED_TO_UNDEF__XOPEN_OR_POSIX +#endif + +#endif diff --git a/devtools/ci/nagios/downtime.h b/devtools/ci/nagios/downtime.h index 4bee3d2..5d1f46f 100644 --- a/devtools/ci/nagios/downtime.h +++ b/devtools/ci/nagios/downtime.h @@ -80,8 +80,10 @@ int schedule_downtime(int, char *, char *, time_t, char *, char *, time_t, time_ int unschedule_downtime(int, unsigned long); int register_downtime(int, unsigned long); -int handle_scheduled_downtime(struct scheduled_downtime *); -int handle_scheduled_downtime_by_id(unsigned long); +int handle_scheduled_downtime_start(struct scheduled_downtime *); +int handle_scheduled_downtime_end(struct scheduled_downtime *); +int handle_scheduled_downtime_start_by_id(unsigned long); +int handle_scheduled_downtime_end_by_id(unsigned long); int check_pending_flex_host_downtime(struct host *); int check_pending_flex_service_downtime(struct service *); diff --git a/devtools/ci/nagios/ignored_config.h b/devtools/ci/nagios/ignored_config.h new file mode 100644 index 0000000..34d35a3 --- /dev/null +++ b/devtools/ci/nagios/ignored_config.h @@ -0,0 +1,287 @@ +/* include/ignored_config.h. Generated from ignored_config.h.in by configure. */ +/* include/ignored_config.h.in. Generated from configure.ac by autoheader. */ + +/* group name to run nagios */ +#define DEFAULT_NAGIOS_GROUP "nagios" + +/* user name to run nagios */ +#define DEFAULT_NAGIOS_USER "nagios" + +/* System has RLIMIT_PROC */ +#define DETECT_RLIMIT_PROBLEM 1 + +/* defined if user enabled NERD (radio dispatcher) */ +/* #undef ENABLE_NERD */ + +/* Define to the type of elements in the array argument to 'getgroups'. + Usually this is either 'int' or 'gid_t'. */ +#define GETGROUPS_T gid_t + +/* Define to 1 if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* Define if system has C99 compatible vsnprintf */ +#define HAVE_C99_VSNPRINTF 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_CTYPE_H 1 + +/* Define to 1 if you have the declaration of 'tzname', and to 0 if you don't. + */ +/* #undef HAVE_DECL_TZNAME */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DIRENT_H 1 + +/* Which loader library should we use? libtdl or dl? */ +#define HAVE_DLFCN_H /**/ + +/* Define to 1 if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define if your gd library has gdImageCreateTrueColor */ +#define HAVE_GDIMAGECREATETRUECOLOR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_GETOPT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_GRP_H 1 + +/* Define to 1 if you have the 'initgroups' function. */ +#define HAVE_INITGROUPS 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Have the krb5.h header file */ +/* #undef HAVE_KRB5_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LIBGEN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_LOCALE_H 1 + +/* Which loader library should we use? libtdl or dl? */ +/* #undef HAVE_LTDL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MATH_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PWD_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_REGEX_H 1 + +/* Define to 1 if you have the 'setenv' function. */ +#define HAVE_SETENV 1 + +/* Define to 1 if you have the 'sigaction' function. */ +#define HAVE_SIGACTION 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SOCKET_H */ + +/* Have SSL support */ +#define HAVE_SSL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the 'strdup' function. */ +#define HAVE_STRDUP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the 'strstr' function. */ +#define HAVE_STRSTR 1 + +/* Define to 1 if you have the 'strtoul' function. */ +#define HAVE_STRTOUL 1 + +/* Define to 1 if 'tm_zone' is a member of 'struct tm'. */ +#define HAVE_STRUCT_TM_TM_ZONE 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYSLOG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IPC_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_LOADAVG_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_MMAN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_MSG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_POLL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PRCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIMEB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define to 1 if your 'struct tm' has 'tm_zone'. Deprecated, use + 'HAVE_STRUCT_TM_TM_ZONE' instead. */ +#define HAVE_TM_ZONE 1 + +/* Define to 1 if you don't have 'tm_zone' but do have the external array + 'tzname'. */ +/* #undef HAVE_TZNAME */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_UIO_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the 'unsetenv' function. */ +#define HAVE_UNSETENV 1 + +/* Whether va_copy() is available */ +#define HAVE_VA_COPY 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_WCHAR_H 1 + +/* Whether __va_copy() is available */ +/* #undef HAVE___VA_COPY */ + +/* iobroker uses epoll method */ +#define IOBROKER_USES_EPOLL 1 + +/* iobroker uses poll method */ +/* #undef IOBROKER_USES_POLL */ + +/* iobroker uses select method */ +/* #undef IOBROKER_USES_SELECT */ + +/* defined if va_list fails to compile */ +/* #undef NEED_VA_LIST */ + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* typedef for socket size */ +#define SOCKET_SIZE_TYPE size_t + +/* Define to 1 if all of the C89 standard headers exist (not just the ones + required in a freestanding environment). This macro is provided for + backward compatibility; new code need not use it. */ +#define STDC_HEADERS 1 + +/* Define to 1 if your declares 'struct tm'. */ +/* #undef TM_IN_SYS_TIME */ + +/* traceroute command to use */ +#define TRACEROUTE_COMMAND "" + +/* defined to bring in the event broker objects */ +#define USE_EVENT_BROKER /**/ + +/* compile histogram CGI */ +#define USE_HISTOGRAM /**/ + +/* enables use of nanosleep (instead of sleep) */ +#define USE_NANOSLEEP /**/ + +/* Use DH parameters for SSL/TLS */ +/* #undef USE_SSL_DH */ + +/* defined if the user chose to include status map */ +#define USE_STATUSMAP /**/ + +/* statuswrl CGI enabled by default, unless users chooses not to use it */ +#define USE_STATUSWRL /**/ + +/* compile trends CGI */ +#define USE_TRENDS /**/ + +/* Define to empty if 'const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define as 'int' if doesn't define. */ +/* #undef gid_t */ + +/* Define to 'int' if does not define. */ +/* #undef mode_t */ + +/* Define as a signed integer type capable of holding a process identifier. */ +/* #undef pid_t */ + +/* Define as 'unsigned int' if doesn't define. */ +/* #undef size_t */ + +/* Define as 'int' if doesn't define. */ +/* #undef uid_t */ diff --git a/devtools/ci/nagios/lib/fanout.h b/devtools/ci/nagios/lib/fanout.h index d1164dd..6d05874 100644 --- a/devtools/ci/nagios/lib/fanout.h +++ b/devtools/ci/nagios/lib/fanout.h @@ -43,7 +43,7 @@ extern void fanout_destroy(fanout_table *t, void (*destructor)(void *)); * Return a pointer from the fanout table t * * @param[in] t table to fetch from - * @param[in] key key to fetch + * @param[in] key Key to fetch * @return NULL on errors; Pointer to data on success */ extern void *fanout_get(fanout_table *t, unsigned long key); diff --git a/devtools/ci/nagios/lib/iocache.h b/devtools/ci/nagios/lib/iocache.h index 70e75a3..ad9e264 100644 --- a/devtools/ci/nagios/lib/iocache.h +++ b/devtools/ci/nagios/lib/iocache.h @@ -60,7 +60,7 @@ extern unsigned long iocache_size(iocache *ioc); /** * Returns remaining read capacity of the io cache * @param ioc The io cache to operate on - * @return The number of bytes available to read, or -1 if ioc is null, -2 if the buffer is null, or -2 if the buffer size is <= 0 + * @return The number of bytes available to read, or -1 if ioc is null, -2 if the buffer is null, or -3 if the buffer size is <= 0 */ extern unsigned long iocache_capacity(iocache *ioc); diff --git a/devtools/ci/nagios/lib/libnagios.h b/devtools/ci/nagios/lib/libnagios.h index 1c78f05..823e791 100644 --- a/devtools/ci/nagios/lib/libnagios.h +++ b/devtools/ci/nagios/lib/libnagios.h @@ -9,7 +9,7 @@ #include "lnag-utils.h" #include "fanout.h" #include "nsutils.h" -#include "pqueue.h" +#include "prqueue.h" #include "squeue.h" #include "kvvec.h" #include "iobroker.h" diff --git a/devtools/ci/nagios/lib/pqueue.h b/devtools/ci/nagios/lib/prqueue.h similarity index 69% rename from devtools/ci/nagios/lib/pqueue.h rename to devtools/ci/nagios/lib/prqueue.h index 9f0059e..0f472ff 100644 --- a/devtools/ci/nagios/lib/pqueue.h +++ b/devtools/ci/nagios/lib/prqueue.h @@ -19,13 +19,13 @@ #include /** - * @file pqueue.h + * @file prqueue.h * @brief Priority Queue function declarations * * This priority queue library was originally written by Volkan Yazici * . It was lated adapted for Nagios by * Andreas Ericsson . Changes compared to the original - * version are pretty much limited to changing pqueue_pri_t to be + * version are pretty much limited to changing prqueue_pri_t to be * an unsigned long long instead of a double, since ULL comparisons * are 107 times faster on my 64-bit laptop. * @@ -34,36 +34,36 @@ /** priority data type (used to be double, but ull is 107 times faster) */ -typedef unsigned long long pqueue_pri_t; +typedef unsigned long long prqueue_pri_t; /** callback functions to get/set/compare the priority of an element */ -typedef pqueue_pri_t (*pqueue_get_pri_f)(void *a); -typedef void (*pqueue_set_pri_f)(void *a, pqueue_pri_t pri); -typedef int (*pqueue_cmp_pri_f)(pqueue_pri_t next, pqueue_pri_t curr); +typedef prqueue_pri_t (*prqueue_get_pri_f)(void *a); +typedef void (*prqueue_set_pri_f)(void *a, prqueue_pri_t pri); +typedef int (*prqueue_cmp_pri_f)(prqueue_pri_t next, prqueue_pri_t curr); /** callback functions to get/set the position of an element */ -typedef unsigned int (*pqueue_get_pos_f)(void *a); -typedef void (*pqueue_set_pos_f)(void *a, unsigned int pos); +typedef unsigned int (*prqueue_get_pos_f)(void *a); +typedef void (*prqueue_set_pos_f)(void *a, unsigned int pos); /** debug callback function to print a entry */ -typedef void (*pqueue_print_entry_f)(FILE *out, void *a); +typedef void (*prqueue_print_entry_f)(FILE *out, void *a); /** the priority queue handle */ -typedef struct pqueue_t +typedef struct prqueue_t { unsigned int size; /**< number of elements in this queue */ unsigned int avail; /**< slots available in this queue */ unsigned int step; /**< growth stepping setting */ - pqueue_cmp_pri_f cmppri; /**< callback to compare nodes */ - pqueue_get_pri_f getpri; /**< callback to get priority of a node */ - pqueue_set_pri_f setpri; /**< callback to set priority of a node */ - pqueue_get_pos_f getpos; /**< callback to get position of a node */ - pqueue_set_pos_f setpos; /**< callback to set position of a node */ + prqueue_cmp_pri_f cmppri; /**< callback to compare nodes */ + prqueue_get_pri_f getpri; /**< callback to get priority of a node */ + prqueue_set_pri_f setpri; /**< callback to set priority of a node */ + prqueue_get_pos_f getpos; /**< callback to get position of a node */ + prqueue_set_pos_f setpos; /**< callback to set position of a node */ void **d; /**< The actual queue in binary heap form */ -} pqueue_t; +} prqueue_t; /** @@ -81,27 +81,27 @@ typedef struct pqueue_t * * @return the handle or NULL for insufficient memory */ -pqueue_t * -pqueue_init(unsigned int n, - pqueue_cmp_pri_f cmppri, - pqueue_get_pri_f getpri, - pqueue_set_pri_f setpri, - pqueue_get_pos_f getpos, - pqueue_set_pos_f setpos); +prqueue_t * +prqueue_init(unsigned int n, + prqueue_cmp_pri_f cmppri, + prqueue_get_pri_f getpri, + prqueue_set_pri_f setpri, + prqueue_get_pos_f getpos, + prqueue_set_pos_f setpos); /** * free all memory used by the queue * @param q the queue */ -void pqueue_free(pqueue_t *q); +void prqueue_free(prqueue_t *q); /** * return the size of the queue. * @param q the queue */ -unsigned int pqueue_size(pqueue_t *q); +unsigned int prqueue_size(prqueue_t *q); /** @@ -110,7 +110,7 @@ unsigned int pqueue_size(pqueue_t *q); * @param d the item * @return 0 on success */ -int pqueue_insert(pqueue_t *q, void *d); +int prqueue_insert(prqueue_t *q, void *d); /** @@ -120,8 +120,8 @@ int pqueue_insert(pqueue_t *q, void *d); * @param d the entry */ void -pqueue_change_priority(pqueue_t *q, - pqueue_pri_t new_pri, +prqueue_change_priority(prqueue_t *q, + prqueue_pri_t new_pri, void *d); @@ -130,7 +130,7 @@ pqueue_change_priority(pqueue_t *q, * @param q the queue * @return NULL on error, otherwise the entry */ -void *pqueue_pop(pqueue_t *q); +void *prqueue_pop(prqueue_t *q); /** @@ -139,7 +139,7 @@ void *pqueue_pop(pqueue_t *q); * @param d the entry * @return 0 on success */ -int pqueue_remove(pqueue_t *q, void *d); +int prqueue_remove(prqueue_t *q, void *d); /** @@ -147,7 +147,7 @@ int pqueue_remove(pqueue_t *q, void *d); * @param q the queue * @return NULL on error, otherwise the entry */ -void *pqueue_peek(pqueue_t *q); +void *prqueue_peek(prqueue_t *q); /** @@ -159,7 +159,7 @@ void *pqueue_peek(pqueue_t *q); * @param the callback function to print the entry */ void -pqueue_print(pqueue_t *q, FILE *out, pqueue_print_entry_f print); +prqueue_print(prqueue_t *q, FILE *out, prqueue_print_entry_f print); /** @@ -170,7 +170,7 @@ pqueue_print(pqueue_t *q, FILE *out, pqueue_print_entry_f print); * @param out the output handle * @param the callback function to print the entry */ -void pqueue_dump(pqueue_t *q, FILE *out, pqueue_print_entry_f print); +void prqueue_dump(prqueue_t *q, FILE *out, prqueue_print_entry_f print); /** @@ -179,7 +179,7 @@ void pqueue_dump(pqueue_t *q, FILE *out, pqueue_print_entry_f print); * debug function only * @param q the queue */ -int pqueue_is_valid(pqueue_t *q); +int prqueue_is_valid(prqueue_t *q); #endif /** @} */ diff --git a/devtools/ci/nagios/lib/squeue.h b/devtools/ci/nagios/lib/squeue.h index 9819c03..f68105b 100644 --- a/devtools/ci/nagios/lib/squeue.h +++ b/devtools/ci/nagios/lib/squeue.h @@ -2,12 +2,12 @@ #define LIBNAGIOS_SQUEUE_H_INCLUDED #include #include -#include "pqueue.h" +#include "prqueue.h" /** * @file squeue.h * @brief Scheduling queue function declarations * - * This library is based on the pqueue api, which implements a + * This library is based on the prqueue api, which implements a * priority queue based on a binary heap, providing O(lg n) times * for insert() and remove(), and O(1) time for peek(). * @note There is no "find". Callers must maintain pointers to their @@ -18,10 +18,10 @@ /* * All opaque types here. - * The pqueue library can be useful on its own though, so we + * The prqueue library can be useful on its own though, so we * don't block that from user view. */ -typedef pqueue_t squeue_t; +typedef prqueue_t squeue_t; struct squeue_event; typedef struct squeue_event squeue_event; @@ -62,7 +62,7 @@ extern squeue_t *squeue_create(unsigned int size); /** * Destroys a scheduling queue completely * @param[in] q The doomed queue - * @param[in] flags Flags determining the the level of destruction + * @param[in] flags Flags determining the level of destruction */ extern void squeue_destroy(squeue_t *q, int flags); diff --git a/devtools/ci/nagios/nagios.h b/devtools/ci/nagios/nagios.h index e1e5595..d300f6f 100644 --- a/devtools/ci/nagios/nagios.h +++ b/devtools/ci/nagios/nagios.h @@ -326,6 +326,7 @@ extern struct load_control loadctl; #define NOTIFICATION_DOWNTIMEEND 6 #define NOTIFICATION_DOWNTIMECANCELLED 7 #define NOTIFICATION_CUSTOM 8 +#define NOTIFICATION_RECOVERY 9 @@ -353,25 +354,27 @@ extern struct load_control loadctl; /******************* EVENT TYPES **********************/ -#define EVENT_SERVICE_CHECK 0 /* active service check */ -#define EVENT_COMMAND_CHECK 1 /* external command check */ -#define EVENT_LOG_ROTATION 2 /* log file rotation */ -#define EVENT_PROGRAM_SHUTDOWN 3 /* program shutdown */ -#define EVENT_PROGRAM_RESTART 4 /* program restart */ -#define EVENT_CHECK_REAPER 5 /* reaps results from host and service checks */ -#define EVENT_ORPHAN_CHECK 6 /* checks for orphaned hosts and services */ -#define EVENT_RETENTION_SAVE 7 /* save (dump) retention data */ -#define EVENT_STATUS_SAVE 8 /* save (dump) status data */ -#define EVENT_SCHEDULED_DOWNTIME 9 /* scheduled host or service downtime */ -#define EVENT_SFRESHNESS_CHECK 10 /* checks service result "freshness" */ -#define EVENT_EXPIRE_DOWNTIME 11 /* checks for (and removes) expired scheduled downtime */ -#define EVENT_HOST_CHECK 12 /* active host check */ -#define EVENT_HFRESHNESS_CHECK 13 /* checks host result "freshness" */ -#define EVENT_RESCHEDULE_CHECKS 14 /* adjust scheduling of host and service checks */ -#define EVENT_EXPIRE_COMMENT 15 /* removes expired comments */ -#define EVENT_CHECK_PROGRAM_UPDATE 16 /* checks for new version of Nagios */ -#define EVENT_SLEEP 98 /* asynchronous sleep event that occurs when event queues are empty */ -#define EVENT_USER_FUNCTION 99 /* USER-defined function (modules) */ +#define EVENT_SERVICE_CHECK 0 /* active service check */ +#define EVENT_COMMAND_CHECK 1 /* external command check */ +#define EVENT_LOG_ROTATION 2 /* log file rotation */ +#define EVENT_PROGRAM_SHUTDOWN 3 /* program shutdown */ +#define EVENT_PROGRAM_RESTART 4 /* program restart */ +#define EVENT_CHECK_REAPER 5 /* reaps results from host and service checks */ +#define EVENT_ORPHAN_CHECK 6 /* checks for orphaned hosts and services */ +#define EVENT_RETENTION_SAVE 7 /* save (dump) retention data */ +#define EVENT_STATUS_SAVE 8 /* save (dump) status data */ +#define EVENT_SCHEDULED_DOWNTIME 9 /* scheduled host or service downtime */ +#define EVENT_SFRESHNESS_CHECK 10 /* checks service result "freshness" */ +#define EVENT_EXPIRE_DOWNTIME 11 /* checks for (and removes) expired scheduled downtime */ +#define EVENT_HOST_CHECK 12 /* active host check */ +#define EVENT_HFRESHNESS_CHECK 13 /* checks host result "freshness" */ +#define EVENT_RESCHEDULE_CHECKS 14 /* adjust scheduling of host and service checks */ +#define EVENT_EXPIRE_COMMENT 15 /* removes expired comments */ +#define EVENT_CHECK_PROGRAM_UPDATE 16 /* checks for new version of Nagios */ +#define EVENT_SCHEDULED_DOWNTIME_START 17 /* start scheduled host or service downtime */ +#define EVENT_SCHEDULED_DOWNTIME_END 18 /* end scheduled host or service downtime */ +#define EVENT_SLEEP 98 /* asynchronous sleep event that occurs when event queues are empty */ +#define EVENT_USER_FUNCTION 99 /* USER-defined function (modules) */ /* * VERSIONFIX: Make EVENT_SLEEP and EVENT_USER_FUNCTION appear @@ -396,6 +399,8 @@ extern struct load_control loadctl; type == EVENT_RESCHEDULE_CHECKS ? "RESCHEDULE_CHECKS" : \ type == EVENT_EXPIRE_COMMENT ? "EXPIRE_COMMENT" : \ type == EVENT_CHECK_PROGRAM_UPDATE ? "CHECK_PROGRAM_UPDATE" : \ + type == EVENT_SCHEDULED_DOWNTIME_START ? "SCHEDULED_DOWNTIME_START" : \ + type == EVENT_SCHEDULED_DOWNTIME_END ? "SCHEDULED_DOWNTIME_END" : \ type == EVENT_SLEEP ? "SLEEP" : \ type == EVENT_USER_FUNCTION ? "USER_FUNCTION" : \ "UNKNOWN" \ @@ -513,10 +518,10 @@ void adjust_timestamp_for_time_change(time_t, time_t, unsigned long, time_t *); /**** IPC Functions ****/ -int process_check_result_queue(char *); -int process_check_result_file(char *); +int process_check_result_queue(const char *); +int process_check_result_file(const char *); int process_check_result(check_result *); -int delete_check_result_file(char *); +int delete_check_result_file(const char *); int init_check_result(check_result *); int free_check_result(check_result *); /* frees memory associated with a host/service check result */ int parse_check_output(char *, char **, char **, char **, int, int); @@ -593,9 +598,11 @@ int obsessive_compulsive_host_check_processor(host *); /* distributed monitorin int handle_service_event(service *); /* top level service event logic */ int run_service_event_handler(nagios_macros *mac, service *); /* runs the event handler for a specific service */ int run_global_service_event_handler(nagios_macros *mac, service *); /* runs the global service event handler */ +int check_service_event_handler_viability(int, service *); /* checks if service event handler can be run */ int handle_host_event(host *); /* top level host event logic */ int run_host_event_handler(nagios_macros *mac, host *); /* runs the event handler for a specific host */ int run_global_host_event_handler(nagios_macros *mac, host *); /* runs the global host event handler */ +int check_host_event_handler_viability(int, host *); /* checks if host event handler can be run */ /**** Notification Functions ****/ diff --git a/devtools/ci/nagios/netutils.h b/devtools/ci/nagios/netutils.h index 1407e3f..b5d40d5 100644 --- a/devtools/ci/nagios/netutils.h +++ b/devtools/ci/nagios/netutils.h @@ -1,7 +1,19 @@ #ifndef NAGIOS_NETUGILS_H_INCLUDED #define NAGIOS_NETUGILS_H_INCLUDED #include "common.h" + +#ifdef HAVE_SSL +#include +#endif + NAGIOS_BEGIN_DECL + +#ifdef HAVE_SSL +int my_ssl_connect(const char *host_name, int port, int *sd, SSL **ssl, SSL_CTX **ctx, int timeout); +int my_ssl_sendall(int sd, SSL *ssl, const char *buf, int *len, int timeout); +int my_ssl_recvall(int s, SSL *ssl, char *buf, int *len, int timeout); +#endif + int my_tcp_connect(const char *host_name, int port, int *sd, int timeout); int my_sendall(int s, const char *buf, int *len, int timeout); int my_recvall(int s, char *buf, int *len, int timeout); diff --git a/devtools/ci/nagios/objects.h b/devtools/ci/nagios/objects.h index dd7f767..aa644b5 100644 --- a/devtools/ci/nagios/objects.h +++ b/devtools/ci/nagios/objects.h @@ -163,7 +163,7 @@ typedef struct check_result { int check_options; int scheduled_check; /* was this a scheduled or an on-demand check? */ int reschedule_check; /* should we reschedule the next check */ - char *output_file; /* what file is the output stored in? */ + const char *output_file; /* what file is the output stored in? */ FILE *output_file_fp; double latency; struct timeval start_time; /* time the service check was initiated */ @@ -416,6 +416,7 @@ struct host { double retry_interval; int max_attempts; char *event_handler; + char *event_handler_period; struct contactgroupsmember *contact_groups; struct contactsmember *contacts; double notification_interval; @@ -516,6 +517,7 @@ struct host { struct command *event_handler_ptr; struct command *check_command_ptr; + struct timeperiod *event_handler_period_ptr; struct timeperiod *check_period_ptr; struct timeperiod *notification_period_ptr; struct objectlist *hostgroups_ptr; @@ -550,6 +552,7 @@ struct service { struct servicesmember *children; char *check_command; char *event_handler; + char *event_handler_period; int initial_state; double check_interval; double retry_interval; @@ -642,6 +645,7 @@ struct service { char *check_command_args; struct timeperiod *check_period_ptr; struct timeperiod *notification_period_ptr; + struct timeperiod *event_handler_period_ptr; struct objectlist *servicegroups_ptr; struct objectlist *exec_deps, *notify_deps; struct objectlist *escalation_list; @@ -749,7 +753,7 @@ struct contact *add_contact(char *name, char *alias, char *email, char *pager, c struct commandsmember *add_service_notification_command_to_contact(contact *, char *); /* adds a service notification command to a contact definition */ struct commandsmember *add_host_notification_command_to_contact(contact *, char *); /* adds a host notification command to a contact definition */ struct customvariablesmember *add_custom_variable_to_contact(contact *, char *, char *); /* adds a custom variable to a service definition */ -struct host *add_host(char *name, char *display_name, char *alias, char *address, char *check_period, int initial_state, double check_interval, double retry_interval, int max_attempts, int notification_options, double notification_interval, double first_notification_delay, char *notification_period, int notifications_enabled, char *check_command, int checks_enabled, int accept_passive_checks, char *event_handler, int event_handler_enabled, int flap_detection_enabled, double low_flap_threshold, double high_flap_threshold, int flap_detection_options, int stalking_options, int process_perfdata, int check_freshness, int freshness_threshold, char *notes, char *notes_url, char *action_url, char *icon_image, char *icon_image_alt, char *vrml_image, char *statusmap_image, int x_2d, int y_2d, int have_2d_coords, double x_3d, double y_3d, double z_3d, int have_3d_coords, int should_be_drawn, int retain_status_information, int retain_nonstatus_information, int obsess_over_host, unsigned int hourly_value); +struct host *add_host(char *name, char *display_name, char *alias, char *address, char *check_period, int initial_state, double check_interval, double retry_interval, int max_attempts, int notification_options, double notification_interval, double first_notification_delay, char *notification_period, int notifications_enabled, char *check_command, int checks_enabled, int accept_passive_checks, char *event_handler, int event_handler_enabled, char *event_handler_period, int flap_detection_enabled, double low_flap_threshold, double high_flap_threshold, int flap_detection_options, int stalking_options, int process_perfdata, int check_freshness, int freshness_threshold, char *notes, char *notes_url, char *action_url, char *icon_image, char *icon_image_alt, char *vrml_image, char *statusmap_image, int x_2d, int y_2d, int have_2d_coords, double x_3d, double y_3d, double z_3d, int have_3d_coords, int should_be_drawn, int retain_status_information, int retain_nonstatus_information, int obsess_over_host, unsigned int hourly_value); struct hostsmember *add_parent_host_to_host(host *, char *); /* adds a parent host to a host definition */ struct servicesmember *add_parent_service_to_service(service *, char *host_name, char *description); struct hostsmember *add_child_link_to_host(host *, host *); /* adds a child host to a host definition */ @@ -769,7 +773,7 @@ struct servicesmember *add_service_to_servicegroup(servicegroup *, char *, char struct contactgroup *add_contactgroup(char *, char *); /* adds a contactgroup definition */ struct contactsmember *add_contact_to_contactgroup(contactgroup *, char *); /* adds a contact to a contact group definition */ struct command *add_command(char *, char *); /* adds a command definition */ -struct service *add_service(char *host_name, char *description, char *display_name, char *check_period, int initial_state, int max_attempts, int parallelize, int accept_passive_checks, double check_interval, double retry_interval, double notification_interval, double first_notification_delay, char *notification_period, int notification_options, int notifications_enabled, int is_volatile, char *event_handler, int event_handler_enabled, char *check_command, int checks_enabled, int flap_detection_enabled, double low_flap_threshold, double high_flap_threshold, int flap_detection_options, int stalking_options, int process_perfdata, int check_freshness, int freshness_threshold, char *notes, char *notes_url, char *action_url, char *icon_image, char *icon_image_alt, int retain_status_information, int retain_nonstatus_information, int obsess_over_service, unsigned int hourly_value); +struct service *add_service(char *host_name, char *description, char *display_name, char *check_period, int initial_state, int max_attempts, int parallelize, int accept_passive_checks, double check_interval, double retry_interval, double notification_interval, double first_notification_delay, char *notification_period, int notification_options, int notifications_enabled, int is_volatile, char *event_handler, int event_handler_enabled, char *event_handler_period, char *check_command, int checks_enabled, int flap_detection_enabled, double low_flap_threshold, double high_flap_threshold, int flap_detection_options, int stalking_options, int process_perfdata, int check_freshness, int freshness_threshold, char *notes, char *notes_url, char *action_url, char *icon_image, char *icon_image_alt, int retain_status_information, int retain_nonstatus_information, int obsess_over_service, unsigned int hourly_value); struct contactgroupsmember *add_contactgroup_to_service(service *, char *); /* adds a contact group to a service definition */ struct contactsmember *add_contact_to_service(service *, char *); /* adds a contact to a host definition */ struct serviceescalation *add_serviceescalation(char *host_name, char *description, int first_notification, int last_notification, double notification_interval, char *escalation_period, int escalation_options); diff --git a/devtools/ci/nagios/shared.h b/devtools/ci/nagios/shared.h index 84836c7..3ec7a9a 100644 --- a/devtools/ci/nagios/shared.h +++ b/devtools/ci/nagios/shared.h @@ -53,5 +53,7 @@ extern void get_datetime_string(time_t *raw_time, char *buffer, extern void get_time_breakdown(unsigned long raw_time, int *days, int *hours, int *minutes, int *seconds); +extern void ensure_path_separator(char *path, size_t size); + NAGIOS_END_DECL #endif From 5b57553c007e6c45c1b5ad8a998999c561cee188 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 22:23:26 +0200 Subject: [PATCH 20/28] Build and run the test suite against nagios too The suite only ever compiled in the naemon configuration. Its stubs assumed naemon's API, so -Dnagios=true failed to build long before it could fail a test, and the nagios code paths - every #ifdef BUILD_NAGIOS branch - had no coverage at all. Three differences had to be handled: nagios calls the comment struct nagios_comment, its get_program_version() returns a non const char*, and it routes nm_log() through an inline in Nebmodule.h to write_to_all_logs(), so that is where the log has to be captured for the assertions to see it. The check_result stub no longer touches output_file, which the broker never sets and which nagios declares const while naemon does not. The nagios CI job now runs the tests instead of only building, so this does not quietly rot again. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- .github/workflows/build.yml | 5 +++-- tests/naemon_stubs.cpp | 30 ++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f07fb8..415b7df 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,11 +85,12 @@ jobs: mkdir -p /opt/nagios/include ln -s "$(readlink -f devtools/ci/nagios)" /opt/nagios/include/nagios - - name: Build + - name: Build and test run: | - meson setup --buildtype=release -Dnagios=true \ + meson setup --buildtype=release -Dtests=true -Dnagios=true \ -Dnagios_include_dir=/opt/nagios/include build ninja -C build + meson test -C build --print-errorlogs tests: name: unit tests (${{ matrix.sanitize && 'sanitized' || 'plain' }}) diff --git a/tests/naemon_stubs.cpp b/tests/naemon_stubs.cpp index b5d650f..e8e4127 100644 --- a/tests/naemon_stubs.cpp +++ b/tests/naemon_stubs.cpp @@ -14,6 +14,15 @@ std::vector capturedLogs; +#ifdef BUILD_NAGIOS +// Under nagios, Nebmodule.h provides nm_log() as an inline that forwards here, so this is +// where the log has to be captured for the assertions to see anything. +extern "C" int write_to_all_logs(char *message, unsigned long) { + capturedLogs.emplace_back(message != nullptr ? message : ""); + return 0; +} +#endif + // Signature has to match naemon/logging.h exactly. extern "C" void nm_log(int, const char *fmt, ...) { char buffer[4096]; @@ -44,6 +53,9 @@ extern "C" int clear_volatile_macros_r(nagios_macros *) { return 0; } +// The two cores differ here: naemon has "struct comment" and a const char* version +// string, nagios has "struct nagios_comment" and a non const one. +#ifndef BUILD_NAGIOS extern "C" struct comment *find_service_comment(unsigned long) { return nullptr; } @@ -55,6 +67,20 @@ extern "C" struct comment *find_host_comment(unsigned long) { extern "C" const char *get_program_version(void) { return "test"; } +#else +extern "C" struct nagios_comment *find_service_comment(unsigned long) { + return nullptr; +} + +extern "C" struct nagios_comment *find_host_comment(unsigned long) { + return nullptr; +} + +extern "C" char *get_program_version(void) { + static char version[] = "test"; + return version; +} +#endif namespace statusengine { // Defined here instead of linking Nebmodule.cpp, which would pull in the whole naemon @@ -76,14 +102,14 @@ extern "C" int init_check_result(check_result *cr) { } extern "C" int free_check_result(check_result *cr) { + // output_file is left alone: the broker never sets it, and nagios declares it const + // while naemon does not. free(cr->host_name); free(cr->service_description); free(cr->output); - free(cr->output_file); cr->host_name = nullptr; cr->service_description = nullptr; cr->output = nullptr; - cr->output_file = nullptr; return 0; } From 4b31cd8792f7d71704170a17e66a5e32bdd59978 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 22:23:41 +0200 Subject: [PATCH 21/28] Add end_time to the acknowledgement message An acknowledgement can be set to expire, and until now the message did not say when. naemon carries it in nebstruct_acknowledgement_data as end_time; nagios has no such member. Reported as null under nagios rather than 0, which is what makes this worth a paragraph. Under naemon 0 is not a missing value, it is a real and common one meaning "does not expire" - confirmed against a running naemon, where an acknowledgement set with ACKNOWLEDGE_SVC_PROBLEM_EXPIRE produced end_time=1787692952 and a plain ACKNOWLEDGE_SVC_PROBLEM produced end_time=0. A 0 for nagios would have been indistinguishable from that, and a consumer reading it as an expiry time would be wrong in both directions. null says "this core cannot tell you", which is the truth. The key is always present either way, so consumers do not have to special case its absence. NagiosObject gains an explicit SetNull() for this - writing a null through the const char* overload works but reads like an accident. The recorded fixture is updated along with it, since the message shape is deliberately changing, and the golden test would otherwise be right to fail. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- CHANGELOG.md | 8 +++++ README.md | 11 +++++++ src/NagiosObject.h | 20 +++++++++++++ .../fixtures/statusngin_acknowledgements.json | 5 ++-- tests/test_messageformat.cpp | 30 +++++++++++++++++++ 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a74799..0812815 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,12 +65,20 @@ set of memory errors that could take the monitoring core down with them. - Nagios support is a deprecation candidate. It still builds and is still tested in CI; if you rely on it, please say so in an issue. +- **`AcknowledgementData` now carries `end_time`**, the point at which an acknowledgement + expires. Under naemon this is the value from the core, where `0` means it does not + expire. Under nagios the field is `null`, because the struct has no such member there - + deliberately not `0`, which would be indistinguishable from naemon's "does not expire". + ### Added - A unit test suite, run in CI both plain and under the address and undefined behaviour sanitizers. The recorded message formats are pinned against fixtures, so an accidental change to the wire format fails the build. - Micro benchmarks for the message paths, see the Performance section in the README. +- The vendored naemon and nagios headers used for building without an installed core were + six years old (naemon 1.0.6, nagios 4.4.5) and are now naemon 1.5.2 and nagios 4.5.14. + The test suite also builds and runs against the nagios headers now, which it never did. - A GitHub Actions workflow covering current Ubuntu and Debian releases and all build options. The configuration it replaces still invoked cmake, which the project dropped in 2020, was GitLab CI in a repository hosted on GitHub, and targeted distributions that diff --git a/README.md b/README.md index e03729c..d1b7f30 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,17 @@ Messages are serialised without insignificant whitespace (`{"a":1}` rather than whitespace only difference - the JSON is unchanged and any conformant parser reads it identically. Do not match on the raw payload text. +### `end_time` on acknowledgements + +The `AcknowledgementData` message carries `end_time`, the point at which the +acknowledgement expires. The field is always present: + +* Under naemon it holds the value from the core, where `0` means the + acknowledgement does not expire. +* Under nagios it is `null`, because `nebstruct_acknowledgement_data` has no such + member there. It is deliberately not `0`, which would be indistinguishable from + naemon's "does not expire". + ### `timestamp` on core restart The `RestartData` message carries the unix timestamp of the restart: diff --git a/src/NagiosObject.h b/src/NagiosObject.h index 0df1b78..67d4f79 100644 --- a/src/NagiosObject.h +++ b/src/NagiosObject.h @@ -118,6 +118,18 @@ namespace statusengine { SetData(data, name, other); } + /** + * Writes a JSON null. Used where a field belongs in the message but the monitoring + * core cannot supply it, which is different from the core supplying a zero. + */ + inline void SetNull(const char *name) { + SetNull(data, name); + } + + inline static void SetNull(json_object *obj, const char *name) { + json_object_object_add(obj, name, nullptr); + } + inline static void SetData(json_object *obj, const char *name, json_object *other) { json_object_object_add(obj, name, other); } @@ -176,6 +188,14 @@ namespace statusengine { acknowledgement.SetData("is_sticky", acknowledgementData->is_sticky); acknowledgement.SetData("persistent_comment", acknowledgementData->persistent_comment); acknowledgement.SetData("notify_contacts", acknowledgementData->notify_contacts); +#ifndef BUILD_NAGIOS + acknowledgement.SetData("end_time", acknowledgementData->end_time); +#else + // Nagios has no end_time in nebstruct_acknowledgement_data. Reported as null + // rather than 0, because under naemon 0 is a real value meaning "never + // expires" - a 0 here would be indistinguishable from that. + acknowledgement.SetNull("end_time"); +#endif SetData("acknowledgement", &acknowledgement); } diff --git a/tests/fixtures/statusngin_acknowledgements.json b/tests/fixtures/statusngin_acknowledgements.json index d46d38c..4392657 100644 --- a/tests/fixtures/statusngin_acknowledgements.json +++ b/tests/fixtures/statusngin_acknowledgements.json @@ -13,6 +13,7 @@ "state": 2, "is_sticky": 0, "persistent_comment": 1, - "notify_contacts": 1 + "notify_contacts": 1, + "end_time": 0 } -} \ No newline at end of file +} diff --git a/tests/test_messageformat.cpp b/tests/test_messageformat.cpp index ed41633..179315d 100644 --- a/tests/test_messageformat.cpp +++ b/tests/test_messageformat.cpp @@ -223,6 +223,36 @@ TEST_CASE("acknowledgement message keeps its shape") { CheckShape(msg, "statusngin_acknowledgements.json", "acknowledgement"); } +TEST_CASE("acknowledgement carries the end time") { + nebstruct_acknowledgement_data data; + FillHeader(data, NEBTYPE_ACKNOWLEDGEMENT_ADD); + data.host_name = Str("localhost"); + data.author_name = Str("admin"); + data.comment_data = Str("until tomorrow"); +#ifndef BUILD_NAGIOS + data.end_time = 1785470668; +#endif + + NagiosAcknowledgementData msg(&data); + json_object *parsed = json_tokener_parse(Rendered(msg).c_str()); + REQUIRE(parsed != nullptr); + + json_object *ack = nullptr; + REQUIRE(json_object_object_get_ex(parsed, "acknowledgement", &ack)); + json_object *endTime = nullptr; + // The key is always present, so consumers can rely on it regardless of the core. + REQUIRE(json_object_object_get_ex(ack, "end_time", &endTime)); + +#ifndef BUILD_NAGIOS + CHECK(json_object_get_int64(endTime) == 1785470668); +#else + // Nagios cannot supply it. Null, not 0, because 0 means "never expires" under naemon. + CHECK(endTime == nullptr); +#endif + + json_object_put(parsed); +} + TEST_CASE("downtime message keeps its shape") { nebstruct_downtime_data data; FillHeader(data, NEBTYPE_DOWNTIME_ADD); From e18e7384cfea00781ff2fee263779525bd2994b1 Mon Sep 17 00:00:00 2001 From: nook24 Date: Tue, 25 Aug 2026 23:58:10 +0200 Subject: [PATCH 22/28] Spell out the end_time difference between the two cores in the README The section already said null was not the same as 0, but only in prose. Someone writing a consumer has to decide what to do with each of the three cases, so they are now a table with the actual values, followed by what each one means for the reader. The concrete values are the ones observed against a running naemon: ACKNOWLEDGE_SVC_PROBLEM_EXPIRE gives a timestamp, ACKNOWLEDGE_SVC_PROBLEM gives 0, nagios gives null. Also records that this is the only field where the two cores produce different output. That is checked, not assumed: NagiosObject.h, which builds every message, contains exactly one BUILD_NAGIOS guard and it is this one. The remaining guards in the project are in Nebmodule and cover scheduling, downtime deletion and logging, none of which reach the message format. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- README.md | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d1b7f30..52b6bb2 100644 --- a/README.md +++ b/README.md @@ -128,14 +128,33 @@ reads it identically. Do not match on the raw payload text. ### `end_time` on acknowledgements -The `AcknowledgementData` message carries `end_time`, the point at which the -acknowledgement expires. The field is always present: - -* Under naemon it holds the value from the core, where `0` means the - acknowledgement does not expire. -* Under nagios it is `null`, because `nebstruct_acknowledgement_data` has no such - member there. It is deliberately not `0`, which would be indistinguishable from - naemon's "does not expire". +The `AcknowledgementData` message carries `end_time`, the unix timestamp at which +the acknowledgement expires. The key is always present, so a consumer never has +to handle it being absent - but it has three distinct meanings, and `0` is not +the same as `null`: + +| Core | Acknowledgement | `end_time` | Meaning | +|---|---|---|---| +| naemon | set with an expiry | `1787692952` | expires at that timestamp | +| naemon | set without one | `0` | never expires | +| nagios | either | `null` | the core cannot tell you | + +Under naemon `0` is a real value, not a missing one: `ACKNOWLEDGE_SVC_PROBLEM` +produces `0`, `ACKNOWLEDGE_SVC_PROBLEM_EXPIRE` produces the timestamp. Nagios has +no `end_time` member in `nebstruct_acknowledgement_data` at all, so reporting `0` +there would be indistinguishable from naemon's "never expires" and a consumer +reading it as an expiry time would be wrong either way. `null` says the +information does not exist. + +For a consumer this means: + +* `null` - do not infer anything about expiry; the acknowledgement may or may not + have one, this core does not report it. +* `0` - the acknowledgement does not expire. +* anything else - a unix timestamp, treat it as the expiry. + +Note that this is the only field where the two cores differ. Everywhere else both +produce the same shape. ### `timestamp` on core restart From 7d714e92d9fa7b871ed3a9be24715c4b401028ad Mon Sep 17 00:00:00 2001 From: nook24 Date: Wed, 26 Aug 2026 16:22:41 +0200 Subject: [PATCH 23/28] Report end_time as 0 under nagios instead of null Nagios has no expiring acknowledgements - the feature does not exist, which is why nebstruct_acknowledgement_data has no end_time member there. Reporting null treated that as missing information, but it is not missing: every nagios acknowledgement genuinely never expires, and 0 already means exactly that under naemon. So the field is now always an integer with one meaning across both cores, and neither a consumer nor a database schema needs to tell them apart. SetNull() went with it. It was added for this case and nothing else used it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- CHANGELOG.md | 5 +++-- README.md | 37 +++++++++++++++--------------------- src/NagiosObject.h | 21 +++++--------------- tests/test_messageformat.cpp | 5 +++-- 4 files changed, 26 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0812815..9836bf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,8 +67,9 @@ set of memory errors that could take the monitoring core down with them. - **`AcknowledgementData` now carries `end_time`**, the point at which an acknowledgement expires. Under naemon this is the value from the core, where `0` means it does not - expire. Under nagios the field is `null`, because the struct has no such member there - - deliberately not `0`, which would be indistinguishable from naemon's "does not expire". + expire. Under nagios it is always `0`: nagios has no expiring acknowledgements, so that + is not a placeholder but the truth for every acknowledgement it reports. Consumers need + no case distinction between the two cores. ### Added diff --git a/README.md b/README.md index 52b6bb2..ce931e0 100644 --- a/README.md +++ b/README.md @@ -129,32 +129,25 @@ reads it identically. Do not match on the raw payload text. ### `end_time` on acknowledgements The `AcknowledgementData` message carries `end_time`, the unix timestamp at which -the acknowledgement expires. The key is always present, so a consumer never has -to handle it being absent - but it has three distinct meanings, and `0` is not -the same as `null`: +the acknowledgement expires. The key is always present and always an integer, so +a consumer never has to handle it being absent or null: | Core | Acknowledgement | `end_time` | Meaning | |---|---|---|---| | naemon | set with an expiry | `1787692952` | expires at that timestamp | -| naemon | set without one | `0` | never expires | -| nagios | either | `null` | the core cannot tell you | - -Under naemon `0` is a real value, not a missing one: `ACKNOWLEDGE_SVC_PROBLEM` -produces `0`, `ACKNOWLEDGE_SVC_PROBLEM_EXPIRE` produces the timestamp. Nagios has -no `end_time` member in `nebstruct_acknowledgement_data` at all, so reporting `0` -there would be indistinguishable from naemon's "never expires" and a consumer -reading it as an expiry time would be wrong either way. `null` says the -information does not exist. - -For a consumer this means: - -* `null` - do not infer anything about expiry; the acknowledgement may or may not - have one, this core does not report it. -* `0` - the acknowledgement does not expire. -* anything else - a unix timestamp, treat it as the expiry. - -Note that this is the only field where the two cores differ. Everywhere else both -produce the same shape. +| naemon | set without one | `0` | does not expire | +| nagios | any | `0` | does not expire | + +`0` means the same thing in both cases, so no distinction between the two cores is +needed anywhere - in a consumer or in a database schema. Under naemon `0` is a +real value and not a missing one: `ACKNOWLEDGE_SVC_PROBLEM` produces `0`, +`ACKNOWLEDGE_SVC_PROBLEM_EXPIRE` produces the timestamp. Nagios has no `end_time` +member in `nebstruct_acknowledgement_data` because it has no expiring +acknowledgements at all, so `0` is not a stand-in there either - a nagios +acknowledgement genuinely never expires. + +For a consumer this means: `0` - does not expire; anything else - a unix +timestamp, treat it as the expiry. ### `timestamp` on core restart diff --git a/src/NagiosObject.h b/src/NagiosObject.h index 67d4f79..f5cf859 100644 --- a/src/NagiosObject.h +++ b/src/NagiosObject.h @@ -118,18 +118,6 @@ namespace statusengine { SetData(data, name, other); } - /** - * Writes a JSON null. Used where a field belongs in the message but the monitoring - * core cannot supply it, which is different from the core supplying a zero. - */ - inline void SetNull(const char *name) { - SetNull(data, name); - } - - inline static void SetNull(json_object *obj, const char *name) { - json_object_object_add(obj, name, nullptr); - } - inline static void SetData(json_object *obj, const char *name, json_object *other) { json_object_object_add(obj, name, other); } @@ -191,10 +179,11 @@ namespace statusengine { #ifndef BUILD_NAGIOS acknowledgement.SetData("end_time", acknowledgementData->end_time); #else - // Nagios has no end_time in nebstruct_acknowledgement_data. Reported as null - // rather than 0, because under naemon 0 is a real value meaning "never - // expires" - a 0 here would be indistinguishable from that. - acknowledgement.SetNull("end_time"); + // Nagios has no end_time in nebstruct_acknowledgement_data, because it has no + // expiring acknowledgements at all. 0 is therefore not a placeholder here: it + // carries the same meaning it does under naemon, "does not expire", which is + // true of every nagios acknowledgement. Consumers need no case distinction. + acknowledgement.SetData("end_time", static_cast(0)); #endif SetData("acknowledgement", &acknowledgement); diff --git a/tests/test_messageformat.cpp b/tests/test_messageformat.cpp index 179315d..51c1ce5 100644 --- a/tests/test_messageformat.cpp +++ b/tests/test_messageformat.cpp @@ -246,8 +246,9 @@ TEST_CASE("acknowledgement carries the end time") { #ifndef BUILD_NAGIOS CHECK(json_object_get_int64(endTime) == 1785470668); #else - // Nagios cannot supply it. Null, not 0, because 0 means "never expires" under naemon. - CHECK(endTime == nullptr); + // Nagios has no expiring acknowledgements, so 0 - "does not expire" - is not a + // placeholder here but the truth for every nagios acknowledgement. + CHECK(json_object_get_int64(endTime) == 0); #endif json_object_put(parsed); From 181a21397468445007f5b2bb7a2cb69dbb70e44c Mon Sep 17 00:00:00 2001 From: nook24 Date: Wed, 26 Aug 2026 16:22:57 +0200 Subject: [PATCH 24/28] Stop the worker loop from throttling itself to one message per tick The bound added in 48b9f3c ended the loop as soon as a full round over all handlers processed no message. That is wrong by one: draining a gearman queue produces exactly such a round per message. libgearman answers the first read after GRAB_JOB with GEARMAN_IO_WAIT because the response is still in flight, and only the following round returns the job. Ending on the first one therefore ends almost every tick before any work happens, and since the worker tick is one second, the module settled at roughly one message per second. Measured against a real gearmand 1.1.19 with the broker's exact call sequence, 50000 queued jobs: bounded on the first round 48 messages in 50 ticks unbounded (master) 49952 messages in one tick, 5.1s And end to end, naemon in docker applying 100000 check results from statusngin_cmd, queue depth sampled while draining: bounded on the first round 99872 left after 120s (128 processed, 1/s) no progress budget of 16 0 left after 120s (~830/s) The loop now counts consecutive rounds without progress and gives up after 16, so a handler that has genuinely stopped progressing still cannot spin inside naemon's event loop - the reason the bound exists. Any completed message resets the count, which is what restores throughput. 16 is headroom, not a tuned value: draining 110000 jobs with concurrent submitters and a competing worker never produced a run longer than two rounds. The 10ms poll cap is unaffected and stays. Measured with and without it, the same 50000 jobs drain in 5.14s and 5.34s, so it costs nothing while still keeping an unresponsive job server from stalling the core. The regression test is a fake handler shaped like the real worker: one round that asks for more without progressing per message, then one that delivers it. It drains under this change and stalls at zero messages without it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- CHANGELOG.md | 5 ++- src/MessageHandler/MessageHandlerList.h | 33 +++++++++++++++----- tests/test_support.h | 41 +++++++++++++++++++++++++ tests/test_workerloop.cpp | 40 ++++++++++++++++++++---- 4 files changed, 104 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9836bf8..acbe71b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,10 @@ set of memory errors that could take the monitoring core down with them. - **The worker loop could freeze naemon.** A handler reporting more work without having processed anything - which the gearman worker does when its socket would block - spun the loop forever inside naemon's event loop, so the core stopped scheduling entirely. - The gearman poll that goes with it was also unbounded and is now capped. + It now gives up after 16 rounds in a row without a processed message. Note that this + cannot be a single round: draining a queue produces one such round per message, so + ending on the first one throttles the worker to about one message per second. The + gearman poll that goes with it was also unbounded and is now capped at 10ms. - **Log levels were wrong in both directions.** At `Level = Error` warnings were still written, and at `Level = Info` - the most verbose setting - warnings were dropped. - The module title was overwritten by the copyright string in the naemon module info. diff --git a/src/MessageHandler/MessageHandlerList.h b/src/MessageHandler/MessageHandlerList.h index 130446f..cea4381 100644 --- a/src/MessageHandler/MessageHandlerList.h +++ b/src/MessageHandler/MessageHandlerList.h @@ -58,20 +58,34 @@ namespace statusengine { } /** - * Lets every handler drain its queue, until nobody has anything left or - * maxMessages have been processed. + * How many rounds in a row may end without a processed message before the loop + * gives up and lets the next tick try again. * - * A handler may ask for another round without having processed anything - the - * gearman worker does exactly that when its socket would block. Such a handler - * cannot advance the message counter, so counting messages alone does not bound - * this loop, and it runs inside naemon's event loop. A round that made no progress - * therefore ends it, and the next worker tick picks the work up again. + * This cannot be 1. A handler may legitimately ask for another round without + * having processed anything: the gearman worker does that on every single job, + * because its GRAB_JOB response is still in flight when the socket is first read + * and libgearman reports GEARMAN_IO_WAIT. Ending the loop on the first such round + * therefore ends it after roughly one message per tick. + * + * Draining 110000 jobs from a real job server - with concurrent submitters and a + * competing worker - never produced a run longer than two rounds, so this is + * almost entirely headroom. It exists only to bound a handler that has stopped + * making progress altogether, which would otherwise spin inside naemon's event + * loop and stop the monitoring core from scheduling anything at all. + */ + static constexpr unsigned maxRoundsWithoutProgress = 16; + + /** + * Lets every handler drain its queue, until nobody has anything left, maxMessages + * have been processed, or nobody has made progress for maxRoundsWithoutProgress + * rounds. * * Static and defined here so it can be exercised with fake handlers. */ static void RunWorkers(std::vector> &handlers, unsigned long maxMessages) { unsigned long counter = 0ul; + unsigned roundsWithoutProgress = 0u; bool moreMessages; do { moreMessages = false; @@ -81,7 +95,10 @@ namespace statusengine { moreMessages = true; } } - if (counter == before) { + if (counter != before) { + roundsWithoutProgress = 0u; + } + else if (++roundsWithoutProgress >= maxRoundsWithoutProgress) { break; } } while (moreMessages && (counter < maxMessages)); diff --git a/tests/test_support.h b/tests/test_support.h index 487813f..99f3a20 100644 --- a/tests/test_support.h +++ b/tests/test_support.h @@ -87,4 +87,45 @@ namespace statusengine { /// How often Worker() was entered - the guard against a spinning loop. unsigned long calls; }; + + /** + * IMessageHandler shaped like the real gearman worker: every message costs one round + * that reports "there is more" without processing anything, because the grab-job + * response is still in flight, followed by a round that delivers it. + * + * Measured against a real job server this is not an edge case, it is what draining a + * queue looks like: one GEARMAN_IO_WAIT per GEARMAN_SUCCESS, all the way down. + */ + class FakeIoWaitHandler : public IMessageHandler { + public: + explicit FakeIoWaitHandler(unsigned long messages) : remaining(messages), waiting(true), calls(0) {} + + bool Worker(unsigned long &counter) override { + ++calls; + if (remaining == 0) { + return false; + } + if (waiting) { + // GEARMAN_IO_WAIT: more work is coming, but none of it is here yet. + waiting = false; + return true; + } + // GEARMAN_SUCCESS + waiting = true; + --remaining; + ++counter; + return true; + } + + bool Connect() override { + return true; + } + void SendMessage(Queue, const std::string &) override {} + void ProcessMessage(WorkerQueue, const std::string &) override {} + void ProcessMessage(WorkerQueue, json_object *) override {} + + unsigned long remaining; + bool waiting; + unsigned long calls; + }; } // namespace statusengine diff --git a/tests/test_workerloop.cpp b/tests/test_workerloop.cpp index 1afd443..7b71eb8 100644 --- a/tests/test_workerloop.cpp +++ b/tests/test_workerloop.cpp @@ -7,6 +7,7 @@ #include "MessageHandler/MessageHandlerList.h" +using statusengine::FakeIoWaitHandler; using statusengine::FakeMessageHandler; using statusengine::IMessageHandler; using statusengine::MessageHandlerList; @@ -22,15 +23,42 @@ namespace { } // namespace TEST_CASE("a handler that asks for more without progressing does not spin the loop") { - // This is the GEARMAN_IO_WAIT shape: always "there is more", never a processed - // message, so the message counter can never end the loop. Before the loop was bounded - // by progress this ran forever, inside naemon's event loop. + // Always "there is more", never a processed message, so the message counter can never + // end the loop. Unbounded this ran forever, inside naemon's event loop. HandlerList handlers; auto stuck = Add(handlers, 0, true); MessageHandlerList::RunWorkers(handlers, 1000000ul); - CHECK(stuck->calls == 1); + CHECK(stuck->calls == MessageHandlerList::maxRoundsWithoutProgress); +} + +TEST_CASE("a worker that waits for its socket before every message still drains") { + // The regression this file exists for. A gearman worker reports GEARMAN_IO_WAIT once + // per job, so the very first round of a tick usually processes nothing. Ending the + // loop on that round cut throughput to roughly one message per tick: measured against + // a real job server, 50000 queued jobs went from being drained in a single tick to 48 + // messages in 50 ticks, and the queue grew without bound. + HandlerList handlers; + auto handler = std::make_shared(500); + handlers.push_back(handler); + + MessageHandlerList::RunWorkers(handlers, 1000000ul); + + CHECK(handler->remaining == 0); +} + +TEST_CASE("waiting for the socket does not consume the no progress budget") { + // Progress resets the budget, so a handler alternating wait and message can keep going + // indefinitely - which is exactly what draining a full queue looks like. + HandlerList handlers; + auto handler = std::make_shared(4); + handlers.push_back(handler); + + MessageHandlerList::RunWorkers(handlers, 1000000ul); + + // four waits, four messages, and the round that finds the queue empty + CHECK(handler->calls == 9); } TEST_CASE("a handler drains its queue within one tick") { @@ -63,8 +91,8 @@ TEST_CASE("a stuck handler does not stop the others") { // The loop keeps going while anyone makes progress, so the working handler drains... CHECK(busy->remaining == 0); - // ...and it ends on the first round where nobody did, rather than on the stuck one. - CHECK(stuck->calls == 4); + // ...and once nobody does, the no progress budget ends it rather than the stuck one. + CHECK(stuck->calls == 3 + MessageHandlerList::maxRoundsWithoutProgress); } TEST_CASE("a handler that goes quiet ends the loop") { From 0d9e8b3d0d9e5af9859aa3ec8032399b27932644 Mon Sep 17 00:00:00 2001 From: nook24 Date: Wed, 26 Aug 2026 18:02:44 +0200 Subject: [PATCH 25/28] Bound how long the worker may hold the monitoring core The worker runs inside naemon's event loop, and nothing bounded how long one run could take: the loop ended only when the queues were empty. Instrumenting the callback in docker showed what that means - applying 100000 queued check results kept naemon in a single callback for 128425ms. For over two minutes the core scheduled no checks, reaped no results and read no external commands. MaxWorkerMessagesPerInterval could not prevent it and never could. It counts messages as they arrive from gearman or rabbitmq, not the check results inside them: GearmanClient::Worker() increments once per job, and the bulk is unpacked a layer below in MessageHandler::ProcessMessage(), which iterates messages[] and recurses without ever seeing the counter. At its default of 1000000 and 200 check results per bulk message the real ceiling was 200 million items in one run. So RunWorkers() now takes a wall clock budget, [Worker] MaxRuntimeMilliseconds, defaulting to 100ms, and reports whether it stopped with work left over. When it did, the callback reschedules itself with schedule_event(0, ...) instead of its one second interval. That zero delay is what makes the budget free. naemon's event_poll_full() clamps the wait to zero for an event that is already due, polls its own descriptors with that timeout, and skips running the timed event entirely if any of them had input, so the core always gets a pass of its own between two slices and its I/O keeps priority over ours. Measured on identical fresh stacks, 100000 check results: budget 100ms drained in 127s, longest run 102ms budget 0 drained in 135s, longest run 132776ms The throughput difference is run to run noise. The blocking difference is a factor of 1300. naemon kept executing its own host checks throughout the bounded run. The budget is checked between messages and a message cannot be interrupted - a bulk job is already acknowledged, abandoning it half way would discard check results. The guarantee is therefore "budget plus the message in flight": with bulks of 200 the longest run was 302ms, because one such message costs ~220ms on its own. Documented rather than papered over. Hitting the budget is logged, because a broker that has to stop before the queues are empty is being handed messages faster than the core can apply them. A backlog hits it on every run, so the reporting follows the shape already used for gearman outages: once when it starts, every 60s while it lasts, once when it is over. Not applied under nagios. Its event is recurring and there is no way to ask for an earlier run, so a budget there would buy latency with throughput; the key is ignored with a warning. [Worker] is also documented for the first time - both keys existed but neither appeared in statusengine.toml or the README. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- CHANGELOG.md | 16 ++ README.md | 37 +++++ src/Configuration.h | 35 ++++- src/EventCallback.h | 16 +- src/MessageHandler/IMessageHandler.h | 3 +- src/MessageHandler/MessageHandlerList.cpp | 3 +- src/MessageHandler/MessageHandlerList.h | 136 +++++++++++++++-- src/Nebmodule.cpp | 26 +++- src/Nebmodule.h | 5 + statusengine.toml | 21 +++ tests/test_configuration.cpp | 23 +++ tests/test_support.h | 32 ++++ tests/test_workerloop.cpp | 174 ++++++++++++++++++++-- 13 files changed, 494 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acbe71b..1657bd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,20 @@ set of memory errors that could take the monitoring core down with them. was unreachable. - **Crash on startup failure.** If one message handler failed to connect, destroying the remaining ones dereferenced a connection that had never been opened. +- **A queue backlog stopped the monitoring core.** The worker runs inside naemon's event + loop and had no bound on how long one run could take: it ended only once the queues were + empty. Measured against naemon in docker, applying 100000 queued check results held the + event loop in a *single* callback for 128 seconds, during which the core scheduled + nothing. The only existing limit, `MaxWorkerMessagesPerInterval`, could not help - it + counts messages as they arrive, not the check results inside them, so at its default of + 1000000 and 200 check results per bulk message the real ceiling was 200 million items in + one run. There is now a time budget, `[Worker] MaxRuntimeMilliseconds`, defaulting to + 100ms. It costs no throughput: a run that stops early is rescheduled for naemon's very + next event loop pass rather than the next second - the same 100000 check results drain in + 127s with the budget and 135s without it, while the longest single run drops from 132776ms + to 102ms. See the README for what it does and does not guarantee, in particular that a + bulk message cannot be interrupted. Not applied under nagios, which cannot reschedule + early. - **The worker loop could freeze naemon.** A handler reporting more work without having processed anything - which the gearman worker does when its socket would block - spun the loop forever inside naemon's event loop, so the core stopped scheduling entirely. @@ -65,6 +79,8 @@ set of memory errors that could take the monitoring core down with them. - Built as C++17, and the vendored toml11 was updated from the 2018 version to 4.4.0. - The reported module version now comes from the build instead of a hardcoded string. Version numbers had drifted apart across four places. +- `[Worker]` is documented for the first time, in `statusengine.toml` and the README. + Both keys existed but neither was written down anywhere. - Nagios support is a deprecation candidate. It still builds and is still tested in CI; if you rely on it, please say so in an issue. diff --git a/README.md b/README.md index ce931e0..4b738ef 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,43 @@ broker module like this: broker_module=/opt/naemon/lib/libstatusengine.so /path/to/statusengine.toml ``` +### How long the worker may block the core + +If you consume queues (`WorkerCommand`, `WorkerOCHP`, `WorkerOCSP`), the broker applies +those results from inside the monitoring core's event loop. While it does that, the core +does nothing else - it schedules no checks, reaps no results and reads no external +commands. A backlog therefore has to be bounded, or clearing it stops monitoring. + +Two settings in `[Worker]` bound it: + +| Key | Default | Bounds | +|---|---|---| +| `MaxRuntimeMilliseconds` | `100` | wall clock time of one worker run; `0` disables | +| `MaxWorkerMessagesPerInterval` | `1000000` | messages taken off the queues in one run | + +`MaxRuntimeMilliseconds` is the one that matters. When a run stops on it with messages +still queued, the broker asks naemon to run it again on its very next event loop pass, so +the core keeps its responsiveness and the broker keeps its throughput - the gap between +two runs is a single loop iteration, not a second. + +Two caveats worth knowing: + +* **One message is indivisible.** A bulk message is a single job that has already been + acknowledged, so it cannot be abandoned half way without throwing check results away. + A run can therefore overshoot by the cost of the message it is busy with. The guarantee + is "budget plus one message", and with large bulk messages the second term is the one + that dominates: measured against naemon in docker with the default 100ms budget, single + check results gave a longest run of 102ms, while bulk messages of 200 check results gave + 302ms - one such message costs around 220ms all by itself. Both drained 100000 check + results in around two minutes. If you need a tighter bound, send smaller bulks. +* **`MaxWorkerMessagesPerInterval` counts messages, not check results.** One bulk message + of 200 check results counts as `1`. That is why it is a poor bound on its own and why + the time budget exists. + +Under nagios `MaxRuntimeMilliseconds` has no effect and is ignored with a warning: nagios +schedules the worker as a recurring one second event and offers no way to ask for an +earlier run, so a budget there could only be paid for out of throughput. + ## Message format ### `long_output` on events that have no long output diff --git a/src/Configuration.h b/src/Configuration.h index e7548e4..9a8dcea 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -206,7 +207,7 @@ namespace statusengine { public: explicit Configuration(IStatusengine &se) : se(se), cfg(toml::table{}), bulkTable(toml::table{}), schedulerTable(toml::table{}), - maxWorkerMessagesPerInterval(0), logLevel(LogLevel::Warning) {} + maxWorkerMessagesPerInterval(0), maxWorkerRuntime(0), logLevel(LogLevel::Warning) {} ~Configuration() { rabbitmq.clear(); // shared_ptr } @@ -290,6 +291,32 @@ namespace statusengine { } } + // How long one worker run may keep the monitoring core busy. Small on purpose: + // when it stops early the core reschedules it for its very next event loop + // pass, so the budget costs latency to the core and next to no throughput. + // Strict like MaxWorkerMessagesPerInterval above, and deliberately not + // GetTomlDefault(): toml11 falls back silently on a type mismatch there, and a + // typo in a bound that protects the monitoring core should not pass quietly. + unsigned long runtimeMs = 100ul; + if (workerTable.contains("MaxRuntimeMilliseconds")) { + try { + runtimeMs = toml::find(workerTable, "MaxRuntimeMilliseconds"); + } + catch (const toml::type_error &tte) { + se.Log() << "Invalid configuration: Invalid value for key " + << "MaxRuntimeMilliseconds" << LogLevel::Error; + return false; + } + } + maxWorkerRuntime = std::chrono::milliseconds(runtimeMs); +#ifdef BUILD_NAGIOS + if (workerTable.contains("MaxRuntimeMilliseconds")) { + se.Log() << "MaxRuntimeMilliseconds has no effect when built for nagios and is " + << "ignored: nagios cannot reschedule the worker before its next interval" + << LogLevel::Warning; + } +#endif + se.Log() << "Finished loading config" << LogLevel::Info; se.Log() << "Gearman Clients: " << gearman.size() << LogLevel::Info; unsigned int counter = 0; @@ -342,6 +369,11 @@ namespace statusengine { return maxWorkerMessagesPerInterval; } + /// Time budget for one worker run. Zero means unbounded. + std::chrono::milliseconds GetMaxWorkerRuntime() const { + return maxWorkerRuntime; + } + LogLevel GetLogLevel() const { return logLevel; } @@ -400,6 +432,7 @@ namespace statusengine { std::set bulkQueues; unsigned long maxWorkerMessagesPerInterval; + std::chrono::milliseconds maxWorkerRuntime; LogLevel logLevel; diff --git a/src/EventCallback.h b/src/EventCallback.h index ca8c3fe..f169e9f 100644 --- a/src/EventCallback.h +++ b/src/EventCallback.h @@ -11,7 +11,14 @@ namespace statusengine { virtual ~EventCallback() = default; virtual double Interval() = 0; - virtual void Callback() = 0; + + /** + * Runs the callback. + * + * @return true if it wants the next run as soon as possible rather than after + * Interval() seconds, because it stopped with work left over. + */ + virtual bool Callback() = 0; protected: IStatusengine &se; @@ -25,8 +32,8 @@ namespace statusengine { double Interval() override { return interval; } - void Callback() override { - se.GetMessageHandler()->Worker(); + bool Callback() override { + return se.GetMessageHandler()->Worker(); } private: @@ -41,8 +48,9 @@ namespace statusengine { double Interval() override { return interval; } - void Callback() override { + bool Callback() override { se.FlushBulkQueue(); + return false; } private: diff --git a/src/MessageHandler/IMessageHandler.h b/src/MessageHandler/IMessageHandler.h index 2076418..9e84119 100644 --- a/src/MessageHandler/IMessageHandler.h +++ b/src/MessageHandler/IMessageHandler.h @@ -35,7 +35,8 @@ namespace statusengine { virtual void InitComplete() = 0; virtual bool Connect() = 0; virtual void FlushBulkQueue() = 0; - virtual void Worker() = 0; + /// Runs one worker slice. Returns true if it stopped with work still queued. + virtual bool Worker() = 0; virtual bool QueueExists(Queue queue) = 0; virtual std::shared_ptr GetMessageQueueHandler(Queue queue) = 0; }; diff --git a/src/MessageHandler/MessageHandlerList.cpp b/src/MessageHandler/MessageHandlerList.cpp index cc2804b..895d1c8 100644 --- a/src/MessageHandler/MessageHandlerList.cpp +++ b/src/MessageHandler/MessageHandlerList.cpp @@ -13,7 +13,7 @@ namespace statusengine { MessageHandlerList::MessageHandlerList(IStatusengine &se, Configuration &cfg) - : se(se), maxBulkSize(0), globalBulkCounter(0), flushInProgress(true) { + : se(se), maxBulkSize(0), globalBulkCounter(0), flushInProgress(true), overload(se) { // flushInProgress is set to true to ensure no messages are sent until initialization is complete maxBulkSize = cfg.GetBulkMaximum(); @@ -30,6 +30,7 @@ namespace statusengine { queueHandlers->push_back(handler); }; maxWorkerMessagesPerInterval = cfg.GetMaxWorkerMessagesPerInterval(); + maxWorkerRuntime = cfg.GetMaxWorkerRuntime(); #ifdef WITH_GEARMAN auto gearmanConfigs = cfg.GetGearmanConfiguration(); diff --git a/src/MessageHandler/MessageHandlerList.h b/src/MessageHandler/MessageHandlerList.h index cea4381..006b8f0 100644 --- a/src/MessageHandler/MessageHandlerList.h +++ b/src/MessageHandler/MessageHandlerList.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -11,6 +13,70 @@ namespace statusengine { + /// What one RunWorkers() slice managed to do. + struct WorkerRunResult { + /// Messages taken off the queues during this slice. + unsigned long processed = 0ul; + /// Stopped because the time budget ran out. + bool budgetExhausted = false; + /// Stopped because maxMessages was reached. + bool messageLimitReached = false; + + /// True if the slice ended while the queues still had something to give. + bool WorkRemaining() const { + return budgetExhausted || messageLimitReached; + } + }; + + /** + * Reports that the worker keeps having to stop before the queues are empty, which + * means the broker is handed messages faster than the monitoring core can apply them. + * + * A real backlog exhausts the budget on every single run, so at a 100ms budget one + * line per run would be ten a second. Same shape as the gearman outage reporting: say + * it when it starts, repeat it on an interval so it cannot be quietly forgotten, and + * say when it is over. + * + * Takes the current time as an argument rather than reading the clock, so the interval + * can be tested without waiting for it. + */ + class OverloadReporter { + public: + explicit OverloadReporter(IStatusengine &se) : se(se), runs(0ul), messages(0ul), lastReport(0) {} + + static const time_t reportIntervalSeconds = 60; + + void Report(const WorkerRunResult &result, time_t now) { + if (!result.WorkRemaining()) { + if (runs > 0) { + se.Log() << "Worker queues are empty again, after " << runs + << " run(s) that had to stop early" << LogLevel::Warning; + runs = 0ul; + messages = 0ul; + lastReport = 0; + } + return; + } + + ++runs; + messages += result.processed; + if (runs == 1ul || now - lastReport >= reportIntervalSeconds) { + lastReport = now; + se.Log() << "Worker stopped with messages still queued, having reached " + << (result.budgetExhausted ? "its time budget" : "its message limit") + << " (" << runs << " run(s), " << messages + << " message(s) so far). The queues are filling up faster than they can be applied." + << LogLevel::Warning; + } + } + + private: + IStatusengine &se; + unsigned long runs; + unsigned long messages; + time_t lastReport; + }; + class MessageHandlerList : public IMessageHandlerList { public: MessageHandlerList(IStatusengine &se, Configuration &cfg); @@ -53,8 +119,19 @@ namespace statusengine { return mqHandlers.find(queue) != mqHandlers.end(); } - void Worker() override { - RunWorkers(allHandlers, maxWorkerMessagesPerInterval); + bool Worker() override { +#ifdef BUILD_NAGIOS + // Nagios schedules the worker as a recurring one second event and offers no way + // to ask for an earlier next slice, so a budget could only be paid for out of + // throughput: whatever it cut short would wait a full second. Left unbounded + // here, which is what nagios installations have always run with. + const auto budget = std::chrono::milliseconds::zero(); +#else + const auto budget = maxWorkerRuntime; +#endif + const auto result = RunWorkers(allHandlers, maxWorkerMessagesPerInterval, budget); + overload.Report(result, std::time(nullptr)); + return result.WorkRemaining(); } /** @@ -76,32 +153,63 @@ namespace statusengine { static constexpr unsigned maxRoundsWithoutProgress = 16; /** - * Lets every handler drain its queue, until nobody has anything left, maxMessages - * have been processed, or nobody has made progress for maxRoundsWithoutProgress - * rounds. + * Lets every handler drain its queue, until nobody has anything left, the time + * budget is spent, maxMessages have been processed, or nobody has made progress + * for maxRoundsWithoutProgress rounds. + * + * This runs inside the monitoring core's event loop, so the budget is what keeps a + * backlog from stopping the core: without it the loop only ends once the queues + * are empty, and a single call was measured blocking naemon for 128 seconds while + * it applied 100000 queued check results. * - * Static and defined here so it can be exercised with fake handlers. + * The budget is checked between messages, and a message is not divisible - a bulk + * message is one gearman job that has already been acknowledged, so abandoning it + * half way would throw check results away. The guarantee is therefore "budget plus + * the message in flight", not "budget". + * + * A budget of zero means unbounded. + * + * Static and defined here so it can be exercised with fake handlers, and templated + * on the clock so the budget can be tested without sleeping. */ - static void RunWorkers(std::vector> &handlers, - unsigned long maxMessages) { - unsigned long counter = 0ul; + template + static WorkerRunResult RunWorkers(std::vector> &handlers, + unsigned long maxMessages, std::chrono::milliseconds budget) { + WorkerRunResult result; unsigned roundsWithoutProgress = 0u; + const bool bounded = budget > std::chrono::milliseconds::zero(); + const auto deadline = bounded ? Clock::now() + budget : typename Clock::time_point(); bool moreMessages; do { moreMessages = false; - const unsigned long before = counter; + const unsigned long before = result.processed; for (auto &handler : handlers) { - if (handler->Worker(counter)) { + if (handler->Worker(result.processed)) { moreMessages = true; } } - if (counter != before) { + if (result.processed != before) { roundsWithoutProgress = 0u; } else if (++roundsWithoutProgress >= maxRoundsWithoutProgress) { break; } - } while (moreMessages && (counter < maxMessages)); + if (!moreMessages) { + break; + } + // Both of these mean work was left behind, which is what the caller + // reschedules on. A round without progress does not - retrying that + // immediately would just spin. + if (bounded && Clock::now() >= deadline) { + result.budgetExhausted = true; + break; + } + if (result.processed >= maxMessages) { + result.messageLimitReached = true; + break; + } + } while (true); + return result; } @@ -113,5 +221,7 @@ namespace statusengine { unsigned long globalBulkCounter; bool flushInProgress; unsigned long maxWorkerMessagesPerInterval; + std::chrono::milliseconds maxWorkerRuntime; + OverloadReporter overload; }; } // namespace statusengine diff --git a/src/Nebmodule.cpp b/src/Nebmodule.cpp index f5a8112..fc8ad3a 100644 --- a/src/Nebmodule.cpp +++ b/src/Nebmodule.cpp @@ -51,7 +51,7 @@ namespace statusengine { void Nebmodule::RegisterEventCallback(EventCallback *ecb) { #ifndef BUILD_NAGIOS - schedule_event(ecb->Interval(), nebmodule_event_callback, ecb); + schedule_event(static_cast(ecb->Interval()), nebmodule_event_callback, ecb); #else time_t interval = static_cast(ecb->Interval()); schedule_new_event(EVENT_USER_FUNCTION, 1, std::time(0) + interval, 1, interval, nullptr, 1, @@ -59,6 +59,17 @@ namespace statusengine { #endif // BUILD_NAGIOS } +#ifndef BUILD_NAGIOS + void Nebmodule::RegisterEventCallbackNow(EventCallback *ecb) { + // A zero delay does not starve the core. naemon's event_poll_full() computes the + // time to the next event, clamps it to zero for one that is already due, polls its + // own file descriptors with that timeout, and skips running the timed event + // altogether if any of them had input. So the next worker slice only happens once + // naemon has had a pass of its own, and naemon's own I/O gets priority over ours. + schedule_event(0, nebmodule_event_callback, ecb); + } +#endif + void Nebmodule::ScheduleHostCheckDelay(host *temp_host, time_t delay) { #ifndef BUILD_NAGIOS schedule_next_host_check(temp_host, delay, CHECK_OPTION_NONE); @@ -241,13 +252,20 @@ int nebmodule_callback(int event_type, void *data) { #ifndef BUILD_NAGIOS void nebmodule_event_callback(struct nm_event_execution_properties *properties) { auto ecb = reinterpret_cast(properties->user_data); - ecb->Callback(); + const bool workRemaining = ecb->Callback(); if (!(sigshutdown || sigrestart)) { - statusengine::Nebmodule::Instance().RegisterEventCallback(ecb); + if (workRemaining) { + statusengine::Nebmodule::Instance().RegisterEventCallbackNow(ecb); + } + else { + statusengine::Nebmodule::Instance().RegisterEventCallback(ecb); + } } } #else void nebmodule_event_callback(statusengine::EventCallback *ecb) { - ecb->Callback(); + // Nagios reschedules this itself, the event is recurring. There is no way to ask for + // an earlier run, so a callback with work left over simply waits for the next one. + (void)ecb->Callback(); } #endif diff --git a/src/Nebmodule.h b/src/Nebmodule.h index a3ee767..b989ddd 100644 --- a/src/Nebmodule.h +++ b/src/Nebmodule.h @@ -52,6 +52,11 @@ namespace statusengine { bool RegisterCallback(NEBCallbackType cbType); void RegisterEventCallback(EventCallback *ecb); +#ifndef BUILD_NAGIOS + /// Schedules the callback for naemon's next event loop pass instead of after its + /// interval, for when it stopped with work left over. + void RegisterEventCallbackNow(EventCallback *ecb); +#endif void ScheduleHostCheckDelay(host *temp_host, time_t delay); diff --git a/statusengine.toml b/statusengine.toml index 0bf2b5a..6337931 100644 --- a/statusengine.toml +++ b/statusengine.toml @@ -91,6 +91,27 @@ WorkerCommand = "statusngin_cmd" # the service/host within $StartupScheduleMax seconds instead of the check_intervall of the host/service. StartupScheduleMax = 30 +[Worker] +# These two bound how long one run of the worker - the part that reads WorkerCommand, +# WorkerOCHP and WorkerOCSP and applies the results - may keep the monitoring core busy. +# The worker runs inside the core's event loop, so while it works, the core does nothing +# else: it schedules no checks, reaps no results and reads no external commands. +# +# Time budget for one run, in milliseconds. 0 means unbounded. +# When a run stops here with messages still queued, the core is asked to run the worker +# again on its very next event loop pass, so this costs the core a little latency and +# next to no throughput. A run can overshoot by the message it is busy with: a bulk +# message is one job that has already been acknowledged and cannot be abandoned half way, +# so the real guarantee is "budget plus one message". +# Has no effect when built for nagios, which cannot reschedule the worker early. +#MaxRuntimeMilliseconds = 100 +# +# Upper limit of messages one run may take off the queues. +# Note that this counts messages as they arrive from gearman or rabbitmq, NOT the check +# results inside them - one bulk message of 200 check results counts as 1. Leave the time +# budget above as the real bound; this is a second, coarser ceiling. +#MaxWorkerMessagesPerInterval = 1000000 + [Bulk] ## Maximum number of messages in one bulk message #Maximum = 200 diff --git a/tests/test_configuration.cpp b/tests/test_configuration.cpp index 3b14902..54fd479 100644 --- a/tests/test_configuration.cpp +++ b/tests/test_configuration.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -134,6 +135,7 @@ TEST_CASE("Configuration applies bulk and worker defaults") { CHECK(cfg.GetBulkMaximum() == 200ul); CHECK(cfg.GetBulkFlushInterval() == 10); CHECK(cfg.GetMaxWorkerMessagesPerInterval() == 1000000ul); + CHECK(cfg.GetMaxWorkerRuntime() == std::chrono::milliseconds(100)); CHECK(cfg.GetStartupScheduleMax() == 0); } @@ -151,6 +153,7 @@ StartupScheduleMax = 30 [Worker] MaxWorkerMessagesPerInterval = 42 +MaxRuntimeMilliseconds = 250 )"); REQUIRE(cfg.Load(file.path)); @@ -158,6 +161,7 @@ MaxWorkerMessagesPerInterval = 42 CHECK(cfg.GetBulkFlushInterval() == 3); CHECK(cfg.GetStartupScheduleMax() == 30); CHECK(cfg.GetMaxWorkerMessagesPerInterval() == 42ul); + CHECK(cfg.GetMaxWorkerRuntime() == std::chrono::milliseconds(250)); CHECK(cfg.IsBulkQueue(Queue::HostStatus)); CHECK(cfg.IsBulkQueue(Queue::ServiceCheck)); CHECK_FALSE(cfg.IsBulkQueue(Queue::LogData)); @@ -275,3 +279,22 @@ TEST_CASE("The developer environment configuration parses") { CHECK(cfg.GetGearmanConfiguration()->size() == 1); CHECK(cfg.GetRabbitmqConfiguration()->size() == 1); } + + +TEST_CASE("A worker runtime of zero is accepted and means unbounded") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file("[Worker]\nMaxRuntimeMilliseconds = 0\n"); + + REQUIRE(cfg.Load(file.path)); + CHECK(cfg.GetMaxWorkerRuntime() == std::chrono::milliseconds::zero()); +} + +TEST_CASE("An invalid worker runtime is rejected rather than ignored") { + FakeStatusengine se; + Configuration cfg(se); + TempConfig file("[Worker]\nMaxRuntimeMilliseconds = \"soon\"\n"); + + CHECK_FALSE(cfg.Load(file.path)); + CHECK(FakeStatusengine::Logged("MaxRuntimeMilliseconds")); +} diff --git a/tests/test_support.h b/tests/test_support.h index 99f3a20..f774ab9 100644 --- a/tests/test_support.h +++ b/tests/test_support.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -88,6 +89,37 @@ namespace statusengine { unsigned long calls; }; + /** + * Clock that only moves when it is read, by a fixed step per reading. Lets the worker + * loop's time budget be tested exactly and without sleeping: with a step of 10ms and a + * budget of 50ms the loop is expected to stop after a known number of rounds. + */ + struct FakeClock { + using duration = std::chrono::steady_clock::duration; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + static const bool is_steady = true; + + inline static std::chrono::milliseconds step{0}; + inline static unsigned long readings = 0ul; + inline static time_point current{}; + + /// Starts at zero again, advancing by perReading every time now() is called. + static void Reset(std::chrono::milliseconds perReading) { + step = perReading; + readings = 0ul; + current = time_point{}; + } + + static time_point now() { + ++readings; + const auto value = current; + current += step; + return value; + } + }; + /** * IMessageHandler shaped like the real gearman worker: every message costs one round * that reports "there is more" without processing anything, because the grab-job diff --git a/tests/test_workerloop.cpp b/tests/test_workerloop.cpp index 7b71eb8..f3f6870 100644 --- a/tests/test_workerloop.cpp +++ b/tests/test_workerloop.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -7,10 +8,14 @@ #include "MessageHandler/MessageHandlerList.h" +using statusengine::FakeClock; using statusengine::FakeIoWaitHandler; using statusengine::FakeMessageHandler; +using statusengine::FakeStatusengine; using statusengine::IMessageHandler; using statusengine::MessageHandlerList; +using statusengine::OverloadReporter; +using statusengine::WorkerRunResult; namespace { using HandlerList = std::vector>; @@ -20,6 +25,11 @@ namespace { list.push_back(handler); return handler; } + + /// Everything that is not about the time budget runs without one. + WorkerRunResult RunUnbounded(HandlerList &handlers, unsigned long maxMessages) { + return MessageHandlerList::RunWorkers(handlers, maxMessages, std::chrono::milliseconds::zero()); + } } // namespace TEST_CASE("a handler that asks for more without progressing does not spin the loop") { @@ -28,7 +38,7 @@ TEST_CASE("a handler that asks for more without progressing does not spin the lo HandlerList handlers; auto stuck = Add(handlers, 0, true); - MessageHandlerList::RunWorkers(handlers, 1000000ul); + RunUnbounded(handlers, 1000000ul); CHECK(stuck->calls == MessageHandlerList::maxRoundsWithoutProgress); } @@ -43,7 +53,7 @@ TEST_CASE("a worker that waits for its socket before every message still drains" auto handler = std::make_shared(500); handlers.push_back(handler); - MessageHandlerList::RunWorkers(handlers, 1000000ul); + RunUnbounded(handlers, 1000000ul); CHECK(handler->remaining == 0); } @@ -55,7 +65,7 @@ TEST_CASE("waiting for the socket does not consume the no progress budget") { auto handler = std::make_shared(4); handlers.push_back(handler); - MessageHandlerList::RunWorkers(handlers, 1000000ul); + RunUnbounded(handlers, 1000000ul); // four waits, four messages, and the round that finds the queue empty CHECK(handler->calls == 9); @@ -65,7 +75,7 @@ TEST_CASE("a handler drains its queue within one tick") { HandlerList handlers; auto busy = Add(handlers, 5, false); - MessageHandlerList::RunWorkers(handlers, 1000000ul); + RunUnbounded(handlers, 1000000ul); CHECK(busy->remaining == 0); // five rounds that processed something, plus the one that found nothing left @@ -76,7 +86,7 @@ TEST_CASE("the message limit is respected") { HandlerList handlers; auto busy = Add(handlers, 100, false); - MessageHandlerList::RunWorkers(handlers, 10ul); + RunUnbounded(handlers, 10ul); CHECK(busy->calls == 10); CHECK(busy->remaining == 90); @@ -87,7 +97,7 @@ TEST_CASE("a stuck handler does not stop the others") { auto stuck = Add(handlers, 0, true); auto busy = Add(handlers, 3, false); - MessageHandlerList::RunWorkers(handlers, 1000000ul); + RunUnbounded(handlers, 1000000ul); // The loop keeps going while anyone makes progress, so the working handler drains... CHECK(busy->remaining == 0); @@ -100,7 +110,7 @@ TEST_CASE("a handler that goes quiet ends the loop") { auto first = Add(handlers, 2, false); auto second = Add(handlers, 1, false); - MessageHandlerList::RunWorkers(handlers, 1000000ul); + RunUnbounded(handlers, 1000000ul); CHECK(first->remaining == 0); CHECK(second->remaining == 0); @@ -108,7 +118,7 @@ TEST_CASE("a handler that goes quiet ends the loop") { TEST_CASE("an empty handler list terminates") { HandlerList handlers; - MessageHandlerList::RunWorkers(handlers, 1000000ul); + RunUnbounded(handlers, 1000000ul); CHECK(handlers.empty()); } @@ -117,8 +127,154 @@ TEST_CASE("a zero message limit still runs one round") { HandlerList handlers; auto busy = Add(handlers, 5, false); - MessageHandlerList::RunWorkers(handlers, 0ul); + RunUnbounded(handlers, 0ul); CHECK(busy->calls == 1); CHECK(busy->remaining == 4); } + + +// --------------------------------------------------------------------------------------- +// The time budget. Without it one call was measured holding naemon's event loop for 128 +// seconds while it applied a backlog of 100000 check results. +// --------------------------------------------------------------------------------------- + +TEST_CASE("the loop stops on its time budget and says so") { + FakeClock::Reset(std::chrono::milliseconds(10)); + HandlerList handlers; + auto busy = Add(handlers, 1000, false); + + // The deadline costs one reading, then one per round: rounds see 10, 20, 30, 40 and + // 50ms, and the fifth reaches the 50ms budget. + auto result = MessageHandlerList::RunWorkers(handlers, 1000000ul, std::chrono::milliseconds(50)); + + CHECK(result.budgetExhausted); + CHECK(result.WorkRemaining()); + CHECK(result.processed == 5); + CHECK(busy->remaining == 995); +} + +TEST_CASE("the clock is read once per round") { + FakeClock::Reset(std::chrono::milliseconds(10)); + HandlerList handlers; + Add(handlers, 1000, false); + + auto result = MessageHandlerList::RunWorkers(handlers, 1000000ul, std::chrono::milliseconds(50)); + + // one for the deadline, one per round + CHECK(FakeClock::readings == result.processed + 1); +} + +TEST_CASE("a handler that runs dry before the budget reports no work remaining") { + // This one matters: work remaining is what makes the core reschedule immediately, so + // an idle broker reporting it would spin instead of waiting for its next interval. + FakeClock::Reset(std::chrono::milliseconds(1)); + HandlerList handlers; + auto busy = Add(handlers, 3, false); + + auto result = MessageHandlerList::RunWorkers(handlers, 1000000ul, std::chrono::milliseconds(1000)); + + CHECK_FALSE(result.budgetExhausted); + CHECK_FALSE(result.WorkRemaining()); + CHECK(busy->remaining == 0); +} + +TEST_CASE("a budget of zero means unbounded") { + FakeClock::Reset(std::chrono::milliseconds(1000)); + HandlerList handlers; + auto busy = Add(handlers, 50, false); + + auto result = MessageHandlerList::RunWorkers(handlers, 1000000ul, std::chrono::milliseconds::zero()); + + CHECK(busy->remaining == 0); + CHECK_FALSE(result.budgetExhausted); + // Not consulted at all, so a huge step cannot end the loop early. + CHECK(FakeClock::readings == 0ul); +} + +TEST_CASE("the message limit also counts as work remaining") { + HandlerList handlers; + Add(handlers, 100, false); + + auto result = RunUnbounded(handlers, 10ul); + + CHECK(result.messageLimitReached); + CHECK(result.WorkRemaining()); + CHECK_FALSE(result.budgetExhausted); +} + +TEST_CASE("a round without progress is not work remaining") { + // A handler that asks for more without progressing must not make the core reschedule + // immediately - that would spin the event loop rather than wait for the next tick. + HandlerList handlers; + Add(handlers, 0, true); + + auto result = RunUnbounded(handlers, 1000000ul); + + CHECK_FALSE(result.WorkRemaining()); +} + +// --------------------------------------------------------------------------------------- +// Overload reporting +// --------------------------------------------------------------------------------------- + +namespace { + WorkerRunResult Stopped(unsigned long processed) { + WorkerRunResult r; + r.processed = processed; + r.budgetExhausted = true; + return r; + } + + WorkerRunResult Drained() { + WorkerRunResult r; + r.processed = 7; + return r; + } +} // namespace + +TEST_CASE("an overload is reported when it starts, on an interval, and when it ends") { + FakeStatusengine se; + OverloadReporter reporter(se); + + reporter.Report(Stopped(100), 1000); + CHECK(FakeStatusengine::Logged("Worker stopped with messages still queued")); + CHECK(FakeStatusengine::Logged("its time budget")); + + capturedLogs.clear(); + // A backlog hits the budget on every run; those must not each produce a line. + for (time_t t = 1001; t < 1000 + OverloadReporter::reportIntervalSeconds; ++t) { + reporter.Report(Stopped(100), t); + } + CHECK(capturedLogs.empty()); + + reporter.Report(Stopped(100), 1000 + OverloadReporter::reportIntervalSeconds); + CHECK(FakeStatusengine::Logged("Worker stopped with messages still queued")); + + capturedLogs.clear(); + reporter.Report(Drained(), 1200); + CHECK(FakeStatusengine::Logged("Worker queues are empty again")); +} + +TEST_CASE("no overload is reported while the queues keep up") { + FakeStatusengine se; + OverloadReporter reporter(se); + + for (time_t t = 0; t < 500; ++t) { + reporter.Report(Drained(), t); + } + + CHECK(capturedLogs.empty()); +} + +TEST_CASE("hitting the message limit is reported as such") { + FakeStatusengine se; + OverloadReporter reporter(se); + + WorkerRunResult result; + result.processed = 10; + result.messageLimitReached = true; + reporter.Report(result, 1000); + + CHECK(FakeStatusengine::Logged("its message limit")); +} From f62c96db0994d540d9c5373235cad13c458660a2 Mon Sep 17 00:00:00 2001 From: nook24 Date: Wed, 26 Aug 2026 19:05:48 +0200 Subject: [PATCH 26/28] Do not report a stuck worker as an overloaded one Found by running the new budget against a naemon whose job server had been stopped mid backlog: Worker stopped with messages still queued, having reached its time budget (227 run(s), 0 message(s) so far). The queues are filling up faster than they can be applied. 227 runs, zero messages. With the job server gone libgearman answers GEARMAN_IO_WAIT round after round, each costing a 10ms poll, so a 100ms budget is spent in ten rounds - before the sixteen round no-progress guard can end the run. The budget then reported work left over, which is wrong twice over: it made the callback reschedule itself immediately, retrying a dead connection ten times a second, and it blamed the queues in the log for what was a connection problem. The handlers report a lost connection themselves, and that is the message an operator needs to see. Both the budget and the message limit now only count as work left over if the run actually completed a message. A run that spends its whole budget without finishing one is not behind, it is stuck, and stuck is worth waiting a second over. Verified the same way it was found: with the job server stopped for 70 seconds under a 26558 job backlog, naemon used 20ms of CPU and logged no overload at all, only the connection errors. MaxWorkerMessagesPerInterval = 0 is left as it is - it means one message per run, not unbounded, which is now written down next to the setting. It is not a useful value, but changing what it does would be a silent behaviour change for anyone who set it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01RzCPmi3qofthqD8WiHkjno --- src/MessageHandler/MessageHandlerList.h | 13 +++++++++++-- statusengine.toml | 2 ++ tests/test_workerloop.cpp | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/MessageHandler/MessageHandlerList.h b/src/MessageHandler/MessageHandlerList.h index 006b8f0..589d0dc 100644 --- a/src/MessageHandler/MessageHandlerList.h +++ b/src/MessageHandler/MessageHandlerList.h @@ -200,12 +200,21 @@ namespace statusengine { // Both of these mean work was left behind, which is what the caller // reschedules on. A round without progress does not - retrying that // immediately would just spin. + // + // Hence the "processed something" condition on both flags. A run that + // spends its whole budget without completing a single message is not + // behind, it is stuck: an unreachable job server makes libgearman report + // GEARMAN_IO_WAIT round after round, each costing a 10ms poll, so the + // budget is gone in ten rounds. Reporting that as work left over would + // retry a broken connection ten times a second and, worse, blame the + // wrong thing in the log - the handlers report a lost connection + // themselves, and that is the message an operator needs. if (bounded && Clock::now() >= deadline) { - result.budgetExhausted = true; + result.budgetExhausted = result.processed > 0ul; break; } if (result.processed >= maxMessages) { - result.messageLimitReached = true; + result.messageLimitReached = result.processed > 0ul; break; } } while (true); diff --git a/statusengine.toml b/statusengine.toml index 6337931..9ec1b61 100644 --- a/statusengine.toml +++ b/statusengine.toml @@ -110,6 +110,8 @@ StartupScheduleMax = 30 # Note that this counts messages as they arrive from gearman or rabbitmq, NOT the check # results inside them - one bulk message of 200 check results counts as 1. Leave the time # budget above as the real bound; this is a second, coarser ceiling. +# Unlike MaxRuntimeMilliseconds, 0 does not mean unbounded here - it means one message per +# run. That is not a useful setting; leave it alone and use the time budget. #MaxWorkerMessagesPerInterval = 1000000 [Bulk] diff --git a/tests/test_workerloop.cpp b/tests/test_workerloop.cpp index f3f6870..3595eed 100644 --- a/tests/test_workerloop.cpp +++ b/tests/test_workerloop.cpp @@ -192,6 +192,24 @@ TEST_CASE("a budget of zero means unbounded") { CHECK(FakeClock::readings == 0ul); } +TEST_CASE("a run that spends its whole budget without progress is not work remaining") { + // What an unreachable job server looks like: libgearman reports GEARMAN_IO_WAIT round + // after round, each costing a 10ms poll, so the budget is gone before the no progress + // guard is reached. Rescheduling immediately would retry a broken connection ten times + // a second, and reporting an overload would blame the queues for a connection problem. + FakeClock::Reset(std::chrono::milliseconds(10)); + HandlerList handlers; + auto stuck = Add(handlers, 0, true); + + auto result = MessageHandlerList::RunWorkers(handlers, 1000000ul, std::chrono::milliseconds(50)); + + CHECK(result.processed == 0ul); + CHECK_FALSE(result.budgetExhausted); + CHECK_FALSE(result.WorkRemaining()); + // Stopped on the budget, well before the 16 round guard would have ended it. + CHECK(stuck->calls == 5); +} + TEST_CASE("the message limit also counts as work remaining") { HandlerList handlers; Add(handlers, 100, false); From da1087ed7627152c6e9fe008a2765c5ab8253b4e Mon Sep 17 00:00:00 2001 From: nook24 Date: Sat, 29 Aug 2026 18:48:23 +0200 Subject: [PATCH 27/28] build outgoing messages with yyjson instead of json-c Profiling naemon with this broker loaded, 5000 hosts / 100000 services on a 60s interval with the production bulk queues, put Statusengine::Callback at 52.9% of the event loop. Nearly all of it was json-c: the allocator alone accounted for ~29% self time, driven by a json_object tree being built and torn down for every single event, plus serialising the batch. yyjson allocates its nodes from a per-document arena, so a message is built in one block and released in one go rather than node by node. NagiosObject now holds a yyjson_mut_doc. The SetData API is unchanged, so all event classes move over with it. Nested objects are copied into the parent document rather than reference counted, since a yyjson value belongs to the document it was created in. Bulk queues now keep the serialised text of each message instead of the object. Holding the object would mean copying the whole tree into a batch document at flush time; keeping the text also removes the single large serialisation of the batch and the array_list that went with it. The envelope around them is fixed and has no dynamic content. Parsing (worker commands) still uses json-c and is untouched. The wire format is unchanged with one exception: json-c escaped forward slashes as "\/", yyjson does not. Both decode to the same string in any conforming parser - verified with the two consumers, PHP 8.1 (=== is true) and Go 1.26 encoding/json (DeepEqual is true). Messages get slightly smaller as a result. Everything else was compared field by field and is identical, including doubles keeping their .0 form so the JSON type stays float, integers beyond 2^53, null, UTF-8, escaping and key order. Real messages captured from both builds have the same 51 fields in the same order with the same types. vendor/ carries yyjson 0.12.0 (MIT) as two files, the same way toml11 is already vendored, so no distribution package is required. yyjson has its own fallbacks for compilers without C99, so RHEL 8 builds as well. loop CPU per check 80.9us -> 51.5us -36.4% loop CPU per 60s 8.09s -> 5.15s Four interleaved pairs, naemon main thread only, identical check rate and 100% coverage in every run, negative in all four (-33/-27/-30/-29 us). Verified over a 14 minute run under full load: RSS flat at 198MB, no errors logged, 99.7% of services checked within their interval, mean check latency 0.05s. Unit tests: 75 cases, 432 assertions, all passing. Signed-off-by: nook24 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WY8mbGLNkt5eQfc57cTnZ5 --- meson.build | 2 +- src/MessageHandler/MessageHandler.h | 33 +- src/NagiosObject.h | 117 +- src/meson.build | 4 +- tests/meson.build | 4 +- vendor/yyjson-LICENSE | 21 + vendor/yyjson-VERSION | 1 + vendor/yyjson.c | 11065 ++++++++++++++++++++++++++ vendor/yyjson.h | 8230 +++++++++++++++++++ 9 files changed, 19404 insertions(+), 73 deletions(-) create mode 100644 vendor/yyjson-LICENSE create mode 100644 vendor/yyjson-VERSION create mode 100644 vendor/yyjson.c create mode 100644 vendor/yyjson.h diff --git a/meson.build b/meson.build index 6e3bfc0..155f152 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('statusengine-broker', 'cpp', +project('statusengine-broker', ['cpp', 'c'], version: '4.0.4', default_options: ['warning_level=3', 'cpp_std=c++17', # The module is loaded by an absolute path from naemon.cfg, so it goes diff --git a/src/MessageHandler/MessageHandler.h b/src/MessageHandler/MessageHandler.h index 2360926..b31d791 100644 --- a/src/MessageHandler/MessageHandler.h +++ b/src/MessageHandler/MessageHandler.h @@ -403,7 +403,10 @@ namespace statusengine { void SendMessage(NagiosObject &obj) override { if (bulk) { if(!obj.isEmpty()){ - bulkMessages.push_back(std::unique_ptr(new NagiosObject(&obj))); + /* Serialise now and keep the text: a yyjson value belongs to + * its own document, so holding the object would mean copying + * the whole tree into a batch document instead. */ + bulkMessages.push_back(obj.ToString()); if (++(*globalBulkCounter) >= maxBulkSize) { mhlist.FlushBulkQueue(); } @@ -421,17 +424,23 @@ namespace statusengine { void FlushBulkQueue() override { if (!bulkMessages.empty()) { - NagiosObject msgObj; - json_object *arr = json_object_new_array(); - - for (auto &obj : bulkMessages) { - json_object_array_add(arr, obj->GetDataCopy()); + /* Fixed envelope around the already serialised messages. */ + std::string msg; + size_t total = 32; + for (auto &m : bulkMessages) { + total += m.size() + 1; } - - msgObj.SetData("messages", arr); - msgObj.SetData("format", "none"); - - std::string msg = msgObj.ToString(); + msg.reserve(total); + msg += "{\"messages\":["; + bool first = true; + for (auto &m : bulkMessages) { + if (!first) { + msg += ','; + } + first = false; + msg += m; + } + msg += "],\"format\":\"none\"}"; for (auto &handler : *handlers) { handler->SendMessage(queue, msg); } @@ -450,7 +459,7 @@ namespace statusengine { Queue queue; std::shared_ptr>> handlers; - std::vector> bulkMessages; + std::vector bulkMessages; unsigned long maxBulkSize; unsigned long *globalBulkCounter; diff --git a/src/NagiosObject.h b/src/NagiosObject.h index f5cf859..c49b18c 100644 --- a/src/NagiosObject.h +++ b/src/NagiosObject.h @@ -3,37 +3,41 @@ #include #include +#include "yyjson.h" #include "Nebmodule.h" namespace statusengine { + /* + * Wire format is unchanged from the json-c version this replaces. The one + * difference is that json-c escaped forward slashes ("\\/"), which yyjson + * does not; both decode to the same string in any conforming parser. + */ class NagiosObject { public: explicit NagiosObject() : nebmodule(Nebmodule::Instance()) { - data = json_object_new_object(); + doc = yyjson_mut_doc_new(nullptr); + data = yyjson_mut_obj(doc); + yyjson_mut_doc_set_root(doc, data); } - /** - * This is like a copy constructor, it increments the counter for data - * @param data - */ - explicit NagiosObject(json_object *data) : nebmodule(Nebmodule::Instance()), data(json_object_get(data)) {} - /** - * This is like a copy constructor, it increments the counter for obj->data - * @param data - */ - explicit NagiosObject(statusengine::NagiosObject *obj) : nebmodule(Nebmodule::Instance()), data(json_object_get(obj->data)) {} + NagiosObject(const NagiosObject &) = delete; + NagiosObject &operator=(const NagiosObject &) = delete; - ~NagiosObject() { - json_object_put(data); + virtual ~NagiosObject() { + yyjson_mut_doc_free(doc); } - std::string ToString() { - // PLAIN rather than json_object_to_json_string()'s SPACED default: same JSON, - // 8 to 9 percent fewer bytes on every message the broker sends, which is - // network, queue memory and parsing work on the consumer side. - return std::string(json_object_to_json_string_ext(data, JSON_C_TO_STRING_PLAIN)); + std::string ToString() const { + size_t len = 0; + char *out = yyjson_mut_write(doc, 0, &len); + if (out == nullptr) { + return std::string(); + } + std::string result(out, len); + free(out); + return result; } /** @@ -42,89 +46,88 @@ namespace statusengine { * @return true if the JSON object is empty, false otherwise */ bool isEmpty() const { - return json_object_object_length(data) == 0; - } - - /** - * Counter will be incremented - * @return json_object* - */ - json_object *GetDataCopy() { - return json_object_get(data); + return yyjson_mut_obj_size(data) == 0; } inline void SetData(const char *name, const std::string &value) { - SetData(data, name, value); + SetData(doc, data, name, value); } - inline static void SetData(json_object *obj, const char *name, const std::string &value) { - json_object_object_add(obj, name, json_object_new_string_len(value.c_str(), value.length())); + inline static void SetData(yyjson_mut_doc *d, yyjson_mut_val *obj, const char *name, const std::string &value) { + yyjson_mut_obj_add_strncpy(d, obj, name, value.c_str(), value.length()); } inline void SetData(const char *name, const char *value) { - SetData(data, name, value); + SetData(doc, data, name, value); } - inline static void SetData(json_object *obj, const char *name, const char *value) { + inline static void SetData(yyjson_mut_doc *d, yyjson_mut_val *obj, const char *name, const char *value) { if (value == nullptr) { - json_object_object_add(obj, name, nullptr); + yyjson_mut_obj_add_null(d, obj, name); } else { - json_object_object_add(obj, name, json_object_new_string(value)); + yyjson_mut_obj_add_strcpy(d, obj, name, value); } } inline void SetData(const char *name, int value) { - SetData(data, name, value); + SetData(doc, data, name, value); } - inline static void SetData(json_object *obj, const char *name, int value) { - json_object_object_add(obj, name, json_object_new_int(value)); + inline static void SetData(yyjson_mut_doc *d, yyjson_mut_val *obj, const char *name, int value) { + yyjson_mut_obj_add_int(d, obj, name, value); } - inline void SetData(const char *name, long int value) { - SetData(data, name, value); + inline void SetData(const char *name, long value) { + SetData(doc, data, name, value); } - inline static void SetData(json_object *obj, const char *name, long int value) { - json_object_object_add(obj, name, json_object_new_int64(value)); + inline static void SetData(yyjson_mut_doc *d, yyjson_mut_val *obj, const char *name, long value) { + yyjson_mut_obj_add_sint(d, obj, name, value); } inline void SetData(const char *name, double value) { - SetData(data, name, value); + SetData(doc, data, name, value); } - inline static void SetData(json_object *obj, const char *name, double value) { - json_object_object_add(obj, name, json_object_new_double(value)); + inline static void SetData(yyjson_mut_doc *d, yyjson_mut_val *obj, const char *name, double value) { + yyjson_mut_obj_add_real(d, obj, name, value); } - inline void SetData(const char *name, long unsigned int value) { - SetData(data, name, value); + inline void SetData(const char *name, unsigned long value) { + SetData(doc, data, name, value); } - inline static void SetData(json_object *obj, const char *name, long unsigned int value) { - json_object_object_add(obj, name, json_object_new_int64(value)); + inline static void SetData(yyjson_mut_doc *d, yyjson_mut_val *obj, const char *name, unsigned long value) { + yyjson_mut_obj_add_sint(d, obj, name, static_cast(value)); } + /* Nested object: yyjson values belong to their own document, so the + * subtree is copied across rather than reference counted. */ inline void SetData(const char *name, NagiosObject *other) { - SetData(data, name, other); + SetData(doc, data, name, other); + } + + inline static void SetData(yyjson_mut_doc *d, yyjson_mut_val *obj, const char *name, NagiosObject *other) { + yyjson_mut_obj_add_val(d, obj, name, yyjson_mut_val_mut_copy(d, other->data)); } - inline static void SetData(json_object *obj, const char *name, NagiosObject *other) { - SetData(obj, name, json_object_get(other->data)); + inline void SetData(const char *name, yyjson_mut_val *other) { + yyjson_mut_obj_add_val(doc, data, name, other); } - inline void SetData(const char *name, json_object *other) { - SetData(data, name, other); + yyjson_mut_doc *GetDoc() const { + return doc; } - inline static void SetData(json_object *obj, const char *name, json_object *other) { - json_object_object_add(obj, name, other); + yyjson_mut_val *GetRoot() const { + return data; } - protected: + protected: Nebmodule &nebmodule; - json_object *data; + yyjson_mut_doc *doc; + yyjson_mut_val *data; }; class NagiosProcessData : public NagiosObject { diff --git a/src/meson.build b/src/meson.build index 8173a42..192012e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,7 +2,9 @@ version_conf = configuration_data() version_conf.set_quoted('STATUSENGINE_VERSION', meson.project_version()) version_h = configure_file(output: 'version.h', configuration: version_conf) -statusengine_sources = ['Encoding.cpp', 'Nebmodule.cpp', 'Statusengine.cpp', 'MessageHandler/MessageHandlerList.cpp'] +# yyjson is vendored in vendor/; its include path comes with toml_dep +statusengine_sources = ['Encoding.cpp', 'Nebmodule.cpp', 'Statusengine.cpp', 'MessageHandler/MessageHandlerList.cpp', + '../vendor/yyjson.c'] statusengine_deps = [uchardet_dep, jsonc_dep, toml_dep] statusengine_cpp_args = [] diff --git a/tests/meson.build b/tests/meson.build index a2fedba..b4e209b 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -22,7 +22,7 @@ test_incdirs = include_directories('.', '../src') statusengine_tests = executable('statusengine-tests', ['main.cpp', 'naemon_stubs.cpp', 'test_encoding.cpp', 'test_configuration.cpp', 'test_logstream.cpp', 'test_queue.cpp', 'test_messageformat.cpp', 'test_workerloop.cpp', - '../src/Encoding.cpp'], + '../src/Encoding.cpp', '../vendor/yyjson.c'], include_directories: test_incdirs, dependencies: test_deps, cpp_args: test_cpp_args, @@ -32,7 +32,7 @@ statusengine_tests = executable('statusengine-tests', test('unit', statusengine_tests, protocol: 'exitcode', timeout: 120) statusengine_bench = executable('statusengine-bench', - ['bench.cpp', 'naemon_stubs.cpp', '../src/Encoding.cpp'], + ['bench.cpp', 'naemon_stubs.cpp', '../src/Encoding.cpp', '../vendor/yyjson.c'], include_directories: test_incdirs, dependencies: test_deps, cpp_args: test_cpp_args, diff --git a/vendor/yyjson-LICENSE b/vendor/yyjson-LICENSE new file mode 100644 index 0000000..a09bff3 --- /dev/null +++ b/vendor/yyjson-LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 YaoYuan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/yyjson-VERSION b/vendor/yyjson-VERSION new file mode 100644 index 0000000..95f8208 --- /dev/null +++ b/vendor/yyjson-VERSION @@ -0,0 +1 @@ +yyjson 0.12.0 (https://github.com/ibireme/yyjson), MIT. diff --git a/vendor/yyjson.c b/vendor/yyjson.c new file mode 100644 index 0000000..c16d925 --- /dev/null +++ b/vendor/yyjson.c @@ -0,0 +1,11065 @@ +/*============================================================================== + Copyright (c) 2020 YaoYuan + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + *============================================================================*/ + +#include "yyjson.h" +#include /* for `HUGE_VAL/INFINIY/NAN` macros, no libm required */ + + + +/*============================================================================== + * MARK: - Warning Suppress (Private) + *============================================================================*/ + +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-parameter" +# pragma clang diagnostic ignored "-Wunused-label" +# pragma clang diagnostic ignored "-Wunused-macros" +# pragma clang diagnostic ignored "-Wunused-variable" +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wunused-parameter" +# pragma GCC diagnostic ignored "-Wunused-label" +# pragma GCC diagnostic ignored "-Wunused-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +#elif defined(_MSC_VER) +# pragma warning(disable:4100) /* unreferenced formal parameter */ +# pragma warning(disable:4101) /* unreferenced variable */ +# pragma warning(disable:4102) /* unreferenced label */ +# pragma warning(disable:4127) /* conditional expression is constant */ +# pragma warning(disable:4706) /* assignment within conditional expression */ +#endif + + + +/*============================================================================== + * MARK: - Version (Public) + *============================================================================*/ + +uint32_t yyjson_version(void) { + return YYJSON_VERSION_HEX; +} + + + +/*============================================================================== + * MARK: - Flags (Private) + *============================================================================*/ + +/* msvc intrinsic */ +#if YYJSON_MSC_VER >= 1400 +# include +# if defined(_M_AMD64) || defined(_M_ARM64) +# define MSC_HAS_BIT_SCAN_64 1 +# pragma intrinsic(_BitScanForward64) +# pragma intrinsic(_BitScanReverse64) +# else +# define MSC_HAS_BIT_SCAN_64 0 +# endif +# if defined(_M_AMD64) || defined(_M_ARM64) || \ + defined(_M_IX86) || defined(_M_ARM) +# define MSC_HAS_BIT_SCAN 1 +# pragma intrinsic(_BitScanForward) +# pragma intrinsic(_BitScanReverse) +# else +# define MSC_HAS_BIT_SCAN 0 +# endif +# if defined(_M_AMD64) +# define MSC_HAS_UMUL128 1 +# pragma intrinsic(_umul128) +# else +# define MSC_HAS_UMUL128 0 +# endif +#else +# define MSC_HAS_BIT_SCAN_64 0 +# define MSC_HAS_BIT_SCAN 0 +# define MSC_HAS_UMUL128 0 +#endif + +/* gcc builtin */ +#if yyjson_has_builtin(__builtin_clzll) || yyjson_gcc_available(3, 4, 0) +# define GCC_HAS_CLZLL 1 +#else +# define GCC_HAS_CLZLL 0 +#endif + +#if yyjson_has_builtin(__builtin_ctzll) || yyjson_gcc_available(3, 4, 0) +# define GCC_HAS_CTZLL 1 +#else +# define GCC_HAS_CTZLL 0 +#endif + +/* int128 type */ +#if defined(__SIZEOF_INT128__) && (__SIZEOF_INT128__ == 16) && \ + (defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER)) +# define YYJSON_HAS_INT128 1 +#else +# define YYJSON_HAS_INT128 0 +#endif + +/* IEEE 754 floating-point binary representation */ +#if defined(__STDC_IEC_559__) || defined(__STDC_IEC_60559_BFP__) +# define YYJSON_HAS_IEEE_754 1 +#elif FLT_RADIX == 2 && \ + FLT_MANT_DIG == 24 && FLT_DIG == 6 && \ + FLT_MIN_EXP == -125 && FLT_MAX_EXP == 128 && \ + FLT_MIN_10_EXP == -37 && FLT_MAX_10_EXP == 38 && \ + DBL_MANT_DIG == 53 && DBL_DIG == 15 && \ + DBL_MIN_EXP == -1021 && DBL_MAX_EXP == 1024 && \ + DBL_MIN_10_EXP == -307 && DBL_MAX_10_EXP == 308 +# define YYJSON_HAS_IEEE_754 1 +#else +# define YYJSON_HAS_IEEE_754 0 +# undef YYJSON_DISABLE_FAST_FP_CONV +# define YYJSON_DISABLE_FAST_FP_CONV 1 +#endif + +/* + Correct rounding in double number computations. + + On the x86 architecture, some compilers may use x87 FPU instructions for + floating-point arithmetic. The x87 FPU loads all floating point number as + 80-bit double-extended precision internally, then rounds the result to original + precision, which may produce inaccurate results. For a more detailed + explanation, see the paper: https://arxiv.org/abs/cs/0701192 + + Here are some examples of double precision calculation error: + + 2877.0 / 1e6 == 0.002877, but x87 returns 0.0028770000000000002 + 43683.0 * 1e21 == 4.3683e25, but x87 returns 4.3683000000000004e25 + + Here are some examples of compiler flags to generate x87 instructions on x86: + + clang -m32 -mno-sse + gcc/icc -m32 -mfpmath=387 + msvc /arch:SSE or /arch:IA32 + + If we are sure that there's no similar error described above, we can define the + YYJSON_DOUBLE_MATH_CORRECT as 1 to enable the fast path calculation. This is + not an accurate detection, it's just try to avoid the error at compile-time. + An accurate detection can be done at run-time: + + bool is_double_math_correct(void) { + volatile double r = 43683.0; + r *= 1e21; + return r == 4.3683e25; + } + + See also: utils.h in https://github.com/google/double-conversion/ + */ +#if !defined(FLT_EVAL_METHOD) && defined(__FLT_EVAL_METHOD__) +# define FLT_EVAL_METHOD __FLT_EVAL_METHOD__ +#endif + +#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0 && FLT_EVAL_METHOD != 1 +# define YYJSON_DOUBLE_MATH_CORRECT 0 +#elif defined(i386) || defined(__i386) || defined(__i386__) || \ + defined(_X86_) || defined(__X86__) || defined(_M_IX86) || \ + defined(__I86__) || defined(__IA32__) || defined(__THW_INTEL) +# if (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 2) || \ + (defined(__SSE2_MATH__) && __SSE2_MATH__) +# define YYJSON_DOUBLE_MATH_CORRECT 1 +# else +# define YYJSON_DOUBLE_MATH_CORRECT 0 +# endif +#elif defined(__mc68000__) || defined(__pnacl__) || defined(__native_client__) +# define YYJSON_DOUBLE_MATH_CORRECT 0 +#else +# define YYJSON_DOUBLE_MATH_CORRECT 1 +#endif + +/* + Detect the endianness at compile-time. + YYJSON_ENDIAN == YYJSON_BIG_ENDIAN + YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN + */ +#define YYJSON_BIG_ENDIAN 4321 +#define YYJSON_LITTLE_ENDIAN 1234 + +#if yyjson_has_include() +# include /* POSIX */ +#endif +#if yyjson_has_include() +# include /* Linux */ +#elif yyjson_has_include() +# include /* BSD, Android */ +#elif yyjson_has_include() +# include /* BSD, Darwin */ +#endif + +#if defined(BYTE_ORDER) && BYTE_ORDER +# if defined(BIG_ENDIAN) && (BYTE_ORDER == BIG_ENDIAN) +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +# elif defined(LITTLE_ENDIAN) && (BYTE_ORDER == LITTLE_ENDIAN) +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +# endif +#elif defined(__BYTE_ORDER) && __BYTE_ORDER +# if defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN) +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +# elif defined(__LITTLE_ENDIAN) && (__BYTE_ORDER == __LITTLE_ENDIAN) +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +# endif +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ +# if defined(__ORDER_BIG_ENDIAN__) && \ + (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +# elif defined(__ORDER_LITTLE_ENDIAN__) && \ + (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +# endif +#elif (defined(__LITTLE_ENDIAN__) && __LITTLE_ENDIAN__ == 1) || \ + defined(__i386) || defined(__i386__) || \ + defined(_X86_) || defined(__X86__) || \ + defined(_M_IX86) || defined(__THW_INTEL__) || \ + defined(__x86_64) || defined(__x86_64__) || \ + defined(__amd64) || defined(__amd64__) || \ + defined(_M_AMD64) || defined(_M_X64) || \ + defined(_M_ARM) || defined(_M_ARM64) || \ + defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || \ + defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ + defined(__EMSCRIPTEN__) || defined(__wasm__) || \ + defined(__loongarch__) +# define YYJSON_ENDIAN YYJSON_LITTLE_ENDIAN +#elif (defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ == 1) || \ + defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__) || \ + defined(__or1k__) || defined(__OR1K__) +# define YYJSON_ENDIAN YYJSON_BIG_ENDIAN +#else +# define YYJSON_ENDIAN 0 /* unknown endian, detect at run-time */ +#endif + +/* + This macro controls how yyjson handles unaligned memory accesses. + + By default, yyjson uses `memcpy()` for memory copying. This allows the compiler + to optimize the code and emit unaligned memory access instructions when + supported by the target architecture. + + However, on some older compilers or architectures where `memcpy()` is not + well-optimized and may result in unnecessary function calls, defining this + macro as 1 may help. In such cases, yyjson switches to manual byte-by-byte + access, which can potentially improve performance. + + An example of the generated assembly code for ARM can be found here: + https://godbolt.org/z/334jjhxPT + + This flag is already enabled for common architectures in the following code, + so manual configuration is usually unnecessary. If unsure, you can check the + generated assembly or run benchmarks to make an informed decision. + */ +#ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS +# if defined(__ia64) || defined(_IA64) || defined(__IA64__) || \ + defined(__ia64__) || defined(_M_IA64) || defined(__itanium__) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* Itanium */ +# elif (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) && \ + (defined(__GNUC__) || defined(__clang__)) && \ + (!defined(__ARM_FEATURE_UNALIGNED) || !__ARM_FEATURE_UNALIGNED) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* ARM */ +# elif defined(__sparc) || defined(__sparc__) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* SPARC */ +# elif defined(__mips) || defined(__mips__) || defined(__MIPS__) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* MIPS */ +# elif defined(__m68k__) || defined(M68000) +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 1 /* M68K */ +# else +# define YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS 0 +# endif +#endif + +/* + Estimated initial ratio of the JSON data (data_size / value_count). + For example: + + data: {"id":12345678,"name":"Harry"} + data_size: 30 + value_count: 5 + ratio: 6 + + yyjson uses dynamic memory with a growth factor of 1.5 when reading and writing + JSON, the ratios below are used to determine the initial memory size. + + A too large ratio will waste memory, and a too small ratio will cause multiple + memory growths and degrade performance. Currently, these ratios are generated + with some commonly used JSON datasets. + */ +#define YYJSON_READER_ESTIMATED_PRETTY_RATIO 16 +#define YYJSON_READER_ESTIMATED_MINIFY_RATIO 6 +#define YYJSON_WRITER_ESTIMATED_PRETTY_RATIO 32 +#define YYJSON_WRITER_ESTIMATED_MINIFY_RATIO 18 + +/* The initial and maximum size of the memory pool's chunk in yyjson_mut_doc. */ +#define YYJSON_MUT_DOC_STR_POOL_INIT_SIZE 0x100 +#define YYJSON_MUT_DOC_STR_POOL_MAX_SIZE 0x10000000 +#define YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE (0x10 * sizeof(yyjson_mut_val)) +#define YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE (0x1000000 * sizeof(yyjson_mut_val)) + +/* The minimum size of the dynamic allocator's chunk. */ +#define YYJSON_ALC_DYN_MIN_SIZE 0x1000 + +/* Default value for compile-time options. */ +#ifndef YYJSON_DISABLE_READER +#define YYJSON_DISABLE_READER 0 +#endif +#ifndef YYJSON_DISABLE_WRITER +#define YYJSON_DISABLE_WRITER 0 +#endif +#ifndef YYJSON_DISABLE_INCR_READER +#define YYJSON_DISABLE_INCR_READER 0 +#endif +#ifndef YYJSON_DISABLE_UTILS +#define YYJSON_DISABLE_UTILS 0 +#endif +#ifndef YYJSON_DISABLE_FAST_FP_CONV +#define YYJSON_DISABLE_FAST_FP_CONV 0 +#endif +#ifndef YYJSON_DISABLE_NON_STANDARD +#define YYJSON_DISABLE_NON_STANDARD 0 +#endif +#ifndef YYJSON_DISABLE_UTF8_VALIDATION +#define YYJSON_DISABLE_UTF8_VALIDATION 0 +#endif + + + +/*============================================================================== + * MARK: - Macros (Private) + *============================================================================*/ + +/* Macros used for loop unrolling and other purpose. */ +#define repeat2(x) { x x } +#define repeat4(x) { x x x x } +#define repeat8(x) { x x x x x x x x } +#define repeat16(x) { x x x x x x x x x x x x x x x x } + +#define repeat2_incr(x) { x(0) x(1) } +#define repeat4_incr(x) { x(0) x(1) x(2) x(3) } +#define repeat8_incr(x) { x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) } +#define repeat16_incr(x) { x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) \ + x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15) } +#define repeat_in_1_18(x) { x(1) x(2) x(3) x(4) x(5) x(6) x(7) x(8) \ + x(9) x(10) x(11) x(12) x(13) x(14) x(15) x(16) \ + x(17) x(18) } + +/* Macros used to provide branch prediction information for compiler. */ +#undef likely +#define likely(x) yyjson_likely(x) +#undef unlikely +#define unlikely(x) yyjson_unlikely(x) + +/* Macros used to provide inline information for compiler. */ +#undef static_inline +#define static_inline static yyjson_inline +#undef static_noinline +#define static_noinline static yyjson_noinline + +/* Macros for min and max. */ +#undef yyjson_min +#define yyjson_min(x, y) ((x) < (y) ? (x) : (y)) +#undef yyjson_max +#define yyjson_max(x, y) ((x) > (y) ? (x) : (y)) + +/* Used to write u64 literal for C89 which doesn't support "ULL" suffix. */ +#undef U64 +#define U64(hi, lo) ((((u64)hi##UL) << 32U) + lo##UL) +#undef U32 +#define U32(hi) ((u32)(hi##UL)) + +/* Used to cast away (remove) const qualifier. */ +#define constcast(type) (type)(void *)(size_t)(const void *) + +/* + Compiler barriers for single variables. + + These macros inform GCC that a read or write access to the given memory + location will occur, preventing certain compiler optimizations or reordering + around the access to 'val'. They do not emit any actual instructions. + + This is useful when GCC's default optimization strategies are suboptimal and + precise control over memory access patterns is required. + These barriers are not needed when using Clang or MSVC. + */ +#if YYJSON_IS_REAL_GCC +# define gcc_load_barrier(val) __asm__ volatile(""::"m"(val)) +# define gcc_store_barrier(val) __asm__ volatile("":"=m"(val)) +# define gcc_full_barrier(val) __asm__ volatile("":"=m"(val):"m"(val)) +#else +# define gcc_load_barrier(val) +# define gcc_store_barrier(val) +# define gcc_full_barrier(val) +#endif + + + +/*============================================================================== + * MARK: - Constants (Private) + *============================================================================*/ + +/* Common error messages. */ +#define MSG_FOPEN "failed to open file" +#define MSG_FREAD "failed to read file" +#define MSG_FWRITE "failed to write file" +#define MSG_FCLOSE "failed to close file" +#define MSG_MALLOC "failed to allocate memory" +#define MSG_CHAR_T "invalid literal, expected 'true'" +#define MSG_CHAR_F "invalid literal, expected 'false'" +#define MSG_CHAR_N "invalid literal, expected 'null'" +#define MSG_CHAR "unexpected character, expected a JSON value" +#define MSG_ARR_END "unexpected character, expected ',' or ']'" +#define MSG_OBJ_KEY "unexpected character, expected a string key" +#define MSG_OBJ_SEP "unexpected character, expected ':' after key" +#define MSG_OBJ_END "unexpected character, expected ',' or '}'" +#define MSG_GARBAGE "unexpected content after document" +#define MSG_NOT_END "unexpected end of data" +#define MSG_COMMENT "unclosed multiline comment" +#define MSG_COMMA "trailing comma is not allowed" +#define MSG_NAN_INF "nan or inf number is not allowed" +#define MSG_ERR_TYPE "invalid JSON value type" +#define MSG_ERR_BOM "UTF-8 byte order mark (BOM) is not supported" +#define MSG_ERR_UTF8 "invalid utf-8 encoding in string" +#define MSG_ERR_UTF16 "UTF-16 encoding is not supported" +#define MSG_ERR_UTF32 "UTF-32 encoding is not supported" + +/* U64 constant values */ +#undef U64_MAX +#define U64_MAX U64(0xFFFFFFFF, 0xFFFFFFFF) +#undef I64_MAX +#define I64_MAX U64(0x7FFFFFFF, 0xFFFFFFFF) +#undef USIZE_MAX +#define USIZE_MAX ((usize)(~(usize)0)) + +/* Maximum number of digits for reading u32/u64/usize safety (not overflow). */ +#undef U32_SAFE_DIG +#define U32_SAFE_DIG 9 /* u32 max is 4294967295, 10 digits */ +#undef U64_SAFE_DIG +#define U64_SAFE_DIG 19 /* u64 max is 18446744073709551615, 20 digits */ +#undef USIZE_SAFE_DIG +#define USIZE_SAFE_DIG (sizeof(usize) == 8 ? U64_SAFE_DIG : U32_SAFE_DIG) + +/* Inf bits (positive) */ +#define F64_BITS_INF U64(0x7FF00000, 0x00000000) + +/* NaN bits (quiet NaN, no payload, no sign) */ +#if defined(__hppa__) || (defined(__mips__) && !defined(__mips_nan2008)) +#define F64_BITS_NAN U64(0x7FF7FFFF, 0xFFFFFFFF) +#else +#define F64_BITS_NAN U64(0x7FF80000, 0x00000000) +#endif + +/* maximum significant digits count in decimal when reading double number */ +#define F64_MAX_DEC_DIG 768 + +/* maximum decimal power of double number (1.7976931348623157e308) */ +#define F64_MAX_DEC_EXP 308 + +/* minimum decimal power of double number (4.9406564584124654e-324) */ +#define F64_MIN_DEC_EXP (-324) + +/* maximum binary power of double number */ +#define F64_MAX_BIN_EXP 1024 + +/* minimum binary power of double number */ +#define F64_MIN_BIN_EXP (-1021) + +/* float/double number bits */ +#define F32_BITS 32 +#define F64_BITS 64 + +/* float/double number exponent part bits */ +#define F32_EXP_BITS 8 +#define F64_EXP_BITS 11 + +/* float/double number significand part bits */ +#define F32_SIG_BITS 23 +#define F64_SIG_BITS 52 + +/* float/double number significand part bits (with 1 hidden bit) */ +#define F32_SIG_FULL_BITS 24 +#define F64_SIG_FULL_BITS 53 + +/* float/double number significand bit mask */ +#define F32_SIG_MASK U32(0x007FFFFF) +#define F64_SIG_MASK U64(0x000FFFFF, 0xFFFFFFFF) + +/* float/double number exponent bit mask */ +#define F32_EXP_MASK U32(0x7F800000) +#define F64_EXP_MASK U64(0x7FF00000, 0x00000000) + +/* float/double number exponent bias */ +#define F32_EXP_BIAS 127 +#define F64_EXP_BIAS 1023 + +/* float/double number significant digits count in decimal */ +#define F32_DEC_DIG 9 +#define F64_DEC_DIG 17 + +/* buffer length required for float/double number writer */ +#define FP_BUF_LEN 40 + +/* maximum length of a number in incremental parsing */ +#define INCR_NUM_MAX_LEN 1024 + + + +/*============================================================================== + * MARK: - Types (Private) + *============================================================================*/ + +/** Type define for primitive types. */ +typedef float f32; +typedef double f64; +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint16_t u16; +typedef int32_t i32; +typedef uint32_t u32; +typedef int64_t i64; +typedef uint64_t u64; +typedef size_t usize; + +/** 128-bit integer, used by floating-point number reader and writer. */ +#if YYJSON_HAS_INT128 +__extension__ typedef __int128 i128; +__extension__ typedef unsigned __int128 u128; +#endif + +/** 16/32/64-bit vector */ +typedef struct v16 { char c[2]; } v16; +typedef struct v32 { char c[4]; } v32; +typedef struct v64 { char c[8]; } v64; + +/** 16/32/64-bit vector union */ +typedef union v16_uni { v16 v; u16 u; } v16_uni; +typedef union v32_uni { v32 v; u32 u; } v32_uni; +typedef union v64_uni { v64 v; u64 u; } v64_uni; + + + +/*============================================================================== + * MARK: - Load/Store Utils (Private) + *============================================================================*/ + +#define byte_move_idx(x) ((char *)dst)[x] = ((const char *)src)[x]; +#define byte_move_src(x) ((char *)tmp)[x] = ((const char *)src)[x]; +#define byte_move_dst(x) ((char *)dst)[x] = ((const char *)tmp)[x]; + +/** Same as `memcpy(dst, src, 2)`, no overlap. */ +static_inline void byte_copy_2(void *dst, const void *src) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + memcpy(dst, src, 2); +#else + repeat2_incr(byte_move_idx) +#endif +} + +/** Same as `memcpy(dst, src, 4)`, no overlap. */ +static_inline void byte_copy_4(void *dst, const void *src) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + memcpy(dst, src, 4); +#else + repeat4_incr(byte_move_idx) +#endif +} + +/** Same as `memcpy(dst, src, 8)`, no overlap. */ +static_inline void byte_copy_8(void *dst, const void *src) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + memcpy(dst, src, 8); +#else + repeat8_incr(byte_move_idx) +#endif +} + +/** Same as `memcpy(dst, src, 16)`, no overlap. */ +static_inline void byte_copy_16(void *dst, const void *src) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + memcpy(dst, src, 16); +#else + repeat16_incr(byte_move_idx) +#endif +} + +/** Same as `memmove(dst, src, 2)`, allows overlap. */ +static_inline void byte_move_2(void *dst, const void *src) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + u16 tmp; + memcpy(&tmp, src, 2); + memcpy(dst, &tmp, 2); +#else + char tmp[2]; + repeat2_incr(byte_move_src) + repeat2_incr(byte_move_dst) +#endif +} + +/** Same as `memmove(dst, src, 4)`, allows overlap. */ +static_inline void byte_move_4(void *dst, const void *src) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + u32 tmp; + memcpy(&tmp, src, 4); + memcpy(dst, &tmp, 4); +#else + char tmp[4]; + repeat4_incr(byte_move_src) + repeat4_incr(byte_move_dst) +#endif +} + +/** Same as `memmove(dst, src, 8)`, allows overlap. */ +static_inline void byte_move_8(void *dst, const void *src) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + u64 tmp; + memcpy(&tmp, src, 8); + memcpy(dst, &tmp, 8); +#else + char tmp[8]; + repeat8_incr(byte_move_src) + repeat8_incr(byte_move_dst) +#endif +} + +/** Same as `memmove(dst, src, 16)`, allows overlap. */ +static_inline void byte_move_16(void *dst, const void *src) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + char *pdst = (char *)dst; + const char *psrc = (const char *)src; + u64 tmp1, tmp2; + memcpy(&tmp1, psrc, 8); + memcpy(&tmp2, psrc + 8, 8); + memcpy(pdst, &tmp1, 8); + memcpy(pdst + 8, &tmp2, 8); +#else + char tmp[16]; + repeat16_incr(byte_move_src) + repeat16_incr(byte_move_dst) +#endif +} + +/** Same as `memmove(dst, src, n)`, but only `dst <= src` and `n <= 16`. */ +static_inline void byte_move_forward(void *dst, void *src, usize n) { + char *d = (char *)dst, *s = (char *)src; + n += (n % 2); /* round up to even */ + if (n == 16) { byte_move_16(d, s); return; } + if (n >= 8) { byte_move_8(d, s); n -= 8; d += 8; s += 8; } + if (n >= 4) { byte_move_4(d, s); n -= 4; d += 4; s += 4; } + if (n >= 2) { byte_move_2(d, s); } +} + +/** Same as `memcmp(buf, pat, 2) == 0`. */ +static_inline bool byte_match_2(void *buf, const char *pat) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + v16_uni u1, u2; + memcpy(&u1, buf, 2); + memcpy(&u2, pat, 2); + return u1.u == u2.u; +#else + return ((char *)buf)[0] == ((const char *)pat)[0] && + ((char *)buf)[1] == ((const char *)pat)[1]; +#endif +} + +/** Same as `memcmp(buf, pat, 4) == 0`. */ +static_inline bool byte_match_4(void *buf, const char *pat) { +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + v32_uni u1, u2; + memcpy(&u1, buf, 4); + memcpy(&u2, pat, 4); + return u1.u == u2.u; +#else + return ((char *)buf)[0] == ((const char *)pat)[0] && + ((char *)buf)[1] == ((const char *)pat)[1] && + ((char *)buf)[2] == ((const char *)pat)[2] && + ((char *)buf)[3] == ((const char *)pat)[3]; +#endif +} + +/** Loads 2 bytes from `src` as a u16 (native-endian). */ +static_inline u16 byte_load_2(const void *src) { + v16_uni uni; +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + memcpy(&uni, src, 2); +#else + uni.v.c[0] = ((const char *)src)[0]; + uni.v.c[1] = ((const char *)src)[1]; +#endif + return uni.u; +} + +/** Loads 3 bytes from `src` as a u32 (native-endian). */ +static_inline u32 byte_load_3(const void *src) { + v32_uni uni; +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + memcpy(&uni, src, 2); + uni.v.c[2] = ((const char *)src)[2]; + uni.v.c[3] = 0; +#else + uni.v.c[0] = ((const char *)src)[0]; + uni.v.c[1] = ((const char *)src)[1]; + uni.v.c[2] = ((const char *)src)[2]; + uni.v.c[3] = 0; +#endif + return uni.u; +} + +/** Loads 4 bytes from `src` as a u32 (native-endian). */ +static_inline u32 byte_load_4(const void *src) { + v32_uni uni; +#if !YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS + memcpy(&uni, src, 4); +#else + uni.v.c[0] = ((const char *)src)[0]; + uni.v.c[1] = ((const char *)src)[1]; + uni.v.c[2] = ((const char *)src)[2]; + uni.v.c[3] = ((const char *)src)[3]; +#endif + return uni.u; +} + + + +/*============================================================================== + * MARK: - Character Utils (Private) + * These lookup tables were generated by `misc/make_tables.c`. + *============================================================================*/ + +/* char_table1 */ +#define CHAR_TYPE_ASCII (1 << 0) /* Except: ["\], [0x00-0x1F, 0x80-0xFF] */ +#define CHAR_TYPE_ASCII_SQ (1 << 1) /* Except: ['\], [0x00-0x1F, 0x80-0xFF] */ +#define CHAR_TYPE_SPACE (1 << 2) /* Whitespace: [ \t\n\r] */ +#define CHAR_TYPE_SPACE_EXT (1 << 3) /* Whitespace: [ \t\n\r\v\f], JSON5 */ +#define CHAR_TYPE_NUM (1 << 4) /* Number: [.-+0-9] */ +#define CHAR_TYPE_COMMENT (1 << 5) /* Comment: [/] */ + +/* char_table2 */ +#define CHAR_TYPE_EOL (1 << 0) /* End of line: [\r\n] */ +#define CHAR_TYPE_EOL_EXT (1 << 1) /* End of line: [\r\n], JSON5 */ +#define CHAR_TYPE_ID_START (1 << 2) /* ID start: [_$A-Za-z\], U+0080+ */ +#define CHAR_TYPE_ID_NEXT (1 << 3) /* ID next: [_$A-Za-z0-9\], U+0080+ */ +#define CHAR_TYPE_ID_ASCII (1 << 4) /* ID next ASCII: [_$A-Za-z0-9] */ + +/* char_table3 */ +#define CHAR_TYPE_SIGN (1 << 0) /* [-+] */ +#define CHAR_TYPE_DIGIT (1 << 1) /* [0-9] */ +#define CHAR_TYPE_NONZERO (1 << 2) /* [1-9] */ +#define CHAR_TYPE_EXP (1 << 3) /* [eE] */ +#define CHAR_TYPE_DOT (1 << 4) /* [.] */ + +static const u8 char_table1[256] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0C, 0x0C, 0x08, 0x08, 0x0C, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x03, 0x02, 0x03, 0x03, 0x03, 0x03, 0x01, + 0x03, 0x03, 0x03, 0x13, 0x03, 0x13, 0x13, 0x23, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x00, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const u8 char_table2[256] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x00, 0x0C, 0x00, 0x00, 0x1C, + 0x00, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, + 0x1C, 0x1C, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C +}; + +static const u8 char_table3[256] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x10, 0x00, + 0x02, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/** Match a whitespace: [ \t\n\r]. */ +static_inline bool char_is_space(u8 c) { + return !!(char_table1[c] & CHAR_TYPE_SPACE); +} + +/** Match an extended whitespace: [ \t\n\r\\v\\f], JSON5 whitespace. */ +static_inline bool char_is_space_ext(u8 c) { + return !!(char_table1[c] & CHAR_TYPE_SPACE_EXT); +} + +/** Match a JSON number: [.-+0-9]. */ +static_inline bool char_is_num(u8 c) { + return !!(char_table1[c] & CHAR_TYPE_NUM); +} + +/** Match an ASCII character in string: ["\], [0x00-0x1F, 0x80-0xFF]. */ +static_inline bool char_is_ascii_skip(u8 c) { + return !!(char_table1[c] & CHAR_TYPE_ASCII); +} + +/** Match an ASCII character single-quoted: ['\], [0x00-0x1F, 0x80-0xFF]. */ +static_inline bool char_is_ascii_skip_sq(u8 c) { + return !!(char_table1[c] & CHAR_TYPE_ASCII_SQ); +} + +/** Match a trivia character: extended whitespace or comment. */ +static_inline bool char_is_trivia(u8 c) { + return !!(char_table1[c] & (CHAR_TYPE_SPACE_EXT | CHAR_TYPE_COMMENT)); +} + +/** Match a line end character: [\r\n]. */ +static_inline bool char_is_eol(u8 c) { + return !!(char_table2[c] & CHAR_TYPE_EOL); +} + +/** Match an extended line end character: [\r\n], JSON5 line terminator. */ +static_inline bool char_is_eol_ext(u8 c) { + return !!(char_table2[c] & CHAR_TYPE_EOL_EXT); +} + +/** Match an identifier name start: [_$A-Za-z\], U+0080+. */ +static_inline bool char_is_id_start(u8 c) { + return !!(char_table2[c] & CHAR_TYPE_ID_START); +} + +/** Match an identifier name next: [_$A-Za-z0-9\], U+0080+. */ +static_inline bool char_is_id_next(u8 c) { + return !!(char_table2[c] & CHAR_TYPE_ID_NEXT); +} + +/** Match an identifier name ASCII: [_$A-Za-z0-9]. */ +static_inline bool char_is_id_ascii(u8 c) { + return !!(char_table2[c] & CHAR_TYPE_ID_ASCII); +} + +/** Match a sign: [+-] */ +static_inline bool char_is_sign(u8 d) { + return !!(char_table3[d] & CHAR_TYPE_SIGN); +} + +/** Match a none-zero digit: [1-9] */ +static_inline bool char_is_nonzero(u8 d) { + return !!(char_table3[d] & CHAR_TYPE_NONZERO); +} + +/** Match a digit: [0-9] */ +static_inline bool char_is_digit(u8 d) { + return !!(char_table3[d] & CHAR_TYPE_DIGIT); +} + +/** Match an exponent sign: [eE]. */ +static_inline bool char_is_exp(u8 d) { + return !!(char_table3[d] & CHAR_TYPE_EXP); +} + +/** Match a floating point indicator: [.eE]. */ +static_inline bool char_is_fp(u8 d) { + return !!(char_table3[d] & (CHAR_TYPE_DOT | CHAR_TYPE_EXP)); +} + +/** Match a digit or floating point indicator: [0-9.eE]. */ +static_inline bool char_is_digit_or_fp(u8 d) { + return !!(char_table3[d] & (CHAR_TYPE_DIGIT | CHAR_TYPE_DOT | + CHAR_TYPE_EXP)); +} + +/** Match a JSON container: `{` or `[`. */ +static_inline bool char_is_ctn(u8 c) { + return (c & 0xDF) == 0x5B; /* '[': 0x5B, '{': 0x7B */ +} + +/** Convert ASCII letter to lowercase; valid only for [A-Za-z]. */ +static_inline u8 char_to_lower(u8 c) { + return c | 0x20; +} + +/** Match UTF-8 byte order mask. */ +static_inline bool is_utf8_bom(const u8 *cur) { + return byte_load_3(cur) == byte_load_3("\xEF\xBB\xBF"); +} + +/** Match UTF-16 byte order mask. */ +static_inline bool is_utf16_bom(const u8 *cur) { + return byte_load_2(cur) == byte_load_2("\xFE\xFF") || + byte_load_2(cur) == byte_load_2("\xFF\xFE"); +} + +/** Match UTF-32 byte order mask, need length check to avoid zero padding. */ +static_inline bool is_utf32_bom(const u8 *cur) { + return byte_load_4(cur) == byte_load_4("\x00\x00\xFE\xFF") || + byte_load_4(cur) == byte_load_4("\xFF\xFE\x00\x00"); +} + +/** Get the extended line end length. Used with `char_is_eol_ext`. */ +static_inline usize ext_eol_len(const u8 *cur) { + if (cur[0] < 0x80) return 1; + if (cur[1] == 0x80 && (cur[2] == 0xA8 || cur[2] == 0xA9)) return 3; + return 0; +} + +/** Get the extended whitespace length. Used with `char_is_space_ext`. */ +static_inline usize ext_space_len(const u8 *cur) { + if (cur[0] < 0x80) { + return 1; + } else if (byte_load_2(cur) == byte_load_2("\xC2\xA0")) { + return 2; + } else if (byte_load_2(cur) == byte_load_2("\xE2\x80")) { + if (cur[2] >= 0x80 && cur[2] <= 0x8A) return 3; + if (cur[2] == 0xA8 || cur[2] == 0xA9 || cur[2] == 0xAF) return 3; + } else { + u32 uni = byte_load_3(cur); + if (uni == byte_load_3("\xE1\x9A\x80") || + uni == byte_load_3("\xE2\x81\x9F") || + uni == byte_load_3("\xE3\x80\x80") || + uni == byte_load_3("\xEF\xBB\xBF")) return 3; + } + return 0; +} + + + +/*============================================================================== + * MARK: - Hex Character Reader (Private) + * This function is used by JSON reader to read escaped characters. + *============================================================================*/ + +/** + This table is used to convert 4 hex character sequence to a number. + A valid hex character [0-9A-Fa-f] will mapped to it's raw number [0x00, 0x0F], + an invalid hex character will mapped to [0xF0]. + (generate with misc/make_tables.c) + */ +static const u8 hex_conv_table[256] = { + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0 +}; + +/** Load 4 hex characters to `u16`, return true on valid input. */ +static_inline bool hex_load_4(const u8 *src, u16 *dst) { + u16 c0 = hex_conv_table[src[0]]; + u16 c1 = hex_conv_table[src[1]]; + u16 c2 = hex_conv_table[src[2]]; + u16 c3 = hex_conv_table[src[3]]; + u16 t0 = (u16)((c0 << 8) | c2); + u16 t1 = (u16)((c1 << 8) | c3); + *dst = (u16)((t0 << 4) | t1); + return ((t0 | t1) & (u16)0xF0F0) == 0; +} + +/** Load 2 hex characters to `u8`, return true on valid input. */ +static_inline bool hex_load_2(const u8 *src, u8 *dst) { + u8 c0 = hex_conv_table[src[0]]; + u8 c1 = hex_conv_table[src[1]]; + *dst = (u8)((c0 << 4) | c1); + return ((c0 | c1) & 0xF0) == 0; +} + +/** Match a hexadecimal numeric character: [0-9a-fA-F]. */ +static_inline bool char_is_hex(u8 c) { + return hex_conv_table[c] != 0xF0; +} + + + +/*============================================================================== + * MARK: - UTF8 Validation (Private) + * Each Unicode code point is encoded using 1 to 4 bytes in UTF-8. + * Validation is performed using a 4-byte mask and pattern-based approach, + * which requires the input data to be padded with four zero bytes at the end. + *============================================================================*/ + +/* Macro for concatenating four u8 into a u32 and keeping the byte order. */ +#if YYJSON_ENDIAN == YYJSON_LITTLE_ENDIAN +# define utf8_seq_def(name, a, b, c, d) \ + static const u32 utf8_seq_##name = 0x##d##c##b##a##UL; +# define utf8_seq(name) utf8_seq_##name +#elif YYJSON_ENDIAN == YYJSON_BIG_ENDIAN +# define utf8_seq_def(name, a, b, c, d) \ + static const u32 utf8_seq_##name = 0x##a##b##c##d##UL; +# define utf8_seq(name) utf8_seq_##name +#else +# define utf8_seq_def(name, a, b, c, d) \ + static const v32_uni utf8_uni_##name = {{ 0x##a, 0x##b, 0x##c, 0x##d }}; +# define utf8_seq(name) utf8_uni_##name.u +#endif + +/* + 1-byte sequence (U+0000 to U+007F) + bit min [.......0] (U+0000) + bit max [.1111111] (U+007F) + bit mask [x.......] (80) + bit pattern [0.......] (00) + */ +utf8_seq_def(b1_mask, 80, 00, 00, 00) +utf8_seq_def(b1_patt, 00, 00, 00, 00) +#define is_utf8_seq1(uni) ( \ + ((uni & utf8_seq(b1_mask)) == utf8_seq(b1_patt)) ) + +/* + 2-byte sequence (U+0080 to U+07FF) + bit min [......10 ..000000] (U+0080) + bit max [...11111 ..111111] (U+07FF) + bit mask [xxx..... xx......] (E0 C0) + bit pattern [110..... 10......] (C0 80) + bit require [...xxxx. ........] (1E 00) + */ +utf8_seq_def(b2_mask, E0, C0, 00, 00) +utf8_seq_def(b2_patt, C0, 80, 00, 00) +utf8_seq_def(b2_requ, 1E, 00, 00, 00) +#define is_utf8_seq2(uni) ( \ + ((uni & utf8_seq(b2_mask)) == utf8_seq(b2_patt)) && \ + ((uni & utf8_seq(b2_requ))) ) + +/* + 3-byte sequence (U+0800 to U+FFFF) + bit min [........ ..100000 ..000000] (U+0800) + bit max [....1111 ..111111 ..111111] (U+FFFF) + bit mask [xxxx.... xx...... xx......] (F0 C0 C0) + bit pattern [1110.... 10...... 10......] (E0 80 80) + bit require [....xxxx ..x..... ........] (0F 20 00) + + 3-byte invalid sequence, reserved for surrogate halves (U+D800 to U+DFFF) + bit min [....1101 ..100000 ..000000] (U+D800) + bit max [....1101 ..111111 ..111111] (U+DFFF) + bit mask [....xxxx ..x..... ........] (0F 20 00) + bit pattern [....1101 ..1..... ........] (0D 20 00) + */ +utf8_seq_def(b3_mask, F0, C0, C0, 00) +utf8_seq_def(b3_patt, E0, 80, 80, 00) +utf8_seq_def(b3_requ, 0F, 20, 00, 00) +utf8_seq_def(b3_erro, 0D, 20, 00, 00) +#define is_utf8_seq3(uni) ( \ + ((uni & utf8_seq(b3_mask)) == utf8_seq(b3_patt)) && \ + ((tmp = (uni & utf8_seq(b3_requ)))) && \ + ((tmp != utf8_seq(b3_erro))) ) + +/* + 4-byte sequence (U+10000 to U+10FFFF) + bit min [........ ...10000 ..000000 ..000000] (U+10000) + bit max [.....100 ..001111 ..111111 ..111111] (U+10FFFF) + bit mask [xxxxx... xx...... xx...... xx......] (F8 C0 C0 C0) + bit pattern [11110... 10...... 10...... 10......] (F0 80 80 80) + bit require [.....xxx ..xx.... ........ ........] (07 30 00 00) + bit require 1 [.....x.. ........ ........ ........] (04 00 00 00) + bit require 2 [......xx ..xx.... ........ ........] (03 30 00 00) + */ +utf8_seq_def(b4_mask, F8, C0, C0, C0) +utf8_seq_def(b4_patt, F0, 80, 80, 80) +utf8_seq_def(b4_requ, 07, 30, 00, 00) +utf8_seq_def(b4_req1, 04, 00, 00, 00) +utf8_seq_def(b4_req2, 03, 30, 00, 00) +#define is_utf8_seq4(uni) ( \ + ((uni & utf8_seq(b4_mask)) == utf8_seq(b4_patt)) && \ + ((tmp = (uni & utf8_seq(b4_requ)))) && \ + ((tmp & utf8_seq(b4_req1)) == 0 || (tmp & utf8_seq(b4_req2)) == 0) ) + + + +/*============================================================================== + * MARK: - Power10 Lookup Table (Private) + * These data are used by the floating-point number reader and writer. + *============================================================================*/ + +#if !YYJSON_DISABLE_FAST_FP_CONV + +/** Maximum pow10 exponent that can be represented exactly as a float64. */ +#define F64_POW10_MAX_EXACT_EXP 22 + +/** Cached pow10 table. */ +static const f64 f64_pow10_table[F64_POW10_MAX_EXACT_EXP + 1] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, + 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 +}; + +/** Maximum pow10 exponent that can be represented exactly as a uint64. */ +#define U64_POW10_MAX_EXACT_EXP 19 + +/** Table: [ 10^0, ..., 10^19 ] (generate with misc/make_tables.c) */ +static const u64 u64_pow10_table[U64_POW10_MAX_EXACT_EXP + 1] = { + U64(0x00000000, 0x00000001), U64(0x00000000, 0x0000000A), + U64(0x00000000, 0x00000064), U64(0x00000000, 0x000003E8), + U64(0x00000000, 0x00002710), U64(0x00000000, 0x000186A0), + U64(0x00000000, 0x000F4240), U64(0x00000000, 0x00989680), + U64(0x00000000, 0x05F5E100), U64(0x00000000, 0x3B9ACA00), + U64(0x00000002, 0x540BE400), U64(0x00000017, 0x4876E800), + U64(0x000000E8, 0xD4A51000), U64(0x00000918, 0x4E72A000), + U64(0x00005AF3, 0x107A4000), U64(0x00038D7E, 0xA4C68000), + U64(0x002386F2, 0x6FC10000), U64(0x01634578, 0x5D8A0000), + U64(0x0DE0B6B3, 0xA7640000), U64(0x8AC72304, 0x89E80000) +}; + +/** Minimum decimal exponent in pow10_sig_table. */ +#define POW10_SIG_TABLE_MIN_EXP -343 + +/** Maximum decimal exponent in pow10_sig_table. */ +#define POW10_SIG_TABLE_MAX_EXP 324 + +/** Minimum exact decimal exponent in pow10_sig_table */ +#define POW10_SIG_TABLE_MIN_EXACT_EXP 0 + +/** Maximum exact decimal exponent in pow10_sig_table */ +#define POW10_SIG_TABLE_MAX_EXACT_EXP 55 + +/** Normalized significant 128 bits of pow10, no rounded up (size: 10.4KB). + This lookup table is used by both the double number reader and writer. + (generate with misc/make_tables.c) */ +static const u64 pow10_sig_table[] = { + U64(0xBF29DCAB, 0xA82FDEAE), U64(0x7432EE87, 0x3880FC33), /* ~= 10^-343 */ + U64(0xEEF453D6, 0x923BD65A), U64(0x113FAA29, 0x06A13B3F), /* ~= 10^-342 */ + U64(0x9558B466, 0x1B6565F8), U64(0x4AC7CA59, 0xA424C507), /* ~= 10^-341 */ + U64(0xBAAEE17F, 0xA23EBF76), U64(0x5D79BCF0, 0x0D2DF649), /* ~= 10^-340 */ + U64(0xE95A99DF, 0x8ACE6F53), U64(0xF4D82C2C, 0x107973DC), /* ~= 10^-339 */ + U64(0x91D8A02B, 0xB6C10594), U64(0x79071B9B, 0x8A4BE869), /* ~= 10^-338 */ + U64(0xB64EC836, 0xA47146F9), U64(0x9748E282, 0x6CDEE284), /* ~= 10^-337 */ + U64(0xE3E27A44, 0x4D8D98B7), U64(0xFD1B1B23, 0x08169B25), /* ~= 10^-336 */ + U64(0x8E6D8C6A, 0xB0787F72), U64(0xFE30F0F5, 0xE50E20F7), /* ~= 10^-335 */ + U64(0xB208EF85, 0x5C969F4F), U64(0xBDBD2D33, 0x5E51A935), /* ~= 10^-334 */ + U64(0xDE8B2B66, 0xB3BC4723), U64(0xAD2C7880, 0x35E61382), /* ~= 10^-333 */ + U64(0x8B16FB20, 0x3055AC76), U64(0x4C3BCB50, 0x21AFCC31), /* ~= 10^-332 */ + U64(0xADDCB9E8, 0x3C6B1793), U64(0xDF4ABE24, 0x2A1BBF3D), /* ~= 10^-331 */ + U64(0xD953E862, 0x4B85DD78), U64(0xD71D6DAD, 0x34A2AF0D), /* ~= 10^-330 */ + U64(0x87D4713D, 0x6F33AA6B), U64(0x8672648C, 0x40E5AD68), /* ~= 10^-329 */ + U64(0xA9C98D8C, 0xCB009506), U64(0x680EFDAF, 0x511F18C2), /* ~= 10^-328 */ + U64(0xD43BF0EF, 0xFDC0BA48), U64(0x0212BD1B, 0x2566DEF2), /* ~= 10^-327 */ + U64(0x84A57695, 0xFE98746D), U64(0x014BB630, 0xF7604B57), /* ~= 10^-326 */ + U64(0xA5CED43B, 0x7E3E9188), U64(0x419EA3BD, 0x35385E2D), /* ~= 10^-325 */ + U64(0xCF42894A, 0x5DCE35EA), U64(0x52064CAC, 0x828675B9), /* ~= 10^-324 */ + U64(0x818995CE, 0x7AA0E1B2), U64(0x7343EFEB, 0xD1940993), /* ~= 10^-323 */ + U64(0xA1EBFB42, 0x19491A1F), U64(0x1014EBE6, 0xC5F90BF8), /* ~= 10^-322 */ + U64(0xCA66FA12, 0x9F9B60A6), U64(0xD41A26E0, 0x77774EF6), /* ~= 10^-321 */ + U64(0xFD00B897, 0x478238D0), U64(0x8920B098, 0x955522B4), /* ~= 10^-320 */ + U64(0x9E20735E, 0x8CB16382), U64(0x55B46E5F, 0x5D5535B0), /* ~= 10^-319 */ + U64(0xC5A89036, 0x2FDDBC62), U64(0xEB2189F7, 0x34AA831D), /* ~= 10^-318 */ + U64(0xF712B443, 0xBBD52B7B), U64(0xA5E9EC75, 0x01D523E4), /* ~= 10^-317 */ + U64(0x9A6BB0AA, 0x55653B2D), U64(0x47B233C9, 0x2125366E), /* ~= 10^-316 */ + U64(0xC1069CD4, 0xEABE89F8), U64(0x999EC0BB, 0x696E840A), /* ~= 10^-315 */ + U64(0xF148440A, 0x256E2C76), U64(0xC00670EA, 0x43CA250D), /* ~= 10^-314 */ + U64(0x96CD2A86, 0x5764DBCA), U64(0x38040692, 0x6A5E5728), /* ~= 10^-313 */ + U64(0xBC807527, 0xED3E12BC), U64(0xC6050837, 0x04F5ECF2), /* ~= 10^-312 */ + U64(0xEBA09271, 0xE88D976B), U64(0xF7864A44, 0xC633682E), /* ~= 10^-311 */ + U64(0x93445B87, 0x31587EA3), U64(0x7AB3EE6A, 0xFBE0211D), /* ~= 10^-310 */ + U64(0xB8157268, 0xFDAE9E4C), U64(0x5960EA05, 0xBAD82964), /* ~= 10^-309 */ + U64(0xE61ACF03, 0x3D1A45DF), U64(0x6FB92487, 0x298E33BD), /* ~= 10^-308 */ + U64(0x8FD0C162, 0x06306BAB), U64(0xA5D3B6D4, 0x79F8E056), /* ~= 10^-307 */ + U64(0xB3C4F1BA, 0x87BC8696), U64(0x8F48A489, 0x9877186C), /* ~= 10^-306 */ + U64(0xE0B62E29, 0x29ABA83C), U64(0x331ACDAB, 0xFE94DE87), /* ~= 10^-305 */ + U64(0x8C71DCD9, 0xBA0B4925), U64(0x9FF0C08B, 0x7F1D0B14), /* ~= 10^-304 */ + U64(0xAF8E5410, 0x288E1B6F), U64(0x07ECF0AE, 0x5EE44DD9), /* ~= 10^-303 */ + U64(0xDB71E914, 0x32B1A24A), U64(0xC9E82CD9, 0xF69D6150), /* ~= 10^-302 */ + U64(0x892731AC, 0x9FAF056E), U64(0xBE311C08, 0x3A225CD2), /* ~= 10^-301 */ + U64(0xAB70FE17, 0xC79AC6CA), U64(0x6DBD630A, 0x48AAF406), /* ~= 10^-300 */ + U64(0xD64D3D9D, 0xB981787D), U64(0x092CBBCC, 0xDAD5B108), /* ~= 10^-299 */ + U64(0x85F04682, 0x93F0EB4E), U64(0x25BBF560, 0x08C58EA5), /* ~= 10^-298 */ + U64(0xA76C5823, 0x38ED2621), U64(0xAF2AF2B8, 0x0AF6F24E), /* ~= 10^-297 */ + U64(0xD1476E2C, 0x07286FAA), U64(0x1AF5AF66, 0x0DB4AEE1), /* ~= 10^-296 */ + U64(0x82CCA4DB, 0x847945CA), U64(0x50D98D9F, 0xC890ED4D), /* ~= 10^-295 */ + U64(0xA37FCE12, 0x6597973C), U64(0xE50FF107, 0xBAB528A0), /* ~= 10^-294 */ + U64(0xCC5FC196, 0xFEFD7D0C), U64(0x1E53ED49, 0xA96272C8), /* ~= 10^-293 */ + U64(0xFF77B1FC, 0xBEBCDC4F), U64(0x25E8E89C, 0x13BB0F7A), /* ~= 10^-292 */ + U64(0x9FAACF3D, 0xF73609B1), U64(0x77B19161, 0x8C54E9AC), /* ~= 10^-291 */ + U64(0xC795830D, 0x75038C1D), U64(0xD59DF5B9, 0xEF6A2417), /* ~= 10^-290 */ + U64(0xF97AE3D0, 0xD2446F25), U64(0x4B057328, 0x6B44AD1D), /* ~= 10^-289 */ + U64(0x9BECCE62, 0x836AC577), U64(0x4EE367F9, 0x430AEC32), /* ~= 10^-288 */ + U64(0xC2E801FB, 0x244576D5), U64(0x229C41F7, 0x93CDA73F), /* ~= 10^-287 */ + U64(0xF3A20279, 0xED56D48A), U64(0x6B435275, 0x78C1110F), /* ~= 10^-286 */ + U64(0x9845418C, 0x345644D6), U64(0x830A1389, 0x6B78AAA9), /* ~= 10^-285 */ + U64(0xBE5691EF, 0x416BD60C), U64(0x23CC986B, 0xC656D553), /* ~= 10^-284 */ + U64(0xEDEC366B, 0x11C6CB8F), U64(0x2CBFBE86, 0xB7EC8AA8), /* ~= 10^-283 */ + U64(0x94B3A202, 0xEB1C3F39), U64(0x7BF7D714, 0x32F3D6A9), /* ~= 10^-282 */ + U64(0xB9E08A83, 0xA5E34F07), U64(0xDAF5CCD9, 0x3FB0CC53), /* ~= 10^-281 */ + U64(0xE858AD24, 0x8F5C22C9), U64(0xD1B3400F, 0x8F9CFF68), /* ~= 10^-280 */ + U64(0x91376C36, 0xD99995BE), U64(0x23100809, 0xB9C21FA1), /* ~= 10^-279 */ + U64(0xB5854744, 0x8FFFFB2D), U64(0xABD40A0C, 0x2832A78A), /* ~= 10^-278 */ + U64(0xE2E69915, 0xB3FFF9F9), U64(0x16C90C8F, 0x323F516C), /* ~= 10^-277 */ + U64(0x8DD01FAD, 0x907FFC3B), U64(0xAE3DA7D9, 0x7F6792E3), /* ~= 10^-276 */ + U64(0xB1442798, 0xF49FFB4A), U64(0x99CD11CF, 0xDF41779C), /* ~= 10^-275 */ + U64(0xDD95317F, 0x31C7FA1D), U64(0x40405643, 0xD711D583), /* ~= 10^-274 */ + U64(0x8A7D3EEF, 0x7F1CFC52), U64(0x482835EA, 0x666B2572), /* ~= 10^-273 */ + U64(0xAD1C8EAB, 0x5EE43B66), U64(0xDA324365, 0x0005EECF), /* ~= 10^-272 */ + U64(0xD863B256, 0x369D4A40), U64(0x90BED43E, 0x40076A82), /* ~= 10^-271 */ + U64(0x873E4F75, 0xE2224E68), U64(0x5A7744A6, 0xE804A291), /* ~= 10^-270 */ + U64(0xA90DE353, 0x5AAAE202), U64(0x711515D0, 0xA205CB36), /* ~= 10^-269 */ + U64(0xD3515C28, 0x31559A83), U64(0x0D5A5B44, 0xCA873E03), /* ~= 10^-268 */ + U64(0x8412D999, 0x1ED58091), U64(0xE858790A, 0xFE9486C2), /* ~= 10^-267 */ + U64(0xA5178FFF, 0x668AE0B6), U64(0x626E974D, 0xBE39A872), /* ~= 10^-266 */ + U64(0xCE5D73FF, 0x402D98E3), U64(0xFB0A3D21, 0x2DC8128F), /* ~= 10^-265 */ + U64(0x80FA687F, 0x881C7F8E), U64(0x7CE66634, 0xBC9D0B99), /* ~= 10^-264 */ + U64(0xA139029F, 0x6A239F72), U64(0x1C1FFFC1, 0xEBC44E80), /* ~= 10^-263 */ + U64(0xC9874347, 0x44AC874E), U64(0xA327FFB2, 0x66B56220), /* ~= 10^-262 */ + U64(0xFBE91419, 0x15D7A922), U64(0x4BF1FF9F, 0x0062BAA8), /* ~= 10^-261 */ + U64(0x9D71AC8F, 0xADA6C9B5), U64(0x6F773FC3, 0x603DB4A9), /* ~= 10^-260 */ + U64(0xC4CE17B3, 0x99107C22), U64(0xCB550FB4, 0x384D21D3), /* ~= 10^-259 */ + U64(0xF6019DA0, 0x7F549B2B), U64(0x7E2A53A1, 0x46606A48), /* ~= 10^-258 */ + U64(0x99C10284, 0x4F94E0FB), U64(0x2EDA7444, 0xCBFC426D), /* ~= 10^-257 */ + U64(0xC0314325, 0x637A1939), U64(0xFA911155, 0xFEFB5308), /* ~= 10^-256 */ + U64(0xF03D93EE, 0xBC589F88), U64(0x793555AB, 0x7EBA27CA), /* ~= 10^-255 */ + U64(0x96267C75, 0x35B763B5), U64(0x4BC1558B, 0x2F3458DE), /* ~= 10^-254 */ + U64(0xBBB01B92, 0x83253CA2), U64(0x9EB1AAED, 0xFB016F16), /* ~= 10^-253 */ + U64(0xEA9C2277, 0x23EE8BCB), U64(0x465E15A9, 0x79C1CADC), /* ~= 10^-252 */ + U64(0x92A1958A, 0x7675175F), U64(0x0BFACD89, 0xEC191EC9), /* ~= 10^-251 */ + U64(0xB749FAED, 0x14125D36), U64(0xCEF980EC, 0x671F667B), /* ~= 10^-250 */ + U64(0xE51C79A8, 0x5916F484), U64(0x82B7E127, 0x80E7401A), /* ~= 10^-249 */ + U64(0x8F31CC09, 0x37AE58D2), U64(0xD1B2ECB8, 0xB0908810), /* ~= 10^-248 */ + U64(0xB2FE3F0B, 0x8599EF07), U64(0x861FA7E6, 0xDCB4AA15), /* ~= 10^-247 */ + U64(0xDFBDCECE, 0x67006AC9), U64(0x67A791E0, 0x93E1D49A), /* ~= 10^-246 */ + U64(0x8BD6A141, 0x006042BD), U64(0xE0C8BB2C, 0x5C6D24E0), /* ~= 10^-245 */ + U64(0xAECC4991, 0x4078536D), U64(0x58FAE9F7, 0x73886E18), /* ~= 10^-244 */ + U64(0xDA7F5BF5, 0x90966848), U64(0xAF39A475, 0x506A899E), /* ~= 10^-243 */ + U64(0x888F9979, 0x7A5E012D), U64(0x6D8406C9, 0x52429603), /* ~= 10^-242 */ + U64(0xAAB37FD7, 0xD8F58178), U64(0xC8E5087B, 0xA6D33B83), /* ~= 10^-241 */ + U64(0xD5605FCD, 0xCF32E1D6), U64(0xFB1E4A9A, 0x90880A64), /* ~= 10^-240 */ + U64(0x855C3BE0, 0xA17FCD26), U64(0x5CF2EEA0, 0x9A55067F), /* ~= 10^-239 */ + U64(0xA6B34AD8, 0xC9DFC06F), U64(0xF42FAA48, 0xC0EA481E), /* ~= 10^-238 */ + U64(0xD0601D8E, 0xFC57B08B), U64(0xF13B94DA, 0xF124DA26), /* ~= 10^-237 */ + U64(0x823C1279, 0x5DB6CE57), U64(0x76C53D08, 0xD6B70858), /* ~= 10^-236 */ + U64(0xA2CB1717, 0xB52481ED), U64(0x54768C4B, 0x0C64CA6E), /* ~= 10^-235 */ + U64(0xCB7DDCDD, 0xA26DA268), U64(0xA9942F5D, 0xCF7DFD09), /* ~= 10^-234 */ + U64(0xFE5D5415, 0x0B090B02), U64(0xD3F93B35, 0x435D7C4C), /* ~= 10^-233 */ + U64(0x9EFA548D, 0x26E5A6E1), U64(0xC47BC501, 0x4A1A6DAF), /* ~= 10^-232 */ + U64(0xC6B8E9B0, 0x709F109A), U64(0x359AB641, 0x9CA1091B), /* ~= 10^-231 */ + U64(0xF867241C, 0x8CC6D4C0), U64(0xC30163D2, 0x03C94B62), /* ~= 10^-230 */ + U64(0x9B407691, 0xD7FC44F8), U64(0x79E0DE63, 0x425DCF1D), /* ~= 10^-229 */ + U64(0xC2109436, 0x4DFB5636), U64(0x985915FC, 0x12F542E4), /* ~= 10^-228 */ + U64(0xF294B943, 0xE17A2BC4), U64(0x3E6F5B7B, 0x17B2939D), /* ~= 10^-227 */ + U64(0x979CF3CA, 0x6CEC5B5A), U64(0xA705992C, 0xEECF9C42), /* ~= 10^-226 */ + U64(0xBD8430BD, 0x08277231), U64(0x50C6FF78, 0x2A838353), /* ~= 10^-225 */ + U64(0xECE53CEC, 0x4A314EBD), U64(0xA4F8BF56, 0x35246428), /* ~= 10^-224 */ + U64(0x940F4613, 0xAE5ED136), U64(0x871B7795, 0xE136BE99), /* ~= 10^-223 */ + U64(0xB9131798, 0x99F68584), U64(0x28E2557B, 0x59846E3F), /* ~= 10^-222 */ + U64(0xE757DD7E, 0xC07426E5), U64(0x331AEADA, 0x2FE589CF), /* ~= 10^-221 */ + U64(0x9096EA6F, 0x3848984F), U64(0x3FF0D2C8, 0x5DEF7621), /* ~= 10^-220 */ + U64(0xB4BCA50B, 0x065ABE63), U64(0x0FED077A, 0x756B53A9), /* ~= 10^-219 */ + U64(0xE1EBCE4D, 0xC7F16DFB), U64(0xD3E84959, 0x12C62894), /* ~= 10^-218 */ + U64(0x8D3360F0, 0x9CF6E4BD), U64(0x64712DD7, 0xABBBD95C), /* ~= 10^-217 */ + U64(0xB080392C, 0xC4349DEC), U64(0xBD8D794D, 0x96AACFB3), /* ~= 10^-216 */ + U64(0xDCA04777, 0xF541C567), U64(0xECF0D7A0, 0xFC5583A0), /* ~= 10^-215 */ + U64(0x89E42CAA, 0xF9491B60), U64(0xF41686C4, 0x9DB57244), /* ~= 10^-214 */ + U64(0xAC5D37D5, 0xB79B6239), U64(0x311C2875, 0xC522CED5), /* ~= 10^-213 */ + U64(0xD77485CB, 0x25823AC7), U64(0x7D633293, 0x366B828B), /* ~= 10^-212 */ + U64(0x86A8D39E, 0xF77164BC), U64(0xAE5DFF9C, 0x02033197), /* ~= 10^-211 */ + U64(0xA8530886, 0xB54DBDEB), U64(0xD9F57F83, 0x0283FDFC), /* ~= 10^-210 */ + U64(0xD267CAA8, 0x62A12D66), U64(0xD072DF63, 0xC324FD7B), /* ~= 10^-209 */ + U64(0x8380DEA9, 0x3DA4BC60), U64(0x4247CB9E, 0x59F71E6D), /* ~= 10^-208 */ + U64(0xA4611653, 0x8D0DEB78), U64(0x52D9BE85, 0xF074E608), /* ~= 10^-207 */ + U64(0xCD795BE8, 0x70516656), U64(0x67902E27, 0x6C921F8B), /* ~= 10^-206 */ + U64(0x806BD971, 0x4632DFF6), U64(0x00BA1CD8, 0xA3DB53B6), /* ~= 10^-205 */ + U64(0xA086CFCD, 0x97BF97F3), U64(0x80E8A40E, 0xCCD228A4), /* ~= 10^-204 */ + U64(0xC8A883C0, 0xFDAF7DF0), U64(0x6122CD12, 0x8006B2CD), /* ~= 10^-203 */ + U64(0xFAD2A4B1, 0x3D1B5D6C), U64(0x796B8057, 0x20085F81), /* ~= 10^-202 */ + U64(0x9CC3A6EE, 0xC6311A63), U64(0xCBE33036, 0x74053BB0), /* ~= 10^-201 */ + U64(0xC3F490AA, 0x77BD60FC), U64(0xBEDBFC44, 0x11068A9C), /* ~= 10^-200 */ + U64(0xF4F1B4D5, 0x15ACB93B), U64(0xEE92FB55, 0x15482D44), /* ~= 10^-199 */ + U64(0x99171105, 0x2D8BF3C5), U64(0x751BDD15, 0x2D4D1C4A), /* ~= 10^-198 */ + U64(0xBF5CD546, 0x78EEF0B6), U64(0xD262D45A, 0x78A0635D), /* ~= 10^-197 */ + U64(0xEF340A98, 0x172AACE4), U64(0x86FB8971, 0x16C87C34), /* ~= 10^-196 */ + U64(0x9580869F, 0x0E7AAC0E), U64(0xD45D35E6, 0xAE3D4DA0), /* ~= 10^-195 */ + U64(0xBAE0A846, 0xD2195712), U64(0x89748360, 0x59CCA109), /* ~= 10^-194 */ + U64(0xE998D258, 0x869FACD7), U64(0x2BD1A438, 0x703FC94B), /* ~= 10^-193 */ + U64(0x91FF8377, 0x5423CC06), U64(0x7B6306A3, 0x4627DDCF), /* ~= 10^-192 */ + U64(0xB67F6455, 0x292CBF08), U64(0x1A3BC84C, 0x17B1D542), /* ~= 10^-191 */ + U64(0xE41F3D6A, 0x7377EECA), U64(0x20CABA5F, 0x1D9E4A93), /* ~= 10^-190 */ + U64(0x8E938662, 0x882AF53E), U64(0x547EB47B, 0x7282EE9C), /* ~= 10^-189 */ + U64(0xB23867FB, 0x2A35B28D), U64(0xE99E619A, 0x4F23AA43), /* ~= 10^-188 */ + U64(0xDEC681F9, 0xF4C31F31), U64(0x6405FA00, 0xE2EC94D4), /* ~= 10^-187 */ + U64(0x8B3C113C, 0x38F9F37E), U64(0xDE83BC40, 0x8DD3DD04), /* ~= 10^-186 */ + U64(0xAE0B158B, 0x4738705E), U64(0x9624AB50, 0xB148D445), /* ~= 10^-185 */ + U64(0xD98DDAEE, 0x19068C76), U64(0x3BADD624, 0xDD9B0957), /* ~= 10^-184 */ + U64(0x87F8A8D4, 0xCFA417C9), U64(0xE54CA5D7, 0x0A80E5D6), /* ~= 10^-183 */ + U64(0xA9F6D30A, 0x038D1DBC), U64(0x5E9FCF4C, 0xCD211F4C), /* ~= 10^-182 */ + U64(0xD47487CC, 0x8470652B), U64(0x7647C320, 0x0069671F), /* ~= 10^-181 */ + U64(0x84C8D4DF, 0xD2C63F3B), U64(0x29ECD9F4, 0x0041E073), /* ~= 10^-180 */ + U64(0xA5FB0A17, 0xC777CF09), U64(0xF4681071, 0x00525890), /* ~= 10^-179 */ + U64(0xCF79CC9D, 0xB955C2CC), U64(0x7182148D, 0x4066EEB4), /* ~= 10^-178 */ + U64(0x81AC1FE2, 0x93D599BF), U64(0xC6F14CD8, 0x48405530), /* ~= 10^-177 */ + U64(0xA21727DB, 0x38CB002F), U64(0xB8ADA00E, 0x5A506A7C), /* ~= 10^-176 */ + U64(0xCA9CF1D2, 0x06FDC03B), U64(0xA6D90811, 0xF0E4851C), /* ~= 10^-175 */ + U64(0xFD442E46, 0x88BD304A), U64(0x908F4A16, 0x6D1DA663), /* ~= 10^-174 */ + U64(0x9E4A9CEC, 0x15763E2E), U64(0x9A598E4E, 0x043287FE), /* ~= 10^-173 */ + U64(0xC5DD4427, 0x1AD3CDBA), U64(0x40EFF1E1, 0x853F29FD), /* ~= 10^-172 */ + U64(0xF7549530, 0xE188C128), U64(0xD12BEE59, 0xE68EF47C), /* ~= 10^-171 */ + U64(0x9A94DD3E, 0x8CF578B9), U64(0x82BB74F8, 0x301958CE), /* ~= 10^-170 */ + U64(0xC13A148E, 0x3032D6E7), U64(0xE36A5236, 0x3C1FAF01), /* ~= 10^-169 */ + U64(0xF18899B1, 0xBC3F8CA1), U64(0xDC44E6C3, 0xCB279AC1), /* ~= 10^-168 */ + U64(0x96F5600F, 0x15A7B7E5), U64(0x29AB103A, 0x5EF8C0B9), /* ~= 10^-167 */ + U64(0xBCB2B812, 0xDB11A5DE), U64(0x7415D448, 0xF6B6F0E7), /* ~= 10^-166 */ + U64(0xEBDF6617, 0x91D60F56), U64(0x111B495B, 0x3464AD21), /* ~= 10^-165 */ + U64(0x936B9FCE, 0xBB25C995), U64(0xCAB10DD9, 0x00BEEC34), /* ~= 10^-164 */ + U64(0xB84687C2, 0x69EF3BFB), U64(0x3D5D514F, 0x40EEA742), /* ~= 10^-163 */ + U64(0xE65829B3, 0x046B0AFA), U64(0x0CB4A5A3, 0x112A5112), /* ~= 10^-162 */ + U64(0x8FF71A0F, 0xE2C2E6DC), U64(0x47F0E785, 0xEABA72AB), /* ~= 10^-161 */ + U64(0xB3F4E093, 0xDB73A093), U64(0x59ED2167, 0x65690F56), /* ~= 10^-160 */ + U64(0xE0F218B8, 0xD25088B8), U64(0x306869C1, 0x3EC3532C), /* ~= 10^-159 */ + U64(0x8C974F73, 0x83725573), U64(0x1E414218, 0xC73A13FB), /* ~= 10^-158 */ + U64(0xAFBD2350, 0x644EEACF), U64(0xE5D1929E, 0xF90898FA), /* ~= 10^-157 */ + U64(0xDBAC6C24, 0x7D62A583), U64(0xDF45F746, 0xB74ABF39), /* ~= 10^-156 */ + U64(0x894BC396, 0xCE5DA772), U64(0x6B8BBA8C, 0x328EB783), /* ~= 10^-155 */ + U64(0xAB9EB47C, 0x81F5114F), U64(0x066EA92F, 0x3F326564), /* ~= 10^-154 */ + U64(0xD686619B, 0xA27255A2), U64(0xC80A537B, 0x0EFEFEBD), /* ~= 10^-153 */ + U64(0x8613FD01, 0x45877585), U64(0xBD06742C, 0xE95F5F36), /* ~= 10^-152 */ + U64(0xA798FC41, 0x96E952E7), U64(0x2C481138, 0x23B73704), /* ~= 10^-151 */ + U64(0xD17F3B51, 0xFCA3A7A0), U64(0xF75A1586, 0x2CA504C5), /* ~= 10^-150 */ + U64(0x82EF8513, 0x3DE648C4), U64(0x9A984D73, 0xDBE722FB), /* ~= 10^-149 */ + U64(0xA3AB6658, 0x0D5FDAF5), U64(0xC13E60D0, 0xD2E0EBBA), /* ~= 10^-148 */ + U64(0xCC963FEE, 0x10B7D1B3), U64(0x318DF905, 0x079926A8), /* ~= 10^-147 */ + U64(0xFFBBCFE9, 0x94E5C61F), U64(0xFDF17746, 0x497F7052), /* ~= 10^-146 */ + U64(0x9FD561F1, 0xFD0F9BD3), U64(0xFEB6EA8B, 0xEDEFA633), /* ~= 10^-145 */ + U64(0xC7CABA6E, 0x7C5382C8), U64(0xFE64A52E, 0xE96B8FC0), /* ~= 10^-144 */ + U64(0xF9BD690A, 0x1B68637B), U64(0x3DFDCE7A, 0xA3C673B0), /* ~= 10^-143 */ + U64(0x9C1661A6, 0x51213E2D), U64(0x06BEA10C, 0xA65C084E), /* ~= 10^-142 */ + U64(0xC31BFA0F, 0xE5698DB8), U64(0x486E494F, 0xCFF30A62), /* ~= 10^-141 */ + U64(0xF3E2F893, 0xDEC3F126), U64(0x5A89DBA3, 0xC3EFCCFA), /* ~= 10^-140 */ + U64(0x986DDB5C, 0x6B3A76B7), U64(0xF8962946, 0x5A75E01C), /* ~= 10^-139 */ + U64(0xBE895233, 0x86091465), U64(0xF6BBB397, 0xF1135823), /* ~= 10^-138 */ + U64(0xEE2BA6C0, 0x678B597F), U64(0x746AA07D, 0xED582E2C), /* ~= 10^-137 */ + U64(0x94DB4838, 0x40B717EF), U64(0xA8C2A44E, 0xB4571CDC), /* ~= 10^-136 */ + U64(0xBA121A46, 0x50E4DDEB), U64(0x92F34D62, 0x616CE413), /* ~= 10^-135 */ + U64(0xE896A0D7, 0xE51E1566), U64(0x77B020BA, 0xF9C81D17), /* ~= 10^-134 */ + U64(0x915E2486, 0xEF32CD60), U64(0x0ACE1474, 0xDC1D122E), /* ~= 10^-133 */ + U64(0xB5B5ADA8, 0xAAFF80B8), U64(0x0D819992, 0x132456BA), /* ~= 10^-132 */ + U64(0xE3231912, 0xD5BF60E6), U64(0x10E1FFF6, 0x97ED6C69), /* ~= 10^-131 */ + U64(0x8DF5EFAB, 0xC5979C8F), U64(0xCA8D3FFA, 0x1EF463C1), /* ~= 10^-130 */ + U64(0xB1736B96, 0xB6FD83B3), U64(0xBD308FF8, 0xA6B17CB2), /* ~= 10^-129 */ + U64(0xDDD0467C, 0x64BCE4A0), U64(0xAC7CB3F6, 0xD05DDBDE), /* ~= 10^-128 */ + U64(0x8AA22C0D, 0xBEF60EE4), U64(0x6BCDF07A, 0x423AA96B), /* ~= 10^-127 */ + U64(0xAD4AB711, 0x2EB3929D), U64(0x86C16C98, 0xD2C953C6), /* ~= 10^-126 */ + U64(0xD89D64D5, 0x7A607744), U64(0xE871C7BF, 0x077BA8B7), /* ~= 10^-125 */ + U64(0x87625F05, 0x6C7C4A8B), U64(0x11471CD7, 0x64AD4972), /* ~= 10^-124 */ + U64(0xA93AF6C6, 0xC79B5D2D), U64(0xD598E40D, 0x3DD89BCF), /* ~= 10^-123 */ + U64(0xD389B478, 0x79823479), U64(0x4AFF1D10, 0x8D4EC2C3), /* ~= 10^-122 */ + U64(0x843610CB, 0x4BF160CB), U64(0xCEDF722A, 0x585139BA), /* ~= 10^-121 */ + U64(0xA54394FE, 0x1EEDB8FE), U64(0xC2974EB4, 0xEE658828), /* ~= 10^-120 */ + U64(0xCE947A3D, 0xA6A9273E), U64(0x733D2262, 0x29FEEA32), /* ~= 10^-119 */ + U64(0x811CCC66, 0x8829B887), U64(0x0806357D, 0x5A3F525F), /* ~= 10^-118 */ + U64(0xA163FF80, 0x2A3426A8), U64(0xCA07C2DC, 0xB0CF26F7), /* ~= 10^-117 */ + U64(0xC9BCFF60, 0x34C13052), U64(0xFC89B393, 0xDD02F0B5), /* ~= 10^-116 */ + U64(0xFC2C3F38, 0x41F17C67), U64(0xBBAC2078, 0xD443ACE2), /* ~= 10^-115 */ + U64(0x9D9BA783, 0x2936EDC0), U64(0xD54B944B, 0x84AA4C0D), /* ~= 10^-114 */ + U64(0xC5029163, 0xF384A931), U64(0x0A9E795E, 0x65D4DF11), /* ~= 10^-113 */ + U64(0xF64335BC, 0xF065D37D), U64(0x4D4617B5, 0xFF4A16D5), /* ~= 10^-112 */ + U64(0x99EA0196, 0x163FA42E), U64(0x504BCED1, 0xBF8E4E45), /* ~= 10^-111 */ + U64(0xC06481FB, 0x9BCF8D39), U64(0xE45EC286, 0x2F71E1D6), /* ~= 10^-110 */ + U64(0xF07DA27A, 0x82C37088), U64(0x5D767327, 0xBB4E5A4C), /* ~= 10^-109 */ + U64(0x964E858C, 0x91BA2655), U64(0x3A6A07F8, 0xD510F86F), /* ~= 10^-108 */ + U64(0xBBE226EF, 0xB628AFEA), U64(0x890489F7, 0x0A55368B), /* ~= 10^-107 */ + U64(0xEADAB0AB, 0xA3B2DBE5), U64(0x2B45AC74, 0xCCEA842E), /* ~= 10^-106 */ + U64(0x92C8AE6B, 0x464FC96F), U64(0x3B0B8BC9, 0x0012929D), /* ~= 10^-105 */ + U64(0xB77ADA06, 0x17E3BBCB), U64(0x09CE6EBB, 0x40173744), /* ~= 10^-104 */ + U64(0xE5599087, 0x9DDCAABD), U64(0xCC420A6A, 0x101D0515), /* ~= 10^-103 */ + U64(0x8F57FA54, 0xC2A9EAB6), U64(0x9FA94682, 0x4A12232D), /* ~= 10^-102 */ + U64(0xB32DF8E9, 0xF3546564), U64(0x47939822, 0xDC96ABF9), /* ~= 10^-101 */ + U64(0xDFF97724, 0x70297EBD), U64(0x59787E2B, 0x93BC56F7), /* ~= 10^-100 */ + U64(0x8BFBEA76, 0xC619EF36), U64(0x57EB4EDB, 0x3C55B65A), /* ~= 10^-99 */ + U64(0xAEFAE514, 0x77A06B03), U64(0xEDE62292, 0x0B6B23F1), /* ~= 10^-98 */ + U64(0xDAB99E59, 0x958885C4), U64(0xE95FAB36, 0x8E45ECED), /* ~= 10^-97 */ + U64(0x88B402F7, 0xFD75539B), U64(0x11DBCB02, 0x18EBB414), /* ~= 10^-96 */ + U64(0xAAE103B5, 0xFCD2A881), U64(0xD652BDC2, 0x9F26A119), /* ~= 10^-95 */ + U64(0xD59944A3, 0x7C0752A2), U64(0x4BE76D33, 0x46F0495F), /* ~= 10^-94 */ + U64(0x857FCAE6, 0x2D8493A5), U64(0x6F70A440, 0x0C562DDB), /* ~= 10^-93 */ + U64(0xA6DFBD9F, 0xB8E5B88E), U64(0xCB4CCD50, 0x0F6BB952), /* ~= 10^-92 */ + U64(0xD097AD07, 0xA71F26B2), U64(0x7E2000A4, 0x1346A7A7), /* ~= 10^-91 */ + U64(0x825ECC24, 0xC873782F), U64(0x8ED40066, 0x8C0C28C8), /* ~= 10^-90 */ + U64(0xA2F67F2D, 0xFA90563B), U64(0x72890080, 0x2F0F32FA), /* ~= 10^-89 */ + U64(0xCBB41EF9, 0x79346BCA), U64(0x4F2B40A0, 0x3AD2FFB9), /* ~= 10^-88 */ + U64(0xFEA126B7, 0xD78186BC), U64(0xE2F610C8, 0x4987BFA8), /* ~= 10^-87 */ + U64(0x9F24B832, 0xE6B0F436), U64(0x0DD9CA7D, 0x2DF4D7C9), /* ~= 10^-86 */ + U64(0xC6EDE63F, 0xA05D3143), U64(0x91503D1C, 0x79720DBB), /* ~= 10^-85 */ + U64(0xF8A95FCF, 0x88747D94), U64(0x75A44C63, 0x97CE912A), /* ~= 10^-84 */ + U64(0x9B69DBE1, 0xB548CE7C), U64(0xC986AFBE, 0x3EE11ABA), /* ~= 10^-83 */ + U64(0xC24452DA, 0x229B021B), U64(0xFBE85BAD, 0xCE996168), /* ~= 10^-82 */ + U64(0xF2D56790, 0xAB41C2A2), U64(0xFAE27299, 0x423FB9C3), /* ~= 10^-81 */ + U64(0x97C560BA, 0x6B0919A5), U64(0xDCCD879F, 0xC967D41A), /* ~= 10^-80 */ + U64(0xBDB6B8E9, 0x05CB600F), U64(0x5400E987, 0xBBC1C920), /* ~= 10^-79 */ + U64(0xED246723, 0x473E3813), U64(0x290123E9, 0xAAB23B68), /* ~= 10^-78 */ + U64(0x9436C076, 0x0C86E30B), U64(0xF9A0B672, 0x0AAF6521), /* ~= 10^-77 */ + U64(0xB9447093, 0x8FA89BCE), U64(0xF808E40E, 0x8D5B3E69), /* ~= 10^-76 */ + U64(0xE7958CB8, 0x7392C2C2), U64(0xB60B1D12, 0x30B20E04), /* ~= 10^-75 */ + U64(0x90BD77F3, 0x483BB9B9), U64(0xB1C6F22B, 0x5E6F48C2), /* ~= 10^-74 */ + U64(0xB4ECD5F0, 0x1A4AA828), U64(0x1E38AEB6, 0x360B1AF3), /* ~= 10^-73 */ + U64(0xE2280B6C, 0x20DD5232), U64(0x25C6DA63, 0xC38DE1B0), /* ~= 10^-72 */ + U64(0x8D590723, 0x948A535F), U64(0x579C487E, 0x5A38AD0E), /* ~= 10^-71 */ + U64(0xB0AF48EC, 0x79ACE837), U64(0x2D835A9D, 0xF0C6D851), /* ~= 10^-70 */ + U64(0xDCDB1B27, 0x98182244), U64(0xF8E43145, 0x6CF88E65), /* ~= 10^-69 */ + U64(0x8A08F0F8, 0xBF0F156B), U64(0x1B8E9ECB, 0x641B58FF), /* ~= 10^-68 */ + U64(0xAC8B2D36, 0xEED2DAC5), U64(0xE272467E, 0x3D222F3F), /* ~= 10^-67 */ + U64(0xD7ADF884, 0xAA879177), U64(0x5B0ED81D, 0xCC6ABB0F), /* ~= 10^-66 */ + U64(0x86CCBB52, 0xEA94BAEA), U64(0x98E94712, 0x9FC2B4E9), /* ~= 10^-65 */ + U64(0xA87FEA27, 0xA539E9A5), U64(0x3F2398D7, 0x47B36224), /* ~= 10^-64 */ + U64(0xD29FE4B1, 0x8E88640E), U64(0x8EEC7F0D, 0x19A03AAD), /* ~= 10^-63 */ + U64(0x83A3EEEE, 0xF9153E89), U64(0x1953CF68, 0x300424AC), /* ~= 10^-62 */ + U64(0xA48CEAAA, 0xB75A8E2B), U64(0x5FA8C342, 0x3C052DD7), /* ~= 10^-61 */ + U64(0xCDB02555, 0x653131B6), U64(0x3792F412, 0xCB06794D), /* ~= 10^-60 */ + U64(0x808E1755, 0x5F3EBF11), U64(0xE2BBD88B, 0xBEE40BD0), /* ~= 10^-59 */ + U64(0xA0B19D2A, 0xB70E6ED6), U64(0x5B6ACEAE, 0xAE9D0EC4), /* ~= 10^-58 */ + U64(0xC8DE0475, 0x64D20A8B), U64(0xF245825A, 0x5A445275), /* ~= 10^-57 */ + U64(0xFB158592, 0xBE068D2E), U64(0xEED6E2F0, 0xF0D56712), /* ~= 10^-56 */ + U64(0x9CED737B, 0xB6C4183D), U64(0x55464DD6, 0x9685606B), /* ~= 10^-55 */ + U64(0xC428D05A, 0xA4751E4C), U64(0xAA97E14C, 0x3C26B886), /* ~= 10^-54 */ + U64(0xF5330471, 0x4D9265DF), U64(0xD53DD99F, 0x4B3066A8), /* ~= 10^-53 */ + U64(0x993FE2C6, 0xD07B7FAB), U64(0xE546A803, 0x8EFE4029), /* ~= 10^-52 */ + U64(0xBF8FDB78, 0x849A5F96), U64(0xDE985204, 0x72BDD033), /* ~= 10^-51 */ + U64(0xEF73D256, 0xA5C0F77C), U64(0x963E6685, 0x8F6D4440), /* ~= 10^-50 */ + U64(0x95A86376, 0x27989AAD), U64(0xDDE70013, 0x79A44AA8), /* ~= 10^-49 */ + U64(0xBB127C53, 0xB17EC159), U64(0x5560C018, 0x580D5D52), /* ~= 10^-48 */ + U64(0xE9D71B68, 0x9DDE71AF), U64(0xAAB8F01E, 0x6E10B4A6), /* ~= 10^-47 */ + U64(0x92267121, 0x62AB070D), U64(0xCAB39613, 0x04CA70E8), /* ~= 10^-46 */ + U64(0xB6B00D69, 0xBB55C8D1), U64(0x3D607B97, 0xC5FD0D22), /* ~= 10^-45 */ + U64(0xE45C10C4, 0x2A2B3B05), U64(0x8CB89A7D, 0xB77C506A), /* ~= 10^-44 */ + U64(0x8EB98A7A, 0x9A5B04E3), U64(0x77F3608E, 0x92ADB242), /* ~= 10^-43 */ + U64(0xB267ED19, 0x40F1C61C), U64(0x55F038B2, 0x37591ED3), /* ~= 10^-42 */ + U64(0xDF01E85F, 0x912E37A3), U64(0x6B6C46DE, 0xC52F6688), /* ~= 10^-41 */ + U64(0x8B61313B, 0xBABCE2C6), U64(0x2323AC4B, 0x3B3DA015), /* ~= 10^-40 */ + U64(0xAE397D8A, 0xA96C1B77), U64(0xABEC975E, 0x0A0D081A), /* ~= 10^-39 */ + U64(0xD9C7DCED, 0x53C72255), U64(0x96E7BD35, 0x8C904A21), /* ~= 10^-38 */ + U64(0x881CEA14, 0x545C7575), U64(0x7E50D641, 0x77DA2E54), /* ~= 10^-37 */ + U64(0xAA242499, 0x697392D2), U64(0xDDE50BD1, 0xD5D0B9E9), /* ~= 10^-36 */ + U64(0xD4AD2DBF, 0xC3D07787), U64(0x955E4EC6, 0x4B44E864), /* ~= 10^-35 */ + U64(0x84EC3C97, 0xDA624AB4), U64(0xBD5AF13B, 0xEF0B113E), /* ~= 10^-34 */ + U64(0xA6274BBD, 0xD0FADD61), U64(0xECB1AD8A, 0xEACDD58E), /* ~= 10^-33 */ + U64(0xCFB11EAD, 0x453994BA), U64(0x67DE18ED, 0xA5814AF2), /* ~= 10^-32 */ + U64(0x81CEB32C, 0x4B43FCF4), U64(0x80EACF94, 0x8770CED7), /* ~= 10^-31 */ + U64(0xA2425FF7, 0x5E14FC31), U64(0xA1258379, 0xA94D028D), /* ~= 10^-30 */ + U64(0xCAD2F7F5, 0x359A3B3E), U64(0x096EE458, 0x13A04330), /* ~= 10^-29 */ + U64(0xFD87B5F2, 0x8300CA0D), U64(0x8BCA9D6E, 0x188853FC), /* ~= 10^-28 */ + U64(0x9E74D1B7, 0x91E07E48), U64(0x775EA264, 0xCF55347D), /* ~= 10^-27 */ + U64(0xC6120625, 0x76589DDA), U64(0x95364AFE, 0x032A819D), /* ~= 10^-26 */ + U64(0xF79687AE, 0xD3EEC551), U64(0x3A83DDBD, 0x83F52204), /* ~= 10^-25 */ + U64(0x9ABE14CD, 0x44753B52), U64(0xC4926A96, 0x72793542), /* ~= 10^-24 */ + U64(0xC16D9A00, 0x95928A27), U64(0x75B7053C, 0x0F178293), /* ~= 10^-23 */ + U64(0xF1C90080, 0xBAF72CB1), U64(0x5324C68B, 0x12DD6338), /* ~= 10^-22 */ + U64(0x971DA050, 0x74DA7BEE), U64(0xD3F6FC16, 0xEBCA5E03), /* ~= 10^-21 */ + U64(0xBCE50864, 0x92111AEA), U64(0x88F4BB1C, 0xA6BCF584), /* ~= 10^-20 */ + U64(0xEC1E4A7D, 0xB69561A5), U64(0x2B31E9E3, 0xD06C32E5), /* ~= 10^-19 */ + U64(0x9392EE8E, 0x921D5D07), U64(0x3AFF322E, 0x62439FCF), /* ~= 10^-18 */ + U64(0xB877AA32, 0x36A4B449), U64(0x09BEFEB9, 0xFAD487C2), /* ~= 10^-17 */ + U64(0xE69594BE, 0xC44DE15B), U64(0x4C2EBE68, 0x7989A9B3), /* ~= 10^-16 */ + U64(0x901D7CF7, 0x3AB0ACD9), U64(0x0F9D3701, 0x4BF60A10), /* ~= 10^-15 */ + U64(0xB424DC35, 0x095CD80F), U64(0x538484C1, 0x9EF38C94), /* ~= 10^-14 */ + U64(0xE12E1342, 0x4BB40E13), U64(0x2865A5F2, 0x06B06FB9), /* ~= 10^-13 */ + U64(0x8CBCCC09, 0x6F5088CB), U64(0xF93F87B7, 0x442E45D3), /* ~= 10^-12 */ + U64(0xAFEBFF0B, 0xCB24AAFE), U64(0xF78F69A5, 0x1539D748), /* ~= 10^-11 */ + U64(0xDBE6FECE, 0xBDEDD5BE), U64(0xB573440E, 0x5A884D1B), /* ~= 10^-10 */ + U64(0x89705F41, 0x36B4A597), U64(0x31680A88, 0xF8953030), /* ~= 10^-9 */ + U64(0xABCC7711, 0x8461CEFC), U64(0xFDC20D2B, 0x36BA7C3D), /* ~= 10^-8 */ + U64(0xD6BF94D5, 0xE57A42BC), U64(0x3D329076, 0x04691B4C), /* ~= 10^-7 */ + U64(0x8637BD05, 0xAF6C69B5), U64(0xA63F9A49, 0xC2C1B10F), /* ~= 10^-6 */ + U64(0xA7C5AC47, 0x1B478423), U64(0x0FCF80DC, 0x33721D53), /* ~= 10^-5 */ + U64(0xD1B71758, 0xE219652B), U64(0xD3C36113, 0x404EA4A8), /* ~= 10^-4 */ + U64(0x83126E97, 0x8D4FDF3B), U64(0x645A1CAC, 0x083126E9), /* ~= 10^-3 */ + U64(0xA3D70A3D, 0x70A3D70A), U64(0x3D70A3D7, 0x0A3D70A3), /* ~= 10^-2 */ + U64(0xCCCCCCCC, 0xCCCCCCCC), U64(0xCCCCCCCC, 0xCCCCCCCC), /* ~= 10^-1 */ + U64(0x80000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^0 */ + U64(0xA0000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^1 */ + U64(0xC8000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^2 */ + U64(0xFA000000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^3 */ + U64(0x9C400000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^4 */ + U64(0xC3500000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^5 */ + U64(0xF4240000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^6 */ + U64(0x98968000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^7 */ + U64(0xBEBC2000, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^8 */ + U64(0xEE6B2800, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^9 */ + U64(0x9502F900, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^10 */ + U64(0xBA43B740, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^11 */ + U64(0xE8D4A510, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^12 */ + U64(0x9184E72A, 0x00000000), U64(0x00000000, 0x00000000), /* == 10^13 */ + U64(0xB5E620F4, 0x80000000), U64(0x00000000, 0x00000000), /* == 10^14 */ + U64(0xE35FA931, 0xA0000000), U64(0x00000000, 0x00000000), /* == 10^15 */ + U64(0x8E1BC9BF, 0x04000000), U64(0x00000000, 0x00000000), /* == 10^16 */ + U64(0xB1A2BC2E, 0xC5000000), U64(0x00000000, 0x00000000), /* == 10^17 */ + U64(0xDE0B6B3A, 0x76400000), U64(0x00000000, 0x00000000), /* == 10^18 */ + U64(0x8AC72304, 0x89E80000), U64(0x00000000, 0x00000000), /* == 10^19 */ + U64(0xAD78EBC5, 0xAC620000), U64(0x00000000, 0x00000000), /* == 10^20 */ + U64(0xD8D726B7, 0x177A8000), U64(0x00000000, 0x00000000), /* == 10^21 */ + U64(0x87867832, 0x6EAC9000), U64(0x00000000, 0x00000000), /* == 10^22 */ + U64(0xA968163F, 0x0A57B400), U64(0x00000000, 0x00000000), /* == 10^23 */ + U64(0xD3C21BCE, 0xCCEDA100), U64(0x00000000, 0x00000000), /* == 10^24 */ + U64(0x84595161, 0x401484A0), U64(0x00000000, 0x00000000), /* == 10^25 */ + U64(0xA56FA5B9, 0x9019A5C8), U64(0x00000000, 0x00000000), /* == 10^26 */ + U64(0xCECB8F27, 0xF4200F3A), U64(0x00000000, 0x00000000), /* == 10^27 */ + U64(0x813F3978, 0xF8940984), U64(0x40000000, 0x00000000), /* == 10^28 */ + U64(0xA18F07D7, 0x36B90BE5), U64(0x50000000, 0x00000000), /* == 10^29 */ + U64(0xC9F2C9CD, 0x04674EDE), U64(0xA4000000, 0x00000000), /* == 10^30 */ + U64(0xFC6F7C40, 0x45812296), U64(0x4D000000, 0x00000000), /* == 10^31 */ + U64(0x9DC5ADA8, 0x2B70B59D), U64(0xF0200000, 0x00000000), /* == 10^32 */ + U64(0xC5371912, 0x364CE305), U64(0x6C280000, 0x00000000), /* == 10^33 */ + U64(0xF684DF56, 0xC3E01BC6), U64(0xC7320000, 0x00000000), /* == 10^34 */ + U64(0x9A130B96, 0x3A6C115C), U64(0x3C7F4000, 0x00000000), /* == 10^35 */ + U64(0xC097CE7B, 0xC90715B3), U64(0x4B9F1000, 0x00000000), /* == 10^36 */ + U64(0xF0BDC21A, 0xBB48DB20), U64(0x1E86D400, 0x00000000), /* == 10^37 */ + U64(0x96769950, 0xB50D88F4), U64(0x13144480, 0x00000000), /* == 10^38 */ + U64(0xBC143FA4, 0xE250EB31), U64(0x17D955A0, 0x00000000), /* == 10^39 */ + U64(0xEB194F8E, 0x1AE525FD), U64(0x5DCFAB08, 0x00000000), /* == 10^40 */ + U64(0x92EFD1B8, 0xD0CF37BE), U64(0x5AA1CAE5, 0x00000000), /* == 10^41 */ + U64(0xB7ABC627, 0x050305AD), U64(0xF14A3D9E, 0x40000000), /* == 10^42 */ + U64(0xE596B7B0, 0xC643C719), U64(0x6D9CCD05, 0xD0000000), /* == 10^43 */ + U64(0x8F7E32CE, 0x7BEA5C6F), U64(0xE4820023, 0xA2000000), /* == 10^44 */ + U64(0xB35DBF82, 0x1AE4F38B), U64(0xDDA2802C, 0x8A800000), /* == 10^45 */ + U64(0xE0352F62, 0xA19E306E), U64(0xD50B2037, 0xAD200000), /* == 10^46 */ + U64(0x8C213D9D, 0xA502DE45), U64(0x4526F422, 0xCC340000), /* == 10^47 */ + U64(0xAF298D05, 0x0E4395D6), U64(0x9670B12B, 0x7F410000), /* == 10^48 */ + U64(0xDAF3F046, 0x51D47B4C), U64(0x3C0CDD76, 0x5F114000), /* == 10^49 */ + U64(0x88D8762B, 0xF324CD0F), U64(0xA5880A69, 0xFB6AC800), /* == 10^50 */ + U64(0xAB0E93B6, 0xEFEE0053), U64(0x8EEA0D04, 0x7A457A00), /* == 10^51 */ + U64(0xD5D238A4, 0xABE98068), U64(0x72A49045, 0x98D6D880), /* == 10^52 */ + U64(0x85A36366, 0xEB71F041), U64(0x47A6DA2B, 0x7F864750), /* == 10^53 */ + U64(0xA70C3C40, 0xA64E6C51), U64(0x999090B6, 0x5F67D924), /* == 10^54 */ + U64(0xD0CF4B50, 0xCFE20765), U64(0xFFF4B4E3, 0xF741CF6D), /* == 10^55 */ + U64(0x82818F12, 0x81ED449F), U64(0xBFF8F10E, 0x7A8921A4), /* ~= 10^56 */ + U64(0xA321F2D7, 0x226895C7), U64(0xAFF72D52, 0x192B6A0D), /* ~= 10^57 */ + U64(0xCBEA6F8C, 0xEB02BB39), U64(0x9BF4F8A6, 0x9F764490), /* ~= 10^58 */ + U64(0xFEE50B70, 0x25C36A08), U64(0x02F236D0, 0x4753D5B4), /* ~= 10^59 */ + U64(0x9F4F2726, 0x179A2245), U64(0x01D76242, 0x2C946590), /* ~= 10^60 */ + U64(0xC722F0EF, 0x9D80AAD6), U64(0x424D3AD2, 0xB7B97EF5), /* ~= 10^61 */ + U64(0xF8EBAD2B, 0x84E0D58B), U64(0xD2E08987, 0x65A7DEB2), /* ~= 10^62 */ + U64(0x9B934C3B, 0x330C8577), U64(0x63CC55F4, 0x9F88EB2F), /* ~= 10^63 */ + U64(0xC2781F49, 0xFFCFA6D5), U64(0x3CBF6B71, 0xC76B25FB), /* ~= 10^64 */ + U64(0xF316271C, 0x7FC3908A), U64(0x8BEF464E, 0x3945EF7A), /* ~= 10^65 */ + U64(0x97EDD871, 0xCFDA3A56), U64(0x97758BF0, 0xE3CBB5AC), /* ~= 10^66 */ + U64(0xBDE94E8E, 0x43D0C8EC), U64(0x3D52EEED, 0x1CBEA317), /* ~= 10^67 */ + U64(0xED63A231, 0xD4C4FB27), U64(0x4CA7AAA8, 0x63EE4BDD), /* ~= 10^68 */ + U64(0x945E455F, 0x24FB1CF8), U64(0x8FE8CAA9, 0x3E74EF6A), /* ~= 10^69 */ + U64(0xB975D6B6, 0xEE39E436), U64(0xB3E2FD53, 0x8E122B44), /* ~= 10^70 */ + U64(0xE7D34C64, 0xA9C85D44), U64(0x60DBBCA8, 0x7196B616), /* ~= 10^71 */ + U64(0x90E40FBE, 0xEA1D3A4A), U64(0xBC8955E9, 0x46FE31CD), /* ~= 10^72 */ + U64(0xB51D13AE, 0xA4A488DD), U64(0x6BABAB63, 0x98BDBE41), /* ~= 10^73 */ + U64(0xE264589A, 0x4DCDAB14), U64(0xC696963C, 0x7EED2DD1), /* ~= 10^74 */ + U64(0x8D7EB760, 0x70A08AEC), U64(0xFC1E1DE5, 0xCF543CA2), /* ~= 10^75 */ + U64(0xB0DE6538, 0x8CC8ADA8), U64(0x3B25A55F, 0x43294BCB), /* ~= 10^76 */ + U64(0xDD15FE86, 0xAFFAD912), U64(0x49EF0EB7, 0x13F39EBE), /* ~= 10^77 */ + U64(0x8A2DBF14, 0x2DFCC7AB), U64(0x6E356932, 0x6C784337), /* ~= 10^78 */ + U64(0xACB92ED9, 0x397BF996), U64(0x49C2C37F, 0x07965404), /* ~= 10^79 */ + U64(0xD7E77A8F, 0x87DAF7FB), U64(0xDC33745E, 0xC97BE906), /* ~= 10^80 */ + U64(0x86F0AC99, 0xB4E8DAFD), U64(0x69A028BB, 0x3DED71A3), /* ~= 10^81 */ + U64(0xA8ACD7C0, 0x222311BC), U64(0xC40832EA, 0x0D68CE0C), /* ~= 10^82 */ + U64(0xD2D80DB0, 0x2AABD62B), U64(0xF50A3FA4, 0x90C30190), /* ~= 10^83 */ + U64(0x83C7088E, 0x1AAB65DB), U64(0x792667C6, 0xDA79E0FA), /* ~= 10^84 */ + U64(0xA4B8CAB1, 0xA1563F52), U64(0x577001B8, 0x91185938), /* ~= 10^85 */ + U64(0xCDE6FD5E, 0x09ABCF26), U64(0xED4C0226, 0xB55E6F86), /* ~= 10^86 */ + U64(0x80B05E5A, 0xC60B6178), U64(0x544F8158, 0x315B05B4), /* ~= 10^87 */ + U64(0xA0DC75F1, 0x778E39D6), U64(0x696361AE, 0x3DB1C721), /* ~= 10^88 */ + U64(0xC913936D, 0xD571C84C), U64(0x03BC3A19, 0xCD1E38E9), /* ~= 10^89 */ + U64(0xFB587849, 0x4ACE3A5F), U64(0x04AB48A0, 0x4065C723), /* ~= 10^90 */ + U64(0x9D174B2D, 0xCEC0E47B), U64(0x62EB0D64, 0x283F9C76), /* ~= 10^91 */ + U64(0xC45D1DF9, 0x42711D9A), U64(0x3BA5D0BD, 0x324F8394), /* ~= 10^92 */ + U64(0xF5746577, 0x930D6500), U64(0xCA8F44EC, 0x7EE36479), /* ~= 10^93 */ + U64(0x9968BF6A, 0xBBE85F20), U64(0x7E998B13, 0xCF4E1ECB), /* ~= 10^94 */ + U64(0xBFC2EF45, 0x6AE276E8), U64(0x9E3FEDD8, 0xC321A67E), /* ~= 10^95 */ + U64(0xEFB3AB16, 0xC59B14A2), U64(0xC5CFE94E, 0xF3EA101E), /* ~= 10^96 */ + U64(0x95D04AEE, 0x3B80ECE5), U64(0xBBA1F1D1, 0x58724A12), /* ~= 10^97 */ + U64(0xBB445DA9, 0xCA61281F), U64(0x2A8A6E45, 0xAE8EDC97), /* ~= 10^98 */ + U64(0xEA157514, 0x3CF97226), U64(0xF52D09D7, 0x1A3293BD), /* ~= 10^99 */ + U64(0x924D692C, 0xA61BE758), U64(0x593C2626, 0x705F9C56), /* ~= 10^100 */ + U64(0xB6E0C377, 0xCFA2E12E), U64(0x6F8B2FB0, 0x0C77836C), /* ~= 10^101 */ + U64(0xE498F455, 0xC38B997A), U64(0x0B6DFB9C, 0x0F956447), /* ~= 10^102 */ + U64(0x8EDF98B5, 0x9A373FEC), U64(0x4724BD41, 0x89BD5EAC), /* ~= 10^103 */ + U64(0xB2977EE3, 0x00C50FE7), U64(0x58EDEC91, 0xEC2CB657), /* ~= 10^104 */ + U64(0xDF3D5E9B, 0xC0F653E1), U64(0x2F2967B6, 0x6737E3ED), /* ~= 10^105 */ + U64(0x8B865B21, 0x5899F46C), U64(0xBD79E0D2, 0x0082EE74), /* ~= 10^106 */ + U64(0xAE67F1E9, 0xAEC07187), U64(0xECD85906, 0x80A3AA11), /* ~= 10^107 */ + U64(0xDA01EE64, 0x1A708DE9), U64(0xE80E6F48, 0x20CC9495), /* ~= 10^108 */ + U64(0x884134FE, 0x908658B2), U64(0x3109058D, 0x147FDCDD), /* ~= 10^109 */ + U64(0xAA51823E, 0x34A7EEDE), U64(0xBD4B46F0, 0x599FD415), /* ~= 10^110 */ + U64(0xD4E5E2CD, 0xC1D1EA96), U64(0x6C9E18AC, 0x7007C91A), /* ~= 10^111 */ + U64(0x850FADC0, 0x9923329E), U64(0x03E2CF6B, 0xC604DDB0), /* ~= 10^112 */ + U64(0xA6539930, 0xBF6BFF45), U64(0x84DB8346, 0xB786151C), /* ~= 10^113 */ + U64(0xCFE87F7C, 0xEF46FF16), U64(0xE6126418, 0x65679A63), /* ~= 10^114 */ + U64(0x81F14FAE, 0x158C5F6E), U64(0x4FCB7E8F, 0x3F60C07E), /* ~= 10^115 */ + U64(0xA26DA399, 0x9AEF7749), U64(0xE3BE5E33, 0x0F38F09D), /* ~= 10^116 */ + U64(0xCB090C80, 0x01AB551C), U64(0x5CADF5BF, 0xD3072CC5), /* ~= 10^117 */ + U64(0xFDCB4FA0, 0x02162A63), U64(0x73D9732F, 0xC7C8F7F6), /* ~= 10^118 */ + U64(0x9E9F11C4, 0x014DDA7E), U64(0x2867E7FD, 0xDCDD9AFA), /* ~= 10^119 */ + U64(0xC646D635, 0x01A1511D), U64(0xB281E1FD, 0x541501B8), /* ~= 10^120 */ + U64(0xF7D88BC2, 0x4209A565), U64(0x1F225A7C, 0xA91A4226), /* ~= 10^121 */ + U64(0x9AE75759, 0x6946075F), U64(0x3375788D, 0xE9B06958), /* ~= 10^122 */ + U64(0xC1A12D2F, 0xC3978937), U64(0x0052D6B1, 0x641C83AE), /* ~= 10^123 */ + U64(0xF209787B, 0xB47D6B84), U64(0xC0678C5D, 0xBD23A49A), /* ~= 10^124 */ + U64(0x9745EB4D, 0x50CE6332), U64(0xF840B7BA, 0x963646E0), /* ~= 10^125 */ + U64(0xBD176620, 0xA501FBFF), U64(0xB650E5A9, 0x3BC3D898), /* ~= 10^126 */ + U64(0xEC5D3FA8, 0xCE427AFF), U64(0xA3E51F13, 0x8AB4CEBE), /* ~= 10^127 */ + U64(0x93BA47C9, 0x80E98CDF), U64(0xC66F336C, 0x36B10137), /* ~= 10^128 */ + U64(0xB8A8D9BB, 0xE123F017), U64(0xB80B0047, 0x445D4184), /* ~= 10^129 */ + U64(0xE6D3102A, 0xD96CEC1D), U64(0xA60DC059, 0x157491E5), /* ~= 10^130 */ + U64(0x9043EA1A, 0xC7E41392), U64(0x87C89837, 0xAD68DB2F), /* ~= 10^131 */ + U64(0xB454E4A1, 0x79DD1877), U64(0x29BABE45, 0x98C311FB), /* ~= 10^132 */ + U64(0xE16A1DC9, 0xD8545E94), U64(0xF4296DD6, 0xFEF3D67A), /* ~= 10^133 */ + U64(0x8CE2529E, 0x2734BB1D), U64(0x1899E4A6, 0x5F58660C), /* ~= 10^134 */ + U64(0xB01AE745, 0xB101E9E4), U64(0x5EC05DCF, 0xF72E7F8F), /* ~= 10^135 */ + U64(0xDC21A117, 0x1D42645D), U64(0x76707543, 0xF4FA1F73), /* ~= 10^136 */ + U64(0x899504AE, 0x72497EBA), U64(0x6A06494A, 0x791C53A8), /* ~= 10^137 */ + U64(0xABFA45DA, 0x0EDBDE69), U64(0x0487DB9D, 0x17636892), /* ~= 10^138 */ + U64(0xD6F8D750, 0x9292D603), U64(0x45A9D284, 0x5D3C42B6), /* ~= 10^139 */ + U64(0x865B8692, 0x5B9BC5C2), U64(0x0B8A2392, 0xBA45A9B2), /* ~= 10^140 */ + U64(0xA7F26836, 0xF282B732), U64(0x8E6CAC77, 0x68D7141E), /* ~= 10^141 */ + U64(0xD1EF0244, 0xAF2364FF), U64(0x3207D795, 0x430CD926), /* ~= 10^142 */ + U64(0x8335616A, 0xED761F1F), U64(0x7F44E6BD, 0x49E807B8), /* ~= 10^143 */ + U64(0xA402B9C5, 0xA8D3A6E7), U64(0x5F16206C, 0x9C6209A6), /* ~= 10^144 */ + U64(0xCD036837, 0x130890A1), U64(0x36DBA887, 0xC37A8C0F), /* ~= 10^145 */ + U64(0x80222122, 0x6BE55A64), U64(0xC2494954, 0xDA2C9789), /* ~= 10^146 */ + U64(0xA02AA96B, 0x06DEB0FD), U64(0xF2DB9BAA, 0x10B7BD6C), /* ~= 10^147 */ + U64(0xC83553C5, 0xC8965D3D), U64(0x6F928294, 0x94E5ACC7), /* ~= 10^148 */ + U64(0xFA42A8B7, 0x3ABBF48C), U64(0xCB772339, 0xBA1F17F9), /* ~= 10^149 */ + U64(0x9C69A972, 0x84B578D7), U64(0xFF2A7604, 0x14536EFB), /* ~= 10^150 */ + U64(0xC38413CF, 0x25E2D70D), U64(0xFEF51385, 0x19684ABA), /* ~= 10^151 */ + U64(0xF46518C2, 0xEF5B8CD1), U64(0x7EB25866, 0x5FC25D69), /* ~= 10^152 */ + U64(0x98BF2F79, 0xD5993802), U64(0xEF2F773F, 0xFBD97A61), /* ~= 10^153 */ + U64(0xBEEEFB58, 0x4AFF8603), U64(0xAAFB550F, 0xFACFD8FA), /* ~= 10^154 */ + U64(0xEEAABA2E, 0x5DBF6784), U64(0x95BA2A53, 0xF983CF38), /* ~= 10^155 */ + U64(0x952AB45C, 0xFA97A0B2), U64(0xDD945A74, 0x7BF26183), /* ~= 10^156 */ + U64(0xBA756174, 0x393D88DF), U64(0x94F97111, 0x9AEEF9E4), /* ~= 10^157 */ + U64(0xE912B9D1, 0x478CEB17), U64(0x7A37CD56, 0x01AAB85D), /* ~= 10^158 */ + U64(0x91ABB422, 0xCCB812EE), U64(0xAC62E055, 0xC10AB33A), /* ~= 10^159 */ + U64(0xB616A12B, 0x7FE617AA), U64(0x577B986B, 0x314D6009), /* ~= 10^160 */ + U64(0xE39C4976, 0x5FDF9D94), U64(0xED5A7E85, 0xFDA0B80B), /* ~= 10^161 */ + U64(0x8E41ADE9, 0xFBEBC27D), U64(0x14588F13, 0xBE847307), /* ~= 10^162 */ + U64(0xB1D21964, 0x7AE6B31C), U64(0x596EB2D8, 0xAE258FC8), /* ~= 10^163 */ + U64(0xDE469FBD, 0x99A05FE3), U64(0x6FCA5F8E, 0xD9AEF3BB), /* ~= 10^164 */ + U64(0x8AEC23D6, 0x80043BEE), U64(0x25DE7BB9, 0x480D5854), /* ~= 10^165 */ + U64(0xADA72CCC, 0x20054AE9), U64(0xAF561AA7, 0x9A10AE6A), /* ~= 10^166 */ + U64(0xD910F7FF, 0x28069DA4), U64(0x1B2BA151, 0x8094DA04), /* ~= 10^167 */ + U64(0x87AA9AFF, 0x79042286), U64(0x90FB44D2, 0xF05D0842), /* ~= 10^168 */ + U64(0xA99541BF, 0x57452B28), U64(0x353A1607, 0xAC744A53), /* ~= 10^169 */ + U64(0xD3FA922F, 0x2D1675F2), U64(0x42889B89, 0x97915CE8), /* ~= 10^170 */ + U64(0x847C9B5D, 0x7C2E09B7), U64(0x69956135, 0xFEBADA11), /* ~= 10^171 */ + U64(0xA59BC234, 0xDB398C25), U64(0x43FAB983, 0x7E699095), /* ~= 10^172 */ + U64(0xCF02B2C2, 0x1207EF2E), U64(0x94F967E4, 0x5E03F4BB), /* ~= 10^173 */ + U64(0x8161AFB9, 0x4B44F57D), U64(0x1D1BE0EE, 0xBAC278F5), /* ~= 10^174 */ + U64(0xA1BA1BA7, 0x9E1632DC), U64(0x6462D92A, 0x69731732), /* ~= 10^175 */ + U64(0xCA28A291, 0x859BBF93), U64(0x7D7B8F75, 0x03CFDCFE), /* ~= 10^176 */ + U64(0xFCB2CB35, 0xE702AF78), U64(0x5CDA7352, 0x44C3D43E), /* ~= 10^177 */ + U64(0x9DEFBF01, 0xB061ADAB), U64(0x3A088813, 0x6AFA64A7), /* ~= 10^178 */ + U64(0xC56BAEC2, 0x1C7A1916), U64(0x088AAA18, 0x45B8FDD0), /* ~= 10^179 */ + U64(0xF6C69A72, 0xA3989F5B), U64(0x8AAD549E, 0x57273D45), /* ~= 10^180 */ + U64(0x9A3C2087, 0xA63F6399), U64(0x36AC54E2, 0xF678864B), /* ~= 10^181 */ + U64(0xC0CB28A9, 0x8FCF3C7F), U64(0x84576A1B, 0xB416A7DD), /* ~= 10^182 */ + U64(0xF0FDF2D3, 0xF3C30B9F), U64(0x656D44A2, 0xA11C51D5), /* ~= 10^183 */ + U64(0x969EB7C4, 0x7859E743), U64(0x9F644AE5, 0xA4B1B325), /* ~= 10^184 */ + U64(0xBC4665B5, 0x96706114), U64(0x873D5D9F, 0x0DDE1FEE), /* ~= 10^185 */ + U64(0xEB57FF22, 0xFC0C7959), U64(0xA90CB506, 0xD155A7EA), /* ~= 10^186 */ + U64(0x9316FF75, 0xDD87CBD8), U64(0x09A7F124, 0x42D588F2), /* ~= 10^187 */ + U64(0xB7DCBF53, 0x54E9BECE), U64(0x0C11ED6D, 0x538AEB2F), /* ~= 10^188 */ + U64(0xE5D3EF28, 0x2A242E81), U64(0x8F1668C8, 0xA86DA5FA), /* ~= 10^189 */ + U64(0x8FA47579, 0x1A569D10), U64(0xF96E017D, 0x694487BC), /* ~= 10^190 */ + U64(0xB38D92D7, 0x60EC4455), U64(0x37C981DC, 0xC395A9AC), /* ~= 10^191 */ + U64(0xE070F78D, 0x3927556A), U64(0x85BBE253, 0xF47B1417), /* ~= 10^192 */ + U64(0x8C469AB8, 0x43B89562), U64(0x93956D74, 0x78CCEC8E), /* ~= 10^193 */ + U64(0xAF584166, 0x54A6BABB), U64(0x387AC8D1, 0x970027B2), /* ~= 10^194 */ + U64(0xDB2E51BF, 0xE9D0696A), U64(0x06997B05, 0xFCC0319E), /* ~= 10^195 */ + U64(0x88FCF317, 0xF22241E2), U64(0x441FECE3, 0xBDF81F03), /* ~= 10^196 */ + U64(0xAB3C2FDD, 0xEEAAD25A), U64(0xD527E81C, 0xAD7626C3), /* ~= 10^197 */ + U64(0xD60B3BD5, 0x6A5586F1), U64(0x8A71E223, 0xD8D3B074), /* ~= 10^198 */ + U64(0x85C70565, 0x62757456), U64(0xF6872D56, 0x67844E49), /* ~= 10^199 */ + U64(0xA738C6BE, 0xBB12D16C), U64(0xB428F8AC, 0x016561DB), /* ~= 10^200 */ + U64(0xD106F86E, 0x69D785C7), U64(0xE13336D7, 0x01BEBA52), /* ~= 10^201 */ + U64(0x82A45B45, 0x0226B39C), U64(0xECC00246, 0x61173473), /* ~= 10^202 */ + U64(0xA34D7216, 0x42B06084), U64(0x27F002D7, 0xF95D0190), /* ~= 10^203 */ + U64(0xCC20CE9B, 0xD35C78A5), U64(0x31EC038D, 0xF7B441F4), /* ~= 10^204 */ + U64(0xFF290242, 0xC83396CE), U64(0x7E670471, 0x75A15271), /* ~= 10^205 */ + U64(0x9F79A169, 0xBD203E41), U64(0x0F0062C6, 0xE984D386), /* ~= 10^206 */ + U64(0xC75809C4, 0x2C684DD1), U64(0x52C07B78, 0xA3E60868), /* ~= 10^207 */ + U64(0xF92E0C35, 0x37826145), U64(0xA7709A56, 0xCCDF8A82), /* ~= 10^208 */ + U64(0x9BBCC7A1, 0x42B17CCB), U64(0x88A66076, 0x400BB691), /* ~= 10^209 */ + U64(0xC2ABF989, 0x935DDBFE), U64(0x6ACFF893, 0xD00EA435), /* ~= 10^210 */ + U64(0xF356F7EB, 0xF83552FE), U64(0x0583F6B8, 0xC4124D43), /* ~= 10^211 */ + U64(0x98165AF3, 0x7B2153DE), U64(0xC3727A33, 0x7A8B704A), /* ~= 10^212 */ + U64(0xBE1BF1B0, 0x59E9A8D6), U64(0x744F18C0, 0x592E4C5C), /* ~= 10^213 */ + U64(0xEDA2EE1C, 0x7064130C), U64(0x1162DEF0, 0x6F79DF73), /* ~= 10^214 */ + U64(0x9485D4D1, 0xC63E8BE7), U64(0x8ADDCB56, 0x45AC2BA8), /* ~= 10^215 */ + U64(0xB9A74A06, 0x37CE2EE1), U64(0x6D953E2B, 0xD7173692), /* ~= 10^216 */ + U64(0xE8111C87, 0xC5C1BA99), U64(0xC8FA8DB6, 0xCCDD0437), /* ~= 10^217 */ + U64(0x910AB1D4, 0xDB9914A0), U64(0x1D9C9892, 0x400A22A2), /* ~= 10^218 */ + U64(0xB54D5E4A, 0x127F59C8), U64(0x2503BEB6, 0xD00CAB4B), /* ~= 10^219 */ + U64(0xE2A0B5DC, 0x971F303A), U64(0x2E44AE64, 0x840FD61D), /* ~= 10^220 */ + U64(0x8DA471A9, 0xDE737E24), U64(0x5CEAECFE, 0xD289E5D2), /* ~= 10^221 */ + U64(0xB10D8E14, 0x56105DAD), U64(0x7425A83E, 0x872C5F47), /* ~= 10^222 */ + U64(0xDD50F199, 0x6B947518), U64(0xD12F124E, 0x28F77719), /* ~= 10^223 */ + U64(0x8A5296FF, 0xE33CC92F), U64(0x82BD6B70, 0xD99AAA6F), /* ~= 10^224 */ + U64(0xACE73CBF, 0xDC0BFB7B), U64(0x636CC64D, 0x1001550B), /* ~= 10^225 */ + U64(0xD8210BEF, 0xD30EFA5A), U64(0x3C47F7E0, 0x5401AA4E), /* ~= 10^226 */ + U64(0x8714A775, 0xE3E95C78), U64(0x65ACFAEC, 0x34810A71), /* ~= 10^227 */ + U64(0xA8D9D153, 0x5CE3B396), U64(0x7F1839A7, 0x41A14D0D), /* ~= 10^228 */ + U64(0xD31045A8, 0x341CA07C), U64(0x1EDE4811, 0x1209A050), /* ~= 10^229 */ + U64(0x83EA2B89, 0x2091E44D), U64(0x934AED0A, 0xAB460432), /* ~= 10^230 */ + U64(0xA4E4B66B, 0x68B65D60), U64(0xF81DA84D, 0x5617853F), /* ~= 10^231 */ + U64(0xCE1DE406, 0x42E3F4B9), U64(0x36251260, 0xAB9D668E), /* ~= 10^232 */ + U64(0x80D2AE83, 0xE9CE78F3), U64(0xC1D72B7C, 0x6B426019), /* ~= 10^233 */ + U64(0xA1075A24, 0xE4421730), U64(0xB24CF65B, 0x8612F81F), /* ~= 10^234 */ + U64(0xC94930AE, 0x1D529CFC), U64(0xDEE033F2, 0x6797B627), /* ~= 10^235 */ + U64(0xFB9B7CD9, 0xA4A7443C), U64(0x169840EF, 0x017DA3B1), /* ~= 10^236 */ + U64(0x9D412E08, 0x06E88AA5), U64(0x8E1F2895, 0x60EE864E), /* ~= 10^237 */ + U64(0xC491798A, 0x08A2AD4E), U64(0xF1A6F2BA, 0xB92A27E2), /* ~= 10^238 */ + U64(0xF5B5D7EC, 0x8ACB58A2), U64(0xAE10AF69, 0x6774B1DB), /* ~= 10^239 */ + U64(0x9991A6F3, 0xD6BF1765), U64(0xACCA6DA1, 0xE0A8EF29), /* ~= 10^240 */ + U64(0xBFF610B0, 0xCC6EDD3F), U64(0x17FD090A, 0x58D32AF3), /* ~= 10^241 */ + U64(0xEFF394DC, 0xFF8A948E), U64(0xDDFC4B4C, 0xEF07F5B0), /* ~= 10^242 */ + U64(0x95F83D0A, 0x1FB69CD9), U64(0x4ABDAF10, 0x1564F98E), /* ~= 10^243 */ + U64(0xBB764C4C, 0xA7A4440F), U64(0x9D6D1AD4, 0x1ABE37F1), /* ~= 10^244 */ + U64(0xEA53DF5F, 0xD18D5513), U64(0x84C86189, 0x216DC5ED), /* ~= 10^245 */ + U64(0x92746B9B, 0xE2F8552C), U64(0x32FD3CF5, 0xB4E49BB4), /* ~= 10^246 */ + U64(0xB7118682, 0xDBB66A77), U64(0x3FBC8C33, 0x221DC2A1), /* ~= 10^247 */ + U64(0xE4D5E823, 0x92A40515), U64(0x0FABAF3F, 0xEAA5334A), /* ~= 10^248 */ + U64(0x8F05B116, 0x3BA6832D), U64(0x29CB4D87, 0xF2A7400E), /* ~= 10^249 */ + U64(0xB2C71D5B, 0xCA9023F8), U64(0x743E20E9, 0xEF511012), /* ~= 10^250 */ + U64(0xDF78E4B2, 0xBD342CF6), U64(0x914DA924, 0x6B255416), /* ~= 10^251 */ + U64(0x8BAB8EEF, 0xB6409C1A), U64(0x1AD089B6, 0xC2F7548E), /* ~= 10^252 */ + U64(0xAE9672AB, 0xA3D0C320), U64(0xA184AC24, 0x73B529B1), /* ~= 10^253 */ + U64(0xDA3C0F56, 0x8CC4F3E8), U64(0xC9E5D72D, 0x90A2741E), /* ~= 10^254 */ + U64(0x88658996, 0x17FB1871), U64(0x7E2FA67C, 0x7A658892), /* ~= 10^255 */ + U64(0xAA7EEBFB, 0x9DF9DE8D), U64(0xDDBB901B, 0x98FEEAB7), /* ~= 10^256 */ + U64(0xD51EA6FA, 0x85785631), U64(0x552A7422, 0x7F3EA565), /* ~= 10^257 */ + U64(0x8533285C, 0x936B35DE), U64(0xD53A8895, 0x8F87275F), /* ~= 10^258 */ + U64(0xA67FF273, 0xB8460356), U64(0x8A892ABA, 0xF368F137), /* ~= 10^259 */ + U64(0xD01FEF10, 0xA657842C), U64(0x2D2B7569, 0xB0432D85), /* ~= 10^260 */ + U64(0x8213F56A, 0x67F6B29B), U64(0x9C3B2962, 0x0E29FC73), /* ~= 10^261 */ + U64(0xA298F2C5, 0x01F45F42), U64(0x8349F3BA, 0x91B47B8F), /* ~= 10^262 */ + U64(0xCB3F2F76, 0x42717713), U64(0x241C70A9, 0x36219A73), /* ~= 10^263 */ + U64(0xFE0EFB53, 0xD30DD4D7), U64(0xED238CD3, 0x83AA0110), /* ~= 10^264 */ + U64(0x9EC95D14, 0x63E8A506), U64(0xF4363804, 0x324A40AA), /* ~= 10^265 */ + U64(0xC67BB459, 0x7CE2CE48), U64(0xB143C605, 0x3EDCD0D5), /* ~= 10^266 */ + U64(0xF81AA16F, 0xDC1B81DA), U64(0xDD94B786, 0x8E94050A), /* ~= 10^267 */ + U64(0x9B10A4E5, 0xE9913128), U64(0xCA7CF2B4, 0x191C8326), /* ~= 10^268 */ + U64(0xC1D4CE1F, 0x63F57D72), U64(0xFD1C2F61, 0x1F63A3F0), /* ~= 10^269 */ + U64(0xF24A01A7, 0x3CF2DCCF), U64(0xBC633B39, 0x673C8CEC), /* ~= 10^270 */ + U64(0x976E4108, 0x8617CA01), U64(0xD5BE0503, 0xE085D813), /* ~= 10^271 */ + U64(0xBD49D14A, 0xA79DBC82), U64(0x4B2D8644, 0xD8A74E18), /* ~= 10^272 */ + U64(0xEC9C459D, 0x51852BA2), U64(0xDDF8E7D6, 0x0ED1219E), /* ~= 10^273 */ + U64(0x93E1AB82, 0x52F33B45), U64(0xCABB90E5, 0xC942B503), /* ~= 10^274 */ + U64(0xB8DA1662, 0xE7B00A17), U64(0x3D6A751F, 0x3B936243), /* ~= 10^275 */ + U64(0xE7109BFB, 0xA19C0C9D), U64(0x0CC51267, 0x0A783AD4), /* ~= 10^276 */ + U64(0x906A617D, 0x450187E2), U64(0x27FB2B80, 0x668B24C5), /* ~= 10^277 */ + U64(0xB484F9DC, 0x9641E9DA), U64(0xB1F9F660, 0x802DEDF6), /* ~= 10^278 */ + U64(0xE1A63853, 0xBBD26451), U64(0x5E7873F8, 0xA0396973), /* ~= 10^279 */ + U64(0x8D07E334, 0x55637EB2), U64(0xDB0B487B, 0x6423E1E8), /* ~= 10^280 */ + U64(0xB049DC01, 0x6ABC5E5F), U64(0x91CE1A9A, 0x3D2CDA62), /* ~= 10^281 */ + U64(0xDC5C5301, 0xC56B75F7), U64(0x7641A140, 0xCC7810FB), /* ~= 10^282 */ + U64(0x89B9B3E1, 0x1B6329BA), U64(0xA9E904C8, 0x7FCB0A9D), /* ~= 10^283 */ + U64(0xAC2820D9, 0x623BF429), U64(0x546345FA, 0x9FBDCD44), /* ~= 10^284 */ + U64(0xD732290F, 0xBACAF133), U64(0xA97C1779, 0x47AD4095), /* ~= 10^285 */ + U64(0x867F59A9, 0xD4BED6C0), U64(0x49ED8EAB, 0xCCCC485D), /* ~= 10^286 */ + U64(0xA81F3014, 0x49EE8C70), U64(0x5C68F256, 0xBFFF5A74), /* ~= 10^287 */ + U64(0xD226FC19, 0x5C6A2F8C), U64(0x73832EEC, 0x6FFF3111), /* ~= 10^288 */ + U64(0x83585D8F, 0xD9C25DB7), U64(0xC831FD53, 0xC5FF7EAB), /* ~= 10^289 */ + U64(0xA42E74F3, 0xD032F525), U64(0xBA3E7CA8, 0xB77F5E55), /* ~= 10^290 */ + U64(0xCD3A1230, 0xC43FB26F), U64(0x28CE1BD2, 0xE55F35EB), /* ~= 10^291 */ + U64(0x80444B5E, 0x7AA7CF85), U64(0x7980D163, 0xCF5B81B3), /* ~= 10^292 */ + U64(0xA0555E36, 0x1951C366), U64(0xD7E105BC, 0xC332621F), /* ~= 10^293 */ + U64(0xC86AB5C3, 0x9FA63440), U64(0x8DD9472B, 0xF3FEFAA7), /* ~= 10^294 */ + U64(0xFA856334, 0x878FC150), U64(0xB14F98F6, 0xF0FEB951), /* ~= 10^295 */ + U64(0x9C935E00, 0xD4B9D8D2), U64(0x6ED1BF9A, 0x569F33D3), /* ~= 10^296 */ + U64(0xC3B83581, 0x09E84F07), U64(0x0A862F80, 0xEC4700C8), /* ~= 10^297 */ + U64(0xF4A642E1, 0x4C6262C8), U64(0xCD27BB61, 0x2758C0FA), /* ~= 10^298 */ + U64(0x98E7E9CC, 0xCFBD7DBD), U64(0x8038D51C, 0xB897789C), /* ~= 10^299 */ + U64(0xBF21E440, 0x03ACDD2C), U64(0xE0470A63, 0xE6BD56C3), /* ~= 10^300 */ + U64(0xEEEA5D50, 0x04981478), U64(0x1858CCFC, 0xE06CAC74), /* ~= 10^301 */ + U64(0x95527A52, 0x02DF0CCB), U64(0x0F37801E, 0x0C43EBC8), /* ~= 10^302 */ + U64(0xBAA718E6, 0x8396CFFD), U64(0xD3056025, 0x8F54E6BA), /* ~= 10^303 */ + U64(0xE950DF20, 0x247C83FD), U64(0x47C6B82E, 0xF32A2069), /* ~= 10^304 */ + U64(0x91D28B74, 0x16CDD27E), U64(0x4CDC331D, 0x57FA5441), /* ~= 10^305 */ + U64(0xB6472E51, 0x1C81471D), U64(0xE0133FE4, 0xADF8E952), /* ~= 10^306 */ + U64(0xE3D8F9E5, 0x63A198E5), U64(0x58180FDD, 0xD97723A6), /* ~= 10^307 */ + U64(0x8E679C2F, 0x5E44FF8F), U64(0x570F09EA, 0xA7EA7648), /* ~= 10^308 */ + U64(0xB201833B, 0x35D63F73), U64(0x2CD2CC65, 0x51E513DA), /* ~= 10^309 */ + U64(0xDE81E40A, 0x034BCF4F), U64(0xF8077F7E, 0xA65E58D1), /* ~= 10^310 */ + U64(0x8B112E86, 0x420F6191), U64(0xFB04AFAF, 0x27FAF782), /* ~= 10^311 */ + U64(0xADD57A27, 0xD29339F6), U64(0x79C5DB9A, 0xF1F9B563), /* ~= 10^312 */ + U64(0xD94AD8B1, 0xC7380874), U64(0x18375281, 0xAE7822BC), /* ~= 10^313 */ + U64(0x87CEC76F, 0x1C830548), U64(0x8F229391, 0x0D0B15B5), /* ~= 10^314 */ + U64(0xA9C2794A, 0xE3A3C69A), U64(0xB2EB3875, 0x504DDB22), /* ~= 10^315 */ + U64(0xD433179D, 0x9C8CB841), U64(0x5FA60692, 0xA46151EB), /* ~= 10^316 */ + U64(0x849FEEC2, 0x81D7F328), U64(0xDBC7C41B, 0xA6BCD333), /* ~= 10^317 */ + U64(0xA5C7EA73, 0x224DEFF3), U64(0x12B9B522, 0x906C0800), /* ~= 10^318 */ + U64(0xCF39E50F, 0xEAE16BEF), U64(0xD768226B, 0x34870A00), /* ~= 10^319 */ + U64(0x81842F29, 0xF2CCE375), U64(0xE6A11583, 0x00D46640), /* ~= 10^320 */ + U64(0xA1E53AF4, 0x6F801C53), U64(0x60495AE3, 0xC1097FD0), /* ~= 10^321 */ + U64(0xCA5E89B1, 0x8B602368), U64(0x385BB19C, 0xB14BDFC4), /* ~= 10^322 */ + U64(0xFCF62C1D, 0xEE382C42), U64(0x46729E03, 0xDD9ED7B5), /* ~= 10^323 */ + U64(0x9E19DB92, 0xB4E31BA9), U64(0x6C07A2C2, 0x6A8346D1) /* ~= 10^324 */ +}; + +/** + Get the cached pow10 value from `pow10_sig_table`. + @param exp10 The exponent of pow(10, e). This value must in range + `POW10_SIG_TABLE_MIN_EXP` to `POW10_SIG_TABLE_MAX_EXP`. + @param hi The highest 64 bits of pow(10, e). + @param lo The lower 64 bits after `hi`. + */ +static_inline void pow10_table_get_sig(i32 exp10, u64 *hi, u64 *lo) { + i32 idx = exp10 - (POW10_SIG_TABLE_MIN_EXP); + *hi = pow10_sig_table[idx * 2]; + *lo = pow10_sig_table[idx * 2 + 1]; +} + +/** + Get the exponent (base 2) for highest 64 bits significand in `pow10_sig_table`. + */ +static_inline void pow10_table_get_exp(i32 exp10, i32 *exp2) { + /* e2 = floor(log2(pow(10, e))) - 64 + 1 */ + /* = floor(e * log2(10) - 63) */ + *exp2 = (exp10 * 217706 - 4128768) >> 16; +} + +#endif + + + +/*============================================================================== + * MARK: - Number and Bit Utils (Private) + *============================================================================*/ + +/** Convert bits to double. */ +static_inline f64 f64_from_bits(u64 u) { + f64 f; + memcpy(&f, &u, sizeof(u)); + return f; +} + +/** Convert double to bits. */ +static_inline u64 f64_to_bits(f64 f) { + u64 u; + memcpy(&u, &f, sizeof(u)); + return u; +} + +/** Convert double to bits. */ +static_inline u32 f32_to_bits(f32 f) { + u32 u; + memcpy(&u, &f, sizeof(u)); + return u; +} + +/** Get 'infinity' bits with sign. */ +static_inline u64 f64_bits_inf(bool sign) { +#if YYJSON_HAS_IEEE_754 + return F64_BITS_INF | ((u64)sign << 63); +#elif defined(INFINITY) + return f64_to_bits(sign ? -INFINITY : INFINITY); +#else + return f64_to_bits(sign ? -HUGE_VAL : HUGE_VAL); +#endif +} + +/** Get 'nan' bits with sign. */ +static_inline u64 f64_bits_nan(bool sign) { +#if YYJSON_HAS_IEEE_754 + return F64_BITS_NAN | ((u64)sign << 63); +#elif defined(NAN) + return f64_to_bits(sign ? (f64)-NAN : (f64)NAN); +#else + return f64_to_bits((sign ? -0.0 : 0.0) / 0.0); +#endif +} + +/** Casting double to float, allow overflow. */ +#if yyjson_has_attribute(no_sanitize) +__attribute__((no_sanitize("undefined"))) +#elif yyjson_gcc_available(4, 9, 0) +__attribute__((__no_sanitize_undefined__)) +#endif +static_inline f32 f64_to_f32(f64 val) { + return (f32)val; +} + +/** Returns the number of leading 0-bits in value (input should not be 0). */ +static_inline u32 u64_lz_bits(u64 v) { +#if GCC_HAS_CLZLL + return (u32)__builtin_clzll(v); +#elif MSC_HAS_BIT_SCAN_64 + unsigned long r; + _BitScanReverse64(&r, v); + return (u32)63 - (u32)r; +#elif MSC_HAS_BIT_SCAN + unsigned long hi, lo; + bool hi_set = _BitScanReverse(&hi, (u32)(v >> 32)) != 0; + _BitScanReverse(&lo, (u32)v); + hi |= 32; + return (u32)63 - (u32)(hi_set ? hi : lo); +#else + /* branchless, use De Bruijn sequence */ + /* see: https://www.chessprogramming.org/BitScan */ + const u8 table[64] = { + 63, 16, 62, 7, 15, 36, 61, 3, 6, 14, 22, 26, 35, 47, 60, 2, + 9, 5, 28, 11, 13, 21, 42, 19, 25, 31, 34, 40, 46, 52, 59, 1, + 17, 8, 37, 4, 23, 27, 48, 10, 29, 12, 43, 20, 32, 41, 53, 18, + 38, 24, 49, 30, 44, 33, 54, 39, 50, 45, 55, 51, 56, 57, 58, 0 + }; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; + return table[(v * U64(0x03F79D71, 0xB4CB0A89)) >> 58]; +#endif +} + +/** Returns the number of trailing 0-bits in value (input should not be 0). */ +static_inline u32 u64_tz_bits(u64 v) { +#if GCC_HAS_CTZLL + return (u32)__builtin_ctzll(v); +#elif MSC_HAS_BIT_SCAN_64 + unsigned long r; + _BitScanForward64(&r, v); + return (u32)r; +#elif MSC_HAS_BIT_SCAN + unsigned long lo, hi; + bool lo_set = _BitScanForward(&lo, (u32)(v)) != 0; + _BitScanForward(&hi, (u32)(v >> 32)); + hi += 32; + return lo_set ? lo : hi; +#else + /* branchless, use De Bruijn sequence */ + /* see: https://www.chessprogramming.org/BitScan */ + const u8 table[64] = { + 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, + 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, + 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, + 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12 + }; + return table[((v & (~v + 1)) * U64(0x022FDD63, 0xCC95386D)) >> 58]; +#endif +} + +/** Multiplies two 64-bit unsigned integers (a * b), + returns the 128-bit result as 'hi' and 'lo'. */ +static_inline void u128_mul(u64 a, u64 b, u64 *hi, u64 *lo) { +#if YYJSON_HAS_INT128 + u128 m = (u128)a * b; + *hi = (u64)(m >> 64); + *lo = (u64)(m); +#elif MSC_HAS_UMUL128 + *lo = _umul128(a, b, hi); +#else + u32 a0 = (u32)(a), a1 = (u32)(a >> 32); + u32 b0 = (u32)(b), b1 = (u32)(b >> 32); + u64 p00 = (u64)a0 * b0, p01 = (u64)a0 * b1; + u64 p10 = (u64)a1 * b0, p11 = (u64)a1 * b1; + u64 m0 = p01 + (p00 >> 32); + u32 m00 = (u32)(m0), m01 = (u32)(m0 >> 32); + u64 m1 = p10 + m00; + u32 m10 = (u32)(m1), m11 = (u32)(m1 >> 32); + *hi = p11 + m01 + m11; + *lo = ((u64)m10 << 32) | (u32)p00; +#endif +} + +/** Multiplies two 64-bit unsigned integers and add a value (a * b + c), + returns the 128-bit result as 'hi' and 'lo'. */ +static_inline void u128_mul_add(u64 a, u64 b, u64 c, u64 *hi, u64 *lo) { +#if YYJSON_HAS_INT128 + u128 m = (u128)a * b + c; + *hi = (u64)(m >> 64); + *lo = (u64)(m); +#else + u64 h, l, t; + u128_mul(a, b, &h, &l); + t = l + c; + h += (u64)(((t < l) | (t < c))); + *hi = h; + *lo = t; +#endif +} + + + +/*============================================================================== + * MARK: - File Utils (Private) + * These functions are used to read and write JSON files. + *============================================================================*/ + +#define YYJSON_FOPEN_E +#if !defined(_MSC_VER) && defined(__GLIBC__) && defined(__GLIBC_PREREQ) +# if __GLIBC_PREREQ(2, 7) +# undef YYJSON_FOPEN_E +# define YYJSON_FOPEN_E "e" /* glibc extension to enable O_CLOEXEC */ +# endif +#endif + +static_inline FILE *fopen_safe(const char *path, const char *mode) { +#if YYJSON_MSC_VER >= 1400 + FILE *file = NULL; + if (fopen_s(&file, path, mode) != 0) return NULL; + return file; +#else + return fopen(path, mode); +#endif +} + +static_inline FILE *fopen_readonly(const char *path) { + return fopen_safe(path, "rb" YYJSON_FOPEN_E); +} + +static_inline FILE *fopen_writeonly(const char *path) { + return fopen_safe(path, "wb" YYJSON_FOPEN_E); +} + +static_inline usize fread_safe(void *buf, usize size, FILE *file) { +#if YYJSON_MSC_VER >= 1400 + return fread_s(buf, size, 1, size, file); +#else + return fread(buf, 1, size, file); +#endif +} + + + +/*============================================================================== + * MARK: - Size Utils (Private) + * These functions are used for memory allocation. + *============================================================================*/ + +/** Returns whether the size is overflow after increment. */ +static_inline bool size_add_is_overflow(usize size, usize add) { + return size > (size + add); +} + +/** Returns whether the size is power of 2 (size should not be 0). */ +static_inline bool size_is_pow2(usize size) { + return (size & (size - 1)) == 0; +} + +/** Align size upwards (may overflow). */ +static_inline usize size_align_up(usize size, usize align) { + if (size_is_pow2(align)) { + return (size + (align - 1)) & ~(align - 1); + } else { + return size + align - (size + align - 1) % align - 1; + } +} + +/** Align size downwards. */ +static_inline usize size_align_down(usize size, usize align) { + if (size_is_pow2(align)) { + return size & ~(align - 1); + } else { + return size - (size % align); + } +} + +/** Align address upwards (may overflow). */ +static_inline void *mem_align_up(void *mem, usize align) { + usize size; + memcpy(&size, &mem, sizeof(usize)); + size = size_align_up(size, align); + memcpy(&mem, &size, sizeof(usize)); + return mem; +} + + + +/*============================================================================== + * MARK: - Default Memory Allocator (Private) + * This is a simple libc memory allocator wrapper. + *============================================================================*/ + +static void *default_malloc(void *ctx, usize size) { + return malloc(size); +} + +static void *default_realloc(void *ctx, void *ptr, usize old_size, usize size) { + return realloc(ptr, size); +} + +static void default_free(void *ctx, void *ptr) { + free(ptr); +} + +static const yyjson_alc YYJSON_DEFAULT_ALC = { + default_malloc, default_realloc, default_free, NULL +}; + + + +/*============================================================================== + * MARK: - Null Memory Allocator (Private) + * This allocator is just a placeholder to ensure that the internal + * malloc/realloc/free function pointers are not null. + *============================================================================*/ + +static void *null_malloc(void *ctx, usize size) { + return NULL; +} + +static void *null_realloc(void *ctx, void *ptr, usize old_size, usize size) { + return NULL; +} + +static void null_free(void *ctx, void *ptr) { + return; +} + +static const yyjson_alc YYJSON_NULL_ALC = { + null_malloc, null_realloc, null_free, NULL +}; + + + +/*============================================================================== + * MARK: - Pool Memory Allocator (Public) + * This allocator is initialized with a fixed-size buffer. + * The buffer is split into multiple memory chunks for memory allocation. + *============================================================================*/ + +/** memory chunk header */ +typedef struct pool_chunk { + usize size; /* chunk memory size, include chunk header */ + struct pool_chunk *next; /* linked list, nullable */ + /* char mem[]; flexible array member */ +} pool_chunk; + +/** allocator ctx header */ +typedef struct pool_ctx { + usize size; /* total memory size, include ctx header */ + pool_chunk *free_list; /* linked list, nullable */ + /* pool_chunk chunks[]; flexible array member */ +} pool_ctx; + +/** align up the input size to chunk size */ +static_inline void pool_size_align(usize *size) { + *size = size_align_up(*size, sizeof(pool_chunk)) + sizeof(pool_chunk); +} + +static void *pool_malloc(void *ctx_ptr, usize size) { + /* assert(size != 0) */ + pool_ctx *ctx = (pool_ctx *)ctx_ptr; + pool_chunk *next, *prev = NULL, *cur = ctx->free_list; + + if (unlikely(size >= ctx->size)) return NULL; + pool_size_align(&size); + + while (cur) { + if (cur->size < size) { + /* not enough space, try next chunk */ + prev = cur; + cur = cur->next; + continue; + } + if (cur->size >= size + sizeof(pool_chunk) * 2) { + /* too much space, split this chunk */ + next = (pool_chunk *)(void *)((u8 *)cur + size); + next->size = cur->size - size; + next->next = cur->next; + cur->size = size; + } else { + /* just enough space, use whole chunk */ + next = cur->next; + } + if (prev) prev->next = next; + else ctx->free_list = next; + return (void *)(cur + 1); + } + return NULL; +} + +static void pool_free(void *ctx_ptr, void *ptr) { + /* assert(ptr != NULL) */ + pool_ctx *ctx = (pool_ctx *)ctx_ptr; + pool_chunk *cur = ((pool_chunk *)ptr) - 1; + pool_chunk *prev = NULL, *next = ctx->free_list; + + while (next && next < cur) { + prev = next; + next = next->next; + } + if (prev) prev->next = cur; + else ctx->free_list = cur; + cur->next = next; + + if (next && ((u8 *)cur + cur->size) == (u8 *)next) { + /* merge cur to higher chunk */ + cur->size += next->size; + cur->next = next->next; + } + if (prev && ((u8 *)prev + prev->size) == (u8 *)cur) { + /* merge cur to lower chunk */ + prev->size += cur->size; + prev->next = cur->next; + } +} + +static void *pool_realloc(void *ctx_ptr, void *ptr, + usize old_size, usize size) { + /* assert(ptr != NULL && size != 0 && old_size < size) */ + pool_ctx *ctx = (pool_ctx *)ctx_ptr; + pool_chunk *cur = ((pool_chunk *)ptr) - 1, *prev, *next, *tmp; + + /* check size */ + if (unlikely(size >= ctx->size)) return NULL; + pool_size_align(&old_size); + pool_size_align(&size); + if (unlikely(old_size == size)) return ptr; + + /* find next and prev chunk */ + prev = NULL; + next = ctx->free_list; + while (next && next < cur) { + prev = next; + next = next->next; + } + + if ((u8 *)cur + cur->size == (u8 *)next && cur->size + next->size >= size) { + /* merge to higher chunk if they are contiguous */ + usize free_size = cur->size + next->size - size; + if (free_size > sizeof(pool_chunk) * 2) { + tmp = (pool_chunk *)(void *)((u8 *)cur + size); + if (prev) prev->next = tmp; + else ctx->free_list = tmp; + tmp->next = next->next; + tmp->size = free_size; + cur->size = size; + } else { + if (prev) prev->next = next->next; + else ctx->free_list = next->next; + cur->size += next->size; + } + return ptr; + } else { + /* fallback to malloc and memcpy */ + void *new_ptr = pool_malloc(ctx_ptr, size - sizeof(pool_chunk)); + if (new_ptr) { + memcpy(new_ptr, ptr, cur->size - sizeof(pool_chunk)); + pool_free(ctx_ptr, ptr); + } + return new_ptr; + } +} + +bool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, usize size) { + pool_chunk *chunk; + pool_ctx *ctx; + + if (unlikely(!alc)) return false; + *alc = YYJSON_NULL_ALC; + if (size < sizeof(pool_ctx) * 4) return false; + ctx = (pool_ctx *)mem_align_up(buf, sizeof(pool_ctx)); + if (unlikely(!ctx)) return false; + size -= (usize)((u8 *)ctx - (u8 *)buf); + size = size_align_down(size, sizeof(pool_ctx)); + + chunk = (pool_chunk *)(ctx + 1); + chunk->size = size - sizeof(pool_ctx); + chunk->next = NULL; + ctx->size = size; + ctx->free_list = chunk; + + alc->malloc = pool_malloc; + alc->realloc = pool_realloc; + alc->free = pool_free; + alc->ctx = (void *)ctx; + return true; +} + + + +/*============================================================================== + * MARK: - Dynamic Memory Allocator (Public) + * This allocator allocates memory on demand and does not immediately release + * unused memory. Instead, it places the unused memory into a freelist for + * potential reuse in the future. It is only when the entire allocator is + * destroyed that all previously allocated memory is released at once. + *============================================================================*/ + +/** memory chunk header */ +typedef struct dyn_chunk { + usize size; /* chunk size, include header */ + struct dyn_chunk *next; + /* char mem[]; flexible array member */ +} dyn_chunk; + +/** allocator ctx header */ +typedef struct { + dyn_chunk free_list; /* dummy header, sorted from small to large */ + dyn_chunk used_list; /* dummy header */ +} dyn_ctx; + +/** align up the input size to chunk size */ +static_inline bool dyn_size_align(usize *size) { + usize alc_size = *size + sizeof(dyn_chunk); + alc_size = size_align_up(alc_size, YYJSON_ALC_DYN_MIN_SIZE); + if (unlikely(alc_size < *size)) return false; /* overflow */ + *size = alc_size; + return true; +} + +/** remove a chunk from list (the chunk must already be in the list) */ +static_inline void dyn_chunk_list_remove(dyn_chunk *list, dyn_chunk *chunk) { + dyn_chunk *prev = list, *cur; + for (cur = prev->next; cur; cur = cur->next) { + if (cur == chunk) { + prev->next = cur->next; + cur->next = NULL; + return; + } + prev = cur; + } +} + +/** add a chunk to list header (the chunk must not be in the list) */ +static_inline void dyn_chunk_list_add(dyn_chunk *list, dyn_chunk *chunk) { + chunk->next = list->next; + list->next = chunk; +} + +static void *dyn_malloc(void *ctx_ptr, usize size) { + /* assert(size != 0) */ + const yyjson_alc def = YYJSON_DEFAULT_ALC; + dyn_ctx *ctx = (dyn_ctx *)ctx_ptr; + dyn_chunk *chunk, *prev; + if (unlikely(!dyn_size_align(&size))) return NULL; + + /* freelist is empty, create new chunk */ + if (!ctx->free_list.next) { + chunk = (dyn_chunk *)def.malloc(def.ctx, size); + if (unlikely(!chunk)) return NULL; + chunk->size = size; + chunk->next = NULL; + dyn_chunk_list_add(&ctx->used_list, chunk); + return (void *)(chunk + 1); + } + + /* find a large enough chunk, or resize the largest chunk */ + prev = &ctx->free_list; + while (true) { + chunk = prev->next; + if (chunk->size >= size) { /* enough size, reuse this chunk */ + prev->next = chunk->next; + dyn_chunk_list_add(&ctx->used_list, chunk); + return (void *)(chunk + 1); + } + if (!chunk->next) { /* resize the largest chunk */ + chunk = (dyn_chunk *)def.realloc(def.ctx, chunk, chunk->size, size); + if (unlikely(!chunk)) return NULL; + prev->next = NULL; + chunk->size = size; + dyn_chunk_list_add(&ctx->used_list, chunk); + return (void *)(chunk + 1); + } + prev = chunk; + } +} + +static void *dyn_realloc(void *ctx_ptr, void *ptr, + usize old_size, usize size) { + /* assert(ptr != NULL && size != 0 && old_size < size) */ + const yyjson_alc def = YYJSON_DEFAULT_ALC; + dyn_ctx *ctx = (dyn_ctx *)ctx_ptr; + dyn_chunk *new_chunk, *chunk = (dyn_chunk *)ptr - 1; + if (unlikely(!dyn_size_align(&size))) return NULL; + if (chunk->size >= size) return ptr; + + dyn_chunk_list_remove(&ctx->used_list, chunk); + new_chunk = (dyn_chunk *)def.realloc(def.ctx, chunk, chunk->size, size); + if (likely(new_chunk)) { + new_chunk->size = size; + chunk = new_chunk; + } + dyn_chunk_list_add(&ctx->used_list, chunk); + return new_chunk ? (void *)(new_chunk + 1) : NULL; +} + +static void dyn_free(void *ctx_ptr, void *ptr) { + /* assert(ptr != NULL) */ + dyn_ctx *ctx = (dyn_ctx *)ctx_ptr; + dyn_chunk *chunk = (dyn_chunk *)ptr - 1, *prev; + + dyn_chunk_list_remove(&ctx->used_list, chunk); + for (prev = &ctx->free_list; prev; prev = prev->next) { + if (!prev->next || prev->next->size >= chunk->size) { + chunk->next = prev->next; + prev->next = chunk; + break; + } + } +} + +yyjson_alc *yyjson_alc_dyn_new(void) { + const yyjson_alc def = YYJSON_DEFAULT_ALC; + usize hdr_len = sizeof(yyjson_alc) + sizeof(dyn_ctx); + yyjson_alc *alc = (yyjson_alc *)def.malloc(def.ctx, hdr_len); + dyn_ctx *ctx = (dyn_ctx *)(void *)(alc + 1); + if (unlikely(!alc)) return NULL; + alc->malloc = dyn_malloc; + alc->realloc = dyn_realloc; + alc->free = dyn_free; + alc->ctx = alc + 1; + memset(ctx, 0, sizeof(*ctx)); + return alc; +} + +void yyjson_alc_dyn_free(yyjson_alc *alc) { + const yyjson_alc def = YYJSON_DEFAULT_ALC; + dyn_ctx *ctx = (dyn_ctx *)(void *)(alc + 1); + dyn_chunk *chunk, *next; + if (unlikely(!alc)) return; + for (chunk = ctx->free_list.next; chunk; chunk = next) { + next = chunk->next; + def.free(def.ctx, chunk); + } + for (chunk = ctx->used_list.next; chunk; chunk = next) { + next = chunk->next; + def.free(def.ctx, chunk); + } + def.free(def.ctx, alc); +} + + + +/*============================================================================== + * MARK: - JSON Struct Utils (Public) + * These functions are used for creating, copying, releasing, and comparing + * JSON documents and values. They are widely used throughout this library. + *============================================================================*/ + +static_inline void unsafe_yyjson_str_pool_release(yyjson_str_pool *pool, + yyjson_alc *alc) { + yyjson_str_chunk *chunk = pool->chunks, *next; + while (chunk) { + next = chunk->next; + alc->free(alc->ctx, chunk); + chunk = next; + } +} + +static_inline void unsafe_yyjson_val_pool_release(yyjson_val_pool *pool, + yyjson_alc *alc) { + yyjson_val_chunk *chunk = pool->chunks, *next; + while (chunk) { + next = chunk->next; + alc->free(alc->ctx, chunk); + chunk = next; + } +} + +bool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool, + const yyjson_alc *alc, usize len) { + yyjson_str_chunk *chunk; + usize size, max_len; + + /* create a new chunk */ + max_len = USIZE_MAX - sizeof(yyjson_str_chunk); + if (unlikely(len > max_len)) return false; + size = len + sizeof(yyjson_str_chunk); + size = yyjson_max(pool->chunk_size, size); + chunk = (yyjson_str_chunk *)alc->malloc(alc->ctx, size); + if (unlikely(!chunk)) return false; + + /* insert the new chunk as the head of the linked list */ + chunk->next = pool->chunks; + chunk->chunk_size = size; + pool->chunks = chunk; + pool->cur = (char *)chunk + sizeof(yyjson_str_chunk); + pool->end = (char *)chunk + size; + + /* the next chunk is twice the size of the current one */ + size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max); + if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */ + pool->chunk_size = size; + return true; +} + +bool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool, + const yyjson_alc *alc, usize count) { + yyjson_val_chunk *chunk; + usize size, max_count; + + /* create a new chunk */ + max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1; + if (unlikely(count > max_count)) return false; + size = (count + 1) * sizeof(yyjson_mut_val); + size = yyjson_max(pool->chunk_size, size); + chunk = (yyjson_val_chunk *)alc->malloc(alc->ctx, size); + if (unlikely(!chunk)) return false; + + /* insert the new chunk as the head of the linked list */ + chunk->next = pool->chunks; + chunk->chunk_size = size; + pool->chunks = chunk; + pool->cur = (yyjson_mut_val *)(void *)((u8 *)chunk) + 1; + pool->end = (yyjson_mut_val *)(void *)((u8 *)chunk + size); + + /* the next chunk is twice the size of the current one */ + size = yyjson_min(pool->chunk_size * 2, pool->chunk_size_max); + if (size < pool->chunk_size) size = pool->chunk_size_max; /* overflow */ + pool->chunk_size = size; + return true; +} + +bool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc, size_t len) { + usize max_size = USIZE_MAX - sizeof(yyjson_str_chunk); + if (!doc || !len || len > max_size) return false; + doc->str_pool.chunk_size = len + sizeof(yyjson_str_chunk); + return true; +} + +bool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc, size_t count) { + usize max_count = USIZE_MAX / sizeof(yyjson_mut_val) - 1; + if (!doc || !count || count > max_count) return false; + doc->val_pool.chunk_size = (count + 1) * sizeof(yyjson_mut_val); + return true; +} + +void yyjson_mut_doc_free(yyjson_mut_doc *doc) { + if (doc) { + yyjson_alc alc = doc->alc; + memset(&doc->alc, 0, sizeof(alc)); + unsafe_yyjson_str_pool_release(&doc->str_pool, &alc); + unsafe_yyjson_val_pool_release(&doc->val_pool, &alc); + alc.free(alc.ctx, doc); + } +} + +yyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc) { + yyjson_mut_doc *doc; + if (!alc) alc = &YYJSON_DEFAULT_ALC; + doc = (yyjson_mut_doc *)alc->malloc(alc->ctx, sizeof(yyjson_mut_doc)); + if (!doc) return NULL; + memset(doc, 0, sizeof(yyjson_mut_doc)); + + doc->alc = *alc; + doc->str_pool.chunk_size = YYJSON_MUT_DOC_STR_POOL_INIT_SIZE; + doc->str_pool.chunk_size_max = YYJSON_MUT_DOC_STR_POOL_MAX_SIZE; + doc->val_pool.chunk_size = YYJSON_MUT_DOC_VAL_POOL_INIT_SIZE; + doc->val_pool.chunk_size_max = YYJSON_MUT_DOC_VAL_POOL_MAX_SIZE; + return doc; +} + +yyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc, const yyjson_alc *alc) { + yyjson_mut_doc *m_doc; + yyjson_mut_val *m_val; + + if (!doc || !doc->root) return NULL; + m_doc = yyjson_mut_doc_new(alc); + if (!m_doc) return NULL; + m_val = yyjson_val_mut_copy(m_doc, doc->root); + if (!m_val) { + yyjson_mut_doc_free(m_doc); + return NULL; + } + yyjson_mut_doc_set_root(m_doc, m_val); + return m_doc; +} + +yyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc, + const yyjson_alc *alc) { + yyjson_mut_doc *m_doc; + yyjson_mut_val *m_val; + + if (!doc) return NULL; + if (!doc->root) return yyjson_mut_doc_new(alc); + + m_doc = yyjson_mut_doc_new(alc); + if (!m_doc) return NULL; + m_val = yyjson_mut_val_mut_copy(m_doc, doc->root); + if (!m_val) { + yyjson_mut_doc_free(m_doc); + return NULL; + } + yyjson_mut_doc_set_root(m_doc, m_val); + return m_doc; +} + +yyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *m_doc, + yyjson_val *i_vals) { + /* + The immutable object or array stores all sub-values in a contiguous memory, + We copy them to another contiguous memory as mutable values, + then reconnect the mutable values with the original relationship. + */ + usize i_vals_len; + yyjson_mut_val *m_vals, *m_val; + yyjson_val *i_val, *i_end; + + if (!m_doc || !i_vals) return NULL; + i_end = unsafe_yyjson_get_next(i_vals); + i_vals_len = (usize)(unsafe_yyjson_get_next(i_vals) - i_vals); + m_vals = unsafe_yyjson_mut_val(m_doc, i_vals_len); + if (!m_vals) return NULL; + i_val = i_vals; + m_val = m_vals; + + for (; i_val < i_end; i_val++, m_val++) { + yyjson_type type = unsafe_yyjson_get_type(i_val); + m_val->tag = i_val->tag; + m_val->uni.u64 = i_val->uni.u64; + if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { + const char *str = i_val->uni.str; + usize str_len = unsafe_yyjson_get_len(i_val); + m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len); + if (!m_val->uni.str) return NULL; + } else if (type == YYJSON_TYPE_ARR) { + usize len = unsafe_yyjson_get_len(i_val); + if (len > 0) { + yyjson_val *ii_val = i_val + 1, *ii_next; + yyjson_mut_val *mm_val = m_val + 1, *mm_ctn = m_val, *mm_next; + while (len-- > 1) { + ii_next = unsafe_yyjson_get_next(ii_val); + mm_next = mm_val + (ii_next - ii_val); + mm_val->next = mm_next; + ii_val = ii_next; + mm_val = mm_next; + } + mm_val->next = mm_ctn + 1; + mm_ctn->uni.ptr = mm_val; + } + } else if (type == YYJSON_TYPE_OBJ) { + usize len = unsafe_yyjson_get_len(i_val); + if (len > 0) { + yyjson_val *ii_key = i_val + 1, *ii_nextkey; + yyjson_mut_val *mm_key = m_val + 1, *mm_ctn = m_val; + yyjson_mut_val *mm_nextkey; + while (len-- > 1) { + ii_nextkey = unsafe_yyjson_get_next(ii_key + 1); + mm_nextkey = mm_key + (ii_nextkey - ii_key); + mm_key->next = mm_key + 1; + mm_key->next->next = mm_nextkey; + ii_key = ii_nextkey; + mm_key = mm_nextkey; + } + mm_key->next = mm_key + 1; + mm_key->next->next = mm_ctn + 1; + mm_ctn->uni.ptr = mm_key; + } + } + } + return m_vals; +} + +static yyjson_mut_val *unsafe_yyjson_mut_val_mut_copy(yyjson_mut_doc *m_doc, + yyjson_mut_val *m_vals) { + /* + The mutable object or array stores all sub-values in a circular linked + list, so we can traverse them in the same loop. The traversal starts from + the last item, continues with the first item in a list, and ends with the + second to last item, which needs to be linked to the last item to close the + circle. + */ + yyjson_mut_val *m_val = unsafe_yyjson_mut_val(m_doc, 1); + if (unlikely(!m_val)) return NULL; + m_val->tag = m_vals->tag; + + switch (unsafe_yyjson_get_type(m_vals)) { + case YYJSON_TYPE_OBJ: + case YYJSON_TYPE_ARR: + if (unsafe_yyjson_get_len(m_vals) > 0) { + yyjson_mut_val *last = (yyjson_mut_val *)m_vals->uni.ptr; + yyjson_mut_val *next = last->next, *prev; + prev = unsafe_yyjson_mut_val_mut_copy(m_doc, last); + if (!prev) return NULL; + m_val->uni.ptr = (void *)prev; + while (next != last) { + prev->next = unsafe_yyjson_mut_val_mut_copy(m_doc, next); + if (!prev->next) return NULL; + prev = prev->next; + next = next->next; + } + prev->next = (yyjson_mut_val *)m_val->uni.ptr; + } + break; + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: { + const char *str = m_vals->uni.str; + usize str_len = unsafe_yyjson_get_len(m_vals); + m_val->uni.str = unsafe_yyjson_mut_strncpy(m_doc, str, str_len); + if (!m_val->uni.str) return NULL; + break; + } + default: + m_val->uni = m_vals->uni; + break; + } + return m_val; +} + +yyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc, + yyjson_mut_val *val) { + if (doc && val) return unsafe_yyjson_mut_val_mut_copy(doc, val); + return NULL; +} + +/* Count the number of values and the total length of the strings. */ +static void yyjson_mut_stat(yyjson_mut_val *val, + usize *val_sum, usize *str_sum) { + yyjson_type type = unsafe_yyjson_get_type(val); + *val_sum += 1; + if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) { + yyjson_mut_val *child = (yyjson_mut_val *)val->uni.ptr; + usize len = unsafe_yyjson_get_len(val), i; + len <<= (u8)(type == YYJSON_TYPE_OBJ); + *val_sum += len; + for (i = 0; i < len; i++) { + yyjson_type stype = unsafe_yyjson_get_type(child); + if (stype == YYJSON_TYPE_STR || stype == YYJSON_TYPE_RAW) { + *str_sum += unsafe_yyjson_get_len(child) + 1; + } else if (stype == YYJSON_TYPE_ARR || stype == YYJSON_TYPE_OBJ) { + yyjson_mut_stat(child, val_sum, str_sum); + *val_sum -= 1; + } + child = child->next; + } + } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { + *str_sum += unsafe_yyjson_get_len(val) + 1; + } +} + +/* Copy mutable values to immutable value pool. */ +static usize yyjson_imut_copy(yyjson_val **val_ptr, char **buf_ptr, + yyjson_mut_val *mval) { + yyjson_val *val = *val_ptr; + yyjson_type type = unsafe_yyjson_get_type(mval); + if (type == YYJSON_TYPE_ARR || type == YYJSON_TYPE_OBJ) { + yyjson_mut_val *child = (yyjson_mut_val *)mval->uni.ptr; + usize len = unsafe_yyjson_get_len(mval), i; + usize val_sum = 1; + if (type == YYJSON_TYPE_OBJ) { + if (len) child = child->next->next; + len <<= 1; + } else { + if (len) child = child->next; + } + *val_ptr = val + 1; + for (i = 0; i < len; i++) { + val_sum += yyjson_imut_copy(val_ptr, buf_ptr, child); + child = child->next; + } + val->tag = mval->tag; + val->uni.ofs = val_sum * sizeof(yyjson_val); + return val_sum; + } else if (type == YYJSON_TYPE_STR || type == YYJSON_TYPE_RAW) { + char *buf = *buf_ptr; + usize len = unsafe_yyjson_get_len(mval); + memcpy((void *)buf, (const void *)mval->uni.str, len); + buf[len] = '\0'; + val->tag = mval->tag; + val->uni.str = buf; + *val_ptr = val + 1; + *buf_ptr = buf + len + 1; + return 1; + } else { + val->tag = mval->tag; + val->uni = mval->uni; + *val_ptr = val + 1; + return 1; + } +} + +yyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *mdoc, + const yyjson_alc *alc) { + if (!mdoc) return NULL; + return yyjson_mut_val_imut_copy(mdoc->root, alc); +} + +yyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *mval, + const yyjson_alc *alc) { + usize val_num = 0, str_sum = 0, hdr_size, buf_size; + yyjson_doc *doc = NULL; + yyjson_val *val_hdr = NULL; + + /* This value should be NULL here. Setting a non-null value suppresses + warning from the clang analyzer. */ + char *str_hdr = (char *)(void *)&str_sum; + if (!mval) return NULL; + if (!alc) alc = &YYJSON_DEFAULT_ALC; + + /* traverse the input value to get pool size */ + yyjson_mut_stat(mval, &val_num, &str_sum); + + /* create doc and val pool */ + hdr_size = size_align_up(sizeof(yyjson_doc), sizeof(yyjson_val)); + buf_size = hdr_size + val_num * sizeof(yyjson_val); + doc = (yyjson_doc *)alc->malloc(alc->ctx, buf_size); + if (!doc) return NULL; + memset(doc, 0, sizeof(yyjson_doc)); + val_hdr = (yyjson_val *)(void *)((char *)(void *)doc + hdr_size); + doc->root = val_hdr; + doc->alc = *alc; + + /* create str pool */ + if (str_sum > 0) { + str_hdr = (char *)alc->malloc(alc->ctx, str_sum); + doc->str_pool = str_hdr; + if (!str_hdr) { + alc->free(alc->ctx, (void *)doc); + return NULL; + } + } + + /* copy vals and strs */ + doc->val_read = yyjson_imut_copy(&val_hdr, &str_hdr, mval); + doc->dat_read = str_sum + 1; + return doc; +} + +static_inline bool unsafe_yyjson_num_equals(void *lhs, void *rhs) { + yyjson_val_uni *luni = &((yyjson_val *)lhs)->uni; + yyjson_val_uni *runi = &((yyjson_val *)rhs)->uni; + yyjson_subtype lt = unsafe_yyjson_get_subtype(lhs); + yyjson_subtype rt = unsafe_yyjson_get_subtype(rhs); + if (lt == rt) return luni->u64 == runi->u64; + if (lt == YYJSON_SUBTYPE_SINT && rt == YYJSON_SUBTYPE_UINT) { + return luni->i64 >= 0 && luni->u64 == runi->u64; + } + if (lt == YYJSON_SUBTYPE_UINT && rt == YYJSON_SUBTYPE_SINT) { + return runi->i64 >= 0 && luni->u64 == runi->u64; + } + return false; +} + +static_inline bool unsafe_yyjson_str_equals(void *lhs, void *rhs) { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + return !memcmp(unsafe_yyjson_get_str(lhs), + unsafe_yyjson_get_str(rhs), len); +} + +bool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) { + yyjson_type type = unsafe_yyjson_get_type(lhs); + if (type != unsafe_yyjson_get_type(rhs)) return false; + + switch (type) { + case YYJSON_TYPE_OBJ: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + yyjson_obj_iter iter; + yyjson_obj_iter_init(rhs, &iter); + lhs = unsafe_yyjson_get_first(lhs); + while (len-- > 0) { + rhs = yyjson_obj_iter_getn(&iter, lhs->uni.str, + unsafe_yyjson_get_len(lhs)); + if (!rhs) return false; + if (!unsafe_yyjson_equals(lhs + 1, rhs)) return false; + lhs = unsafe_yyjson_get_next(lhs + 1); + } + } + /* yyjson allows duplicate keys, so the check may be inaccurate */ + return true; + } + + case YYJSON_TYPE_ARR: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + lhs = unsafe_yyjson_get_first(lhs); + rhs = unsafe_yyjson_get_first(rhs); + while (len-- > 0) { + if (!unsafe_yyjson_equals(lhs, rhs)) return false; + lhs = unsafe_yyjson_get_next(lhs); + rhs = unsafe_yyjson_get_next(rhs); + } + } + return true; + } + + case YYJSON_TYPE_NUM: + return unsafe_yyjson_num_equals(lhs, rhs); + + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: + return unsafe_yyjson_str_equals(lhs, rhs); + + case YYJSON_TYPE_NULL: + case YYJSON_TYPE_BOOL: + return lhs->tag == rhs->tag; + + default: + return false; + } +} + +bool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs, yyjson_mut_val *rhs) { + yyjson_type type = unsafe_yyjson_get_type(lhs); + if (type != unsafe_yyjson_get_type(rhs)) return false; + + switch (type) { + case YYJSON_TYPE_OBJ: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + yyjson_mut_obj_iter iter; + yyjson_mut_obj_iter_init(rhs, &iter); + lhs = (yyjson_mut_val *)lhs->uni.ptr; + while (len-- > 0) { + rhs = yyjson_mut_obj_iter_getn(&iter, lhs->uni.str, + unsafe_yyjson_get_len(lhs)); + if (!rhs) return false; + if (!unsafe_yyjson_mut_equals(lhs->next, rhs)) return false; + lhs = lhs->next->next; + } + } + /* yyjson allows duplicate keys, so the check may be inaccurate */ + return true; + } + + case YYJSON_TYPE_ARR: { + usize len = unsafe_yyjson_get_len(lhs); + if (len != unsafe_yyjson_get_len(rhs)) return false; + if (len > 0) { + lhs = (yyjson_mut_val *)lhs->uni.ptr; + rhs = (yyjson_mut_val *)rhs->uni.ptr; + while (len-- > 0) { + if (!unsafe_yyjson_mut_equals(lhs, rhs)) return false; + lhs = lhs->next; + rhs = rhs->next; + } + } + return true; + } + + case YYJSON_TYPE_NUM: + return unsafe_yyjson_num_equals(lhs, rhs); + + case YYJSON_TYPE_RAW: + case YYJSON_TYPE_STR: + return unsafe_yyjson_str_equals(lhs, rhs); + + case YYJSON_TYPE_NULL: + case YYJSON_TYPE_BOOL: + return lhs->tag == rhs->tag; + + default: + return false; + } +} + +bool yyjson_locate_pos(const char *str, size_t len, size_t pos, + size_t *line, size_t *col, size_t *chr) { + usize line_sum = 0, line_pos = 0, chr_sum = 0; + const u8 *cur = (const u8 *)str; + const u8 *end = cur + pos; + + if (!str || pos > len) { + if (line) *line = 0; + if (col) *col = 0; + if (chr) *chr = 0; + return false; + } + + if (pos >= 3 && is_utf8_bom(cur)) cur += 3; /* don't count BOM */ + while (cur < end) { + u8 c = *cur; + chr_sum += 1; + if (likely(c < 0x80)) { /* 0xxxxxxx (0x00-0x7F) ASCII */ + if (c == '\n') { + line_sum += 1; + line_pos = chr_sum; + } + cur += 1; + } + else if (c < 0xC0) cur += 1; /* 10xxxxxx (0x80-0xBF) Invalid */ + else if (c < 0xE0) cur += 2; /* 110xxxxx (0xC0-0xDF) 2-byte UTF-8 */ + else if (c < 0xF0) cur += 3; /* 1110xxxx (0xE0-0xEF) 3-byte UTF-8 */ + else if (c < 0xF8) cur += 4; /* 11110xxx (0xF0-0xF7) 4-byte UTF-8 */ + else cur += 1; /* 11111xxx (0xF8-0xFF) Invalid */ + } + if (line) *line = line_sum + 1; + if (col) *col = chr_sum - line_pos + 1; + if (chr) *chr = chr_sum; + return true; +} + + + +#if !YYJSON_DISABLE_READER /* reader begin */ + +/* Check read flag, avoids `always false` warning when disabled. */ +#define has_flg(_flg) unlikely(has_rflag(flg, YYJSON_READ_##_flg, 0)) +#define has_allow(_flg) unlikely(has_rflag(flg, YYJSON_READ_ALLOW_##_flg, 1)) +#define YYJSON_READ_ALLOW_TRIVIA (YYJSON_READ_ALLOW_COMMENTS | \ + YYJSON_READ_ALLOW_EXT_WHITESPACE) +static_inline bool has_rflag(yyjson_read_flag flg, yyjson_read_flag chk, + bool non_standard) { +#if YYJSON_DISABLE_NON_STANDARD + if (non_standard) return false; +#endif + return (flg & chk) != 0; +} + + + +/*============================================================================== + * MARK: - JSON Reader Utils (Private) + * These functions are used by JSON reader to read literals and comments. + *============================================================================*/ + +/** Read `true` literal, `*ptr[0]` should be `t`. */ +static_inline bool read_true(u8 **ptr, yyjson_val *val) { + u8 *cur = *ptr; + if (likely(byte_match_4(cur, "true"))) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + *ptr = cur + 4; + return true; + } + return false; +} + +/** Read `false` literal, `*ptr[0]` should be `f`. */ +static_inline bool read_false(u8 **ptr, yyjson_val *val) { + u8 *cur = *ptr; + if (likely(byte_match_4(cur + 1, "alse"))) { + val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + *ptr = cur + 5; + return true; + } + return false; +} + +/** Read `null` literal, `*ptr[0]` should be `n`. */ +static_inline bool read_null(u8 **ptr, yyjson_val *val) { + u8 *cur = *ptr; + if (likely(byte_match_4(cur, "null"))) { + val->tag = YYJSON_TYPE_NULL; + *ptr = cur + 4; + return true; + } + return false; +} + +/** Read `Inf` or `Infinity` literal (ignoring case). */ +static_inline bool read_inf(u8 **ptr, u8 **pre, + yyjson_read_flag flg, yyjson_val *val) { + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + bool sign = (*cur == '-'); + if (*cur == '+' && !has_allow(EXT_NUMBER)) return false; + cur += char_is_sign(*cur); + if (char_to_lower(cur[0]) == 'i' && + char_to_lower(cur[1]) == 'n' && + char_to_lower(cur[2]) == 'f') { + if (char_to_lower(cur[3]) == 'i') { + if (char_to_lower(cur[4]) == 'n' && + char_to_lower(cur[5]) == 'i' && + char_to_lower(cur[6]) == 't' && + char_to_lower(cur[7]) == 'y') { + cur += 8; + } else { + return false; + } + } else { + cur += 3; + } + *end = cur; + if (has_flg(NUMBER_AS_RAW)) { + **pre = '\0'; /* add null-terminator for previous raw string */ + *pre = cur; /* save end position for current raw string */ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = (const char *)hdr; + } else { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.u64 = f64_bits_inf(sign); + } + return true; + } + return false; +} + +/** Read `NaN` literal (ignoring case). */ +static_inline bool read_nan(u8 **ptr, u8 **pre, + yyjson_read_flag flg, yyjson_val *val) { + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + bool sign = (*cur == '-'); + if (*cur == '+' && !has_allow(EXT_NUMBER)) return false; + cur += char_is_sign(*cur); + if (char_to_lower(cur[0]) == 'n' && + char_to_lower(cur[1]) == 'a' && + char_to_lower(cur[2]) == 'n') { + cur += 3; + *end = cur; + if (has_flg(NUMBER_AS_RAW)) { + **pre = '\0'; /* add null-terminator for previous raw string */ + *pre = cur; /* save end position for current raw string */ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = (const char *)hdr; + } else { + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + val->uni.u64 = f64_bits_nan(sign); + } + return true; + } + return false; +} + +/** Read `Inf`, `Infinity` or `NaN` literal (ignoring case). */ +static_inline bool read_inf_or_nan(u8 **ptr, u8 **pre, + yyjson_read_flag flg, yyjson_val *val) { + if (read_inf(ptr, pre, flg, val)) return true; + if (read_nan(ptr, pre, flg, val)) return true; + return false; +} + +/** Read a JSON number as raw string. */ +static_noinline bool read_num_raw(u8 **ptr, u8 **pre, yyjson_read_flag flg, + yyjson_val *val, const char **msg) { +#define return_err(_pos, _msg) do { \ + *msg = _msg; *end = _pos; return false; \ +} while (false) + +#define return_raw() do { \ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ + val->uni.str = (const char *)hdr; \ + **pre = '\0'; *pre = cur; *end = cur; return true; \ +} while (false) + + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + + /* skip sign */ + cur += (*cur == '-'); + + /* read first digit, check leading zero */ + while (unlikely(!char_is_digit(*cur))) { + if (has_allow(EXT_NUMBER)) { + if (*cur == '+' && cur == hdr) { /* leading `+` sign */ + cur++; + continue; + } + if (*cur == '.' && char_is_digit(cur[1])) { /* e.g. '.123' */ + goto read_double; + } + } + if (has_allow(INF_AND_NAN)) { + if (read_inf_or_nan(ptr, pre, flg, val)) return true; + } + return_err(cur, "no digit after sign"); + } + + /* read integral part */ + if (*cur == '0') { + cur++; + if (unlikely(char_is_digit(*cur))) { + return_err(cur - 1, "number with leading zero is not allowed"); + } + if (!char_is_fp(*cur)) { + if (has_allow(EXT_NUMBER) && char_to_lower(*cur) == 'x') { /* hex */ + if (!char_is_hex(*++cur)) return_err(cur, "invalid hex number"); + while(char_is_hex(*cur)) cur++; + } + return_raw(); + } + } else { + while (char_is_digit(*cur)) cur++; + if (!char_is_fp(*cur)) return_raw(); + } + +read_double: + /* read fraction part */ + if (*cur == '.') { + cur++; + if (!char_is_digit(*cur)) { + if (has_allow(EXT_NUMBER)) { + if (!char_is_exp(*cur)) return_raw(); + } else { + return_err(cur, "no digit after decimal point"); + } + } + while (char_is_digit(*cur)) cur++; + } + + /* read exponent part */ + if (char_is_exp(*cur)) { + cur += 1 + char_is_sign(cur[1]); + if (!char_is_digit(*cur++)) { + return_err(cur, "no digit after exponent sign"); + } + while (char_is_digit(*cur)) cur++; + } + + return_raw(); + +#undef return_err +#undef return_raw +} + +/** Read a hex number. */ +static_noinline bool read_num_hex(u8 **ptr, u8 **pre, yyjson_read_flag flg, + yyjson_val *val, const char **msg) { + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + u64 sig = 0, i = 0; + bool sign; + + /* skip sign and '0x' */ + sign = (*cur == '-'); + cur += (*cur == '-' || *cur == '+') + 2; + + /* read hex */ + for(; i < 16; i++) { + u8 c = hex_conv_table[cur[i]]; + if (c == 0xF0) break; + sig <<= 4; + sig |= c; + } + + /* check error */ + if (unlikely(i == 0)) { + *msg = "invalid hex number"; + return false; + } + + /* check overflow */ + if (unlikely(i == 16)) { + if (char_is_hex(cur[16]) || (sign && sig > ((u64)1 << 63))) { + if (!has_flg(BIGNUM_AS_RAW)) { + *msg = "hex number overflow"; + return false; + } + cur += 16; + while (char_is_hex(*cur)) cur++; + **pre = '\0'; + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; + val->uni.str = (const char *)hdr; + *pre = cur; *end = cur; + return true; + } + } + + val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); + val->uni.u64 = (u64)(sign ? (u64)(~(sig) + 1) : (u64)(sig)); + *end = cur + i; + return true; +} + +/** + Skip trivia (whitespace and comments). + This function should be used only when `char_is_trivia()` returns true. + @param ptr (inout) Input current position, output end position. + @param eof JSON end position. + @param flg JSON read flags. + @return true if at least one character was skipped. + false if no characters were skipped, + or if a multi-line comment is unterminated; + in the latter case, `ptr` will be set to `eof`. + */ +static_noinline bool skip_trivia(u8 **ptr, u8 *eof, yyjson_read_flag flg) { + u8 *hdr = *ptr, *cur = *ptr; + usize len; + + while (cur < eof) { + u8 *loop_begin = cur; + + /* skip standard whitespace */ + while(char_is_space(*cur)) cur++; + + /* skip extended whitespace */ + if (has_allow(EXT_WHITESPACE)) { + while (char_is_space_ext(*cur)) { + cur += (len = ext_space_len(cur)); + if (!len) break; + } + } + + /* skip comment, do not validate encoding */ + if (has_allow(COMMENTS) && cur[0] == '/') { + if (cur[1] == '/') { /* single-line comment */ + cur += 2; + if (has_allow(EXT_WHITESPACE)) { + while (cur < eof) { + if (char_is_eol_ext(*cur)) { + cur += (len = ext_eol_len(cur)); + if (len) break; + } + cur++; + } + } else { + while (cur < eof && !char_is_eol(*cur)) cur++; + } + } else if (cur[1] == '*') { /* multi-line comment */ + cur += 2; + while (!byte_match_2(cur, "*/") && cur < eof) cur++; + if (cur == eof) { + *ptr = eof; + return false; /* unclosed comment */ + } + cur += 2; + } + } + if (cur == loop_begin) break; + } + *ptr = cur; + return cur > hdr; +} + +/** + Check truncated UTF-8 character. + Return true if `cur` starts a valid UTF-8 sequence that is truncated. + */ +static bool is_truncated_utf8(u8 *cur, u8 *eof) { + u8 c0, c1, c2; + usize len = (usize)(eof - cur); + if (cur >= eof || len >= 4) return false; + c0 = cur[0]; c1 = cur[1]; c2 = cur[2]; + /* 1-byte UTF-8, not truncated */ + if (c0 < 0x80) return false; + if (len == 1) { + /* 2-byte UTF-8, truncated */ + if ((c0 & 0xE0) == 0xC0 && (c0 & 0x1E) != 0x00) return true; + /* 3-byte UTF-8, truncated */ + if ((c0 & 0xF0) == 0xE0) return true; + /* 4-byte UTF-8, truncated */ + if ((c0 & 0xF8) == 0xF0 && (c0 & 0x07) <= 0x04) return true; + } else if (len == 2) { + /* 3-byte UTF-8, truncated */ + if ((c0 & 0xF0) == 0xE0 && (c1 & 0xC0) == 0x80) { + u8 t = (u8)(((c0 & 0x0F) << 1) | ((c1 & 0x20) >> 5)); + return 0x01 <= t && t != 0x1B; + } + /* 4-byte UTF-8, truncated */ + if ((c0 & 0xF8) == 0xF0 && (c1 & 0xC0) == 0x80) { + u8 t = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4)); + return 0x01 <= t && t <= 0x10; + } + } else if (len == 3) { + /* 4 bytes UTF-8, truncated */ + if ((c0 & 0xF8) == 0xF0 && (c1 & 0xC0) == 0x80 && (c2 & 0xC0) == 0x80) { + u8 t = (u8)(((c0 & 0x07) << 2) | ((c1 & 0x30) >> 4)); + return 0x01 <= t && t <= 0x10; + } + } + return false; +} + +/** + Check truncated string. + Returns true if `cur` match `str` but is truncated. + The `str` should be lowercase ASCII letters. + */ +static bool is_truncated_str(u8 *cur, u8 *eof, const char *str, + bool case_sensitive) { + usize len = strlen(str); + if (cur + len <= eof || eof <= cur) return false; + if (case_sensitive) { + return memcmp(cur, str, (usize)(eof - cur)) == 0; + } + for (; cur < eof; cur++, str++) { + if (char_to_lower(*cur) != *(const u8 *)str) return false; + } + return true; +} + +/** + Check truncated JSON on parsing errors. + Returns true if the input is valid but truncated. + */ +static_noinline bool is_truncated_end(u8 *hdr, u8 *cur, u8 *eof, + yyjson_read_code code, + yyjson_read_flag flg) { + if (cur >= eof) return true; + if (code == YYJSON_READ_ERROR_LITERAL) { + if (is_truncated_str(cur, eof, "true", true) || + is_truncated_str(cur, eof, "false", true) || + is_truncated_str(cur, eof, "null", true)) { + return true; + } + } + if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER || + code == YYJSON_READ_ERROR_INVALID_NUMBER || + code == YYJSON_READ_ERROR_LITERAL) { + if (has_allow(INF_AND_NAN)) { + if (*cur == '-') cur++; + if (is_truncated_str(cur, eof, "infinity", false) || + is_truncated_str(cur, eof, "nan", false)) { + return true; + } + } + } + if (code == YYJSON_READ_ERROR_UNEXPECTED_CONTENT) { + if (has_allow(INF_AND_NAN)) { + if (hdr + 3 <= cur && + is_truncated_str(cur - 3, eof, "infinity", false)) { + return true; /* e.g. infin would be read as inf + in */ + } + } + } + if (code == YYJSON_READ_ERROR_INVALID_STRING) { + usize len = (usize)(eof - cur); + + /* unicode escape sequence */ + if (*cur == '\\') { + if (len == 1) return true; + if (len <= 5) { + if (*++cur != 'u') return false; + for (++cur; cur < eof; cur++) { + if (!char_is_hex(*cur)) return false; + } + return true; + } else if (len <= 11) { + /* incomplete surrogate pair? */ + u16 hi; + if (*++cur != 'u') return false; + if (!hex_load_4(++cur, &hi)) return false; + if ((hi & 0xF800) != 0xD800) return false; + cur += 4; + if (cur >= eof) return true; + /* valid low surrogate is DC00...DFFF */ + if (*cur != '\\') return false; + if (++cur >= eof) return true; + if (*cur != 'u') return false; + if (++cur >= eof) return true; + if (*cur != 'd' && *cur != 'D') return false; + if (++cur >= eof) return true; + if ((*cur < 'c' || *cur > 'f') && (*cur < 'C' || *cur > 'F')) + return false; + if (++cur >= eof) return true; + if (!char_is_hex(*cur)) return false; + return true; + } + return false; + } + + /* 2 to 4 bytes UTF-8 */ + if (is_truncated_utf8(cur, eof)) { + return true; + } + } + if (has_allow(COMMENTS)) { + if (code == YYJSON_READ_ERROR_INVALID_COMMENT) { + /* unclosed multiline comment */ + return true; + } + if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER && + *cur == '/' && cur + 1 == eof) { + /* truncated beginning of comment */ + return true; + } + } + if (code == YYJSON_READ_ERROR_UNEXPECTED_CHARACTER && + has_allow(BOM)) { + /* truncated UTF-8 BOM */ + usize len = (usize)(eof - cur); + if (cur == hdr && len < 3 && !memcmp(hdr, "\xEF\xBB\xBF", len)) { + return true; + } + } + return false; +} + + + +#if !YYJSON_DISABLE_FAST_FP_CONV /* FP_READER */ + +/*============================================================================== + * MARK: - BigInt For Floating Point Number Reader (Private) + * + * The bigint algorithm is used by floating-point number reader to get correctly + * rounded result for numbers with lots of digits. This part of code is rarely + * used for common numbers. + *============================================================================*/ + +/** Unsigned arbitrarily large integer */ +typedef struct bigint { + u32 used; /* used chunks count, should not be 0 */ + u64 bits[64]; /* chunks (58 is enough here) */ +} bigint; + +/** + Evaluate 'big += val'. + @param big A big number (can be 0). + @param val An unsigned integer (can be 0). + */ +static_inline void bigint_add_u64(bigint *big, u64 val) { + u32 idx, max; + u64 num = big->bits[0]; + u64 add = num + val; + big->bits[0] = add; + if (likely((add >= num) || (add >= val))) return; + for ((void)(idx = 1), max = big->used; idx < max; idx++) { + if (likely(big->bits[idx] != U64_MAX)) { + big->bits[idx] += 1; + return; + } + big->bits[idx] = 0; + } + big->bits[big->used++] = 1; +} + +/** + Evaluate 'big *= val'. + @param big A big number (can be 0). + @param val An unsigned integer (cannot be 0). + */ +static_inline void bigint_mul_u64(bigint *big, u64 val) { + u32 idx = 0, max = big->used; + u64 hi, lo, carry = 0; + for (; idx < max; idx++) { + if (big->bits[idx]) break; + } + for (; idx < max; idx++) { + u128_mul_add(big->bits[idx], val, carry, &hi, &lo); + big->bits[idx] = lo; + carry = hi; + } + if (carry) big->bits[big->used++] = carry; +} + +/** + Evaluate 'big *= 2^exp'. + @param big A big number (can be 0). + @param exp An exponent integer (can be 0). + */ +static_inline void bigint_mul_pow2(bigint *big, u32 exp) { + u32 shft = exp % 64; + u32 move = exp / 64; + u32 idx = big->used; + if (unlikely(shft == 0)) { + for (; idx > 0; idx--) { + big->bits[idx + move - 1] = big->bits[idx - 1]; + } + big->used += move; + while (move) big->bits[--move] = 0; + } else { + big->bits[idx] = 0; + for (; idx > 0; idx--) { + u64 num = big->bits[idx] << shft; + num |= big->bits[idx - 1] >> (64 - shft); + big->bits[idx + move] = num; + } + big->bits[move] = big->bits[0] << shft; + big->used += move + (big->bits[big->used + move] > 0); + while (move) big->bits[--move] = 0; + } +} + +/** + Evaluate 'big *= 10^exp'. + @param big A big number (can be 0). + @param exp An exponent integer (cannot be 0). + */ +static_inline void bigint_mul_pow10(bigint *big, i32 exp) { + for (; exp >= U64_POW10_MAX_EXACT_EXP; exp -= U64_POW10_MAX_EXACT_EXP) { + bigint_mul_u64(big, u64_pow10_table[U64_POW10_MAX_EXACT_EXP]); + } + if (exp) { + bigint_mul_u64(big, u64_pow10_table[exp]); + } +} + +/** + Compare two bigint. + @return -1 if 'a < b', +1 if 'a > b', 0 if 'a == b'. + */ +static_inline i32 bigint_cmp(bigint *a, bigint *b) { + u32 idx = a->used; + if (a->used < b->used) return -1; + if (a->used > b->used) return +1; + while (idx-- > 0) { + u64 av = a->bits[idx]; + u64 bv = b->bits[idx]; + if (av < bv) return -1; + if (av > bv) return +1; + } + return 0; +} + +/** + Evaluate 'big = val'. + @param big A big number (can be 0). + @param val An unsigned integer (can be 0). + */ +static_inline void bigint_set_u64(bigint *big, u64 val) { + big->used = 1; + big->bits[0] = val; +} + +/** Set a bigint with floating point number string. */ +static_noinline void bigint_set_buf(bigint *big, u64 sig, i32 *exp, + u8 *sig_cut, u8 *sig_end, u8 *dot_pos) { + + if (unlikely(!sig_cut)) { + /* no digit cut, set significant part only */ + bigint_set_u64(big, sig); + return; + + } else { + /* some digits were cut, read them from 'sig_cut' to 'sig_end' */ + u8 *hdr = sig_cut; + u8 *cur = hdr; + u32 len = 0; + u64 val = 0; + bool dig_big_cut = false; + bool has_dot = (hdr < dot_pos) & (dot_pos < sig_end); + u32 dig_len_total = U64_SAFE_DIG + (u32)(sig_end - hdr) - has_dot; + + sig -= (*sig_cut >= '5'); /* sig was rounded before */ + if (dig_len_total > F64_MAX_DEC_DIG) { + dig_big_cut = true; + sig_end -= dig_len_total - (F64_MAX_DEC_DIG + 1); + sig_end -= (dot_pos + 1 == sig_end); + dig_len_total = (F64_MAX_DEC_DIG + 1); + } + *exp -= (i32)dig_len_total - U64_SAFE_DIG; + + big->used = 1; + big->bits[0] = sig; + while (cur < sig_end) { + if (likely(cur != dot_pos)) { + val = val * 10 + (u8)(*cur++ - '0'); + len++; + if (unlikely(cur == sig_end && dig_big_cut)) { + /* The last digit must be non-zero, */ + /* set it to '1' for correct rounding. */ + val = val - (val % 10) + 1; + } + if (len == U64_SAFE_DIG || cur == sig_end) { + bigint_mul_pow10(big, (i32)len); + bigint_add_u64(big, val); + val = 0; + len = 0; + } + } else { + cur++; + } + } + } +} + + + +/*============================================================================== + * MARK: - Diy Floating Point (Private) + *============================================================================*/ + +/** "Do It Yourself Floating Point" struct. */ +typedef struct diy_fp { + u64 sig; /* significand */ + i32 exp; /* exponent, base 2 */ + i32 pad; /* padding, useless */ +} diy_fp; + +/** Get cached rounded diy_fp with pow(10, e) The input value must in range + [POW10_SIG_TABLE_MIN_EXP, POW10_SIG_TABLE_MAX_EXP]. */ +static_inline diy_fp diy_fp_get_cached_pow10(i32 exp10) { + diy_fp fp; + u64 sig_ext; + pow10_table_get_sig(exp10, &fp.sig, &sig_ext); + pow10_table_get_exp(exp10, &fp.exp); + fp.sig += (sig_ext >> 63); + return fp; +} + +/** Returns fp * fp2. */ +static_inline diy_fp diy_fp_mul(diy_fp fp, diy_fp fp2) { + u64 hi, lo; + u128_mul(fp.sig, fp2.sig, &hi, &lo); + fp.sig = hi + (lo >> 63); + fp.exp += fp2.exp + 64; + return fp; +} + +/** Convert diy_fp to IEEE-754 raw value. */ +static_inline u64 diy_fp_to_ieee_raw(diy_fp fp) { + u64 sig = fp.sig; + i32 exp = fp.exp; + u32 lz_bits; + if (unlikely(fp.sig == 0)) return 0; + + lz_bits = u64_lz_bits(sig); + sig <<= lz_bits; + sig >>= F64_BITS - F64_SIG_FULL_BITS; + exp -= (i32)lz_bits; + exp += F64_BITS - F64_SIG_FULL_BITS; + exp += F64_SIG_BITS; + + if (unlikely(exp >= F64_MAX_BIN_EXP)) { + /* overflow */ + return F64_BITS_INF; + } else if (likely(exp >= F64_MIN_BIN_EXP - 1)) { + /* normal */ + exp += F64_EXP_BIAS; + return ((u64)exp << F64_SIG_BITS) | (sig & F64_SIG_MASK); + } else if (likely(exp >= F64_MIN_BIN_EXP - F64_SIG_FULL_BITS)) { + /* subnormal */ + return sig >> (F64_MIN_BIN_EXP - exp - 1); + } else { + /* underflow */ + return 0; + } +} + + + +/*============================================================================== + * MARK: - Number Reader (Private) + *============================================================================*/ + +/** + Read a JSON number. + + 1. This function assume that the floating-point number is in IEEE-754 format. + 2. This function support uint64/int64/double number. If an integer number + cannot fit in uint64/int64, it will returns as a double number. If a double + number is infinite, the return value is based on flag. + 3. This function (with inline attribute) may generate a lot of instructions. + */ +static_inline bool read_num(u8 **ptr, u8 **pre, yyjson_read_flag flg, + yyjson_val *val, const char **msg) { +#define return_err(_pos, _msg) do { \ + *msg = _msg; \ + *end = _pos; \ + return false; \ +} while (false) + +#define return_0() do { \ + val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \ + val->uni.u64 = 0; \ + *end = cur; return true; \ +} while (false) + +#define return_i64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | (u8)((u8)sign << 3); \ + val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \ + *end = cur; return true; \ +} while (false) + +#define return_f64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_f64_bin(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_inf() do { \ + if (has_flg(BIGNUM_AS_RAW)) return_raw(); \ + if (has_allow(INF_AND_NAN)) return_f64_bin(F64_BITS_INF); \ + else return_err(hdr, "number is infinity when parsed as double"); \ +} while (false) + +#define return_raw() do { \ + **pre = '\0'; /* add null-terminator for previous raw string */ \ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ + val->uni.str = (const char *)hdr; \ + *pre = cur; *end = cur; return true; \ +} while (false) + + u8 *sig_cut = NULL; /* significant part cutting position for long number */ + u8 *sig_end = NULL; /* significant part ending position */ + u8 *dot_pos = NULL; /* decimal point position */ + + u64 sig = 0; /* significant part of the number */ + i32 exp = 0; /* exponent part of the number */ + + bool exp_sign; /* temporary exponent sign from literal part */ + i64 exp_sig = 0; /* temporary exponent number from significant part */ + i64 exp_lit = 0; /* temporary exponent number from exponent literal part */ + u64 num; /* temporary number for reading */ + u8 *tmp; /* temporary cursor for reading */ + + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + bool sign; + + /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */ + if (has_flg(NUMBER_AS_RAW)) { + return read_num_raw(ptr, pre, flg, val, msg); + } + + sign = (*hdr == '-'); + cur += sign; + + /* begin with a leading zero or non-digit */ + while (unlikely(!char_is_nonzero(*cur))) { /* 0 or non-digit char */ + if (unlikely(*cur != '0')) { /* non-digit char */ + if (has_allow(EXT_NUMBER)) { + if (*cur == '+' && cur == hdr) { /* leading `+` sign */ + cur++; + continue; + } + if (*cur == '.' && char_is_digit(cur[1])) { /* e.g. '.123' */ + goto leading_dot; + } + } + if (has_allow(INF_AND_NAN)) { + if (read_inf_or_nan(ptr, pre, flg, val)) return true; + } + return_err(cur, "no digit after sign"); + } + /* begin with 0 */ + if (likely(!char_is_digit_or_fp(*++cur))) { + if (has_allow(EXT_NUMBER) && char_to_lower(*cur) == 'x') { /* hex */ + return read_num_hex(ptr, pre, flg, val, msg); + } + return_0(); + } + if (likely(*cur == '.')) { +leading_dot: + dot_pos = cur++; + if (unlikely(!char_is_digit(*cur))) { + if (has_allow(EXT_NUMBER)) { + if (char_is_exp(*cur)) { + goto digi_exp_more; + } else { + return_f64_bin(0); + } + } + return_err(cur, "no digit after decimal point"); + } + while (unlikely(*cur == '0')) cur++; + if (likely(char_is_digit(*cur))) { + /* first non-zero digit after decimal point */ + sig = (u64)(*cur - '0'); /* read first digit */ + cur--; + goto digi_frac_1; /* continue read fraction part */ + } + } + if (unlikely(char_is_digit(*cur))) { + return_err(cur - 1, "number with leading zero is not allowed"); + } + if (unlikely(char_is_exp(*cur))) { /* 0 with any exponent is still 0 */ + cur += (usize)1 + char_is_sign(cur[1]); + if (unlikely(!char_is_digit(*cur))) { + return_err(cur, "no digit after exponent sign"); + } + while (char_is_digit(*++cur)); + } + return_f64_bin(0); + } + + /* begin with non-zero digit */ + sig = (u64)(*cur - '0'); + + /* + Read integral part, same as the following code. + + for (int i = 1; i <= 18; i++) { + num = cur[i] - '0'; + if (num <= 9) sig = num + sig * 10; + else goto digi_sepr_i; + } + */ +#define expr_intg(i) \ + if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \ + else { goto digi_sepr_##i; } + repeat_in_1_18(expr_intg) +#undef expr_intg + + + cur += 19; /* skip continuous 19 digits */ + if (!char_is_digit_or_fp(*cur)) { + /* this number is an integer consisting of 19 digits */ + if (sign && (sig > ((u64)1 << 63))) { /* overflow */ + if (has_flg(BIGNUM_AS_RAW)) return_raw(); + return_f64(unsafe_yyjson_u64_to_f64(sig)); + } + return_i64(sig); + } + goto digi_intg_more; /* read more digits in integral part */ + + + /* process first non-digit character */ +#define expr_sepr(i) \ + digi_sepr_##i: \ + if (likely(!char_is_fp(cur[i]))) { cur += i; return_i64(sig); } \ + dot_pos = cur + i; \ + if (likely(cur[i] == '.')) goto digi_frac_##i; \ + cur += i; sig_end = cur; goto digi_exp_more; + repeat_in_1_18(expr_sepr) +#undef expr_sepr + + + /* read fraction part */ +#define expr_frac(i) \ + digi_frac_##i: \ + if (likely((num = (u64)(cur[i + 1] - (u8)'0')) <= 9)) \ + sig = num + sig * 10; \ + else { goto digi_stop_##i; } + repeat_in_1_18(expr_frac) +#undef expr_frac + + cur += 20; /* skip 19 digits and 1 decimal point */ + if (!char_is_digit(*cur)) goto digi_frac_end; /* fraction part end */ + goto digi_frac_more; /* read more digits in fraction part */ + + + /* significant part end */ +#define expr_stop(i) \ + digi_stop_##i: \ + cur += i + 1; \ + goto digi_frac_end; + repeat_in_1_18(expr_stop) +#undef expr_stop + + + /* read more digits in integral part */ +digi_intg_more: + if (char_is_digit(*cur)) { + if (!char_is_digit_or_fp(cur[1])) { + /* this number is an integer consisting of 20 digits */ + num = (u64)(*cur - '0'); + if ((sig < (U64_MAX / 10)) || + (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) { + sig = num + sig * 10; + cur++; + /* convert to double if overflow */ + if (sign) { + if (has_flg(BIGNUM_AS_RAW)) return_raw(); + return_f64(unsafe_yyjson_u64_to_f64(sig)); + } + return_i64(sig); + } + } + } + + if (char_is_exp(*cur)) { + dot_pos = cur; + goto digi_exp_more; + } + + if (*cur == '.') { + dot_pos = cur++; + if (unlikely(!char_is_digit(*cur))) { + if (has_allow(EXT_NUMBER)) { + goto digi_frac_end; + } + return_err(cur, "no digit after decimal point"); + } + } + + + /* read more digits in fraction part */ +digi_frac_more: + sig_cut = cur; /* too large to fit in u64, excess digits need to be cut */ + sig += (*cur >= '5'); /* round */ + while (char_is_digit(*++cur)); + if (!dot_pos) { + if (!char_is_fp(*cur) && has_flg(BIGNUM_AS_RAW)) { + return_raw(); /* it's a large integer */ + } + dot_pos = cur; + if (*cur == '.') { + if (unlikely(!char_is_digit(*++cur))) { + if (!has_allow(EXT_NUMBER)) { + return_err(cur, "no digit after decimal point"); + } + } + while (char_is_digit(*cur)) cur++; + } + } + exp_sig = (i64)(dot_pos - sig_cut); + exp_sig += (dot_pos < sig_cut); + + /* ignore trailing zeros */ + tmp = cur - 1; + while ((*tmp == '0' || *tmp == '.') && tmp > hdr) tmp--; + if (tmp < sig_cut) { + sig_cut = NULL; + } else { + sig_end = cur; + } + + if (char_is_exp(*cur)) goto digi_exp_more; + goto digi_exp_finish; + + + /* fraction part end */ +digi_frac_end: + if (unlikely(dot_pos + 1 == cur)) { + if (!has_allow(EXT_NUMBER)) { + return_err(cur, "no digit after decimal point"); + } + } + sig_end = cur; + exp_sig = -(i64)((u64)(cur - dot_pos) - 1); + if (likely(!char_is_exp(*cur))) { + if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) { + return_f64_bin(0); /* underflow */ + } + exp = (i32)exp_sig; + goto digi_finish; + } else { + goto digi_exp_more; + } + + + /* read exponent part */ +digi_exp_more: + exp_sign = (*++cur == '-'); + cur += char_is_sign(*cur); + if (unlikely(!char_is_digit(*cur))) { + return_err(cur, "no digit after exponent sign"); + } + while (*cur == '0') cur++; + + /* read exponent literal */ + tmp = cur; + while (char_is_digit(*cur)) { + exp_lit = (i64)((u8)(*cur++ - '0') + (u64)exp_lit * 10); + } + if (unlikely(cur - tmp >= U64_SAFE_DIG)) { + if (exp_sign) { + return_f64_bin(0); /* underflow */ + } else { + return_inf(); /* overflow */ + } + } + exp_sig += exp_sign ? -exp_lit : exp_lit; + + + /* validate exponent value */ +digi_exp_finish: + if (unlikely(exp_sig < F64_MIN_DEC_EXP - 19)) { + return_f64_bin(0); /* underflow */ + } + if (unlikely(exp_sig > F64_MAX_DEC_EXP)) { + return_inf(); /* overflow */ + } + exp = (i32)exp_sig; + + + /* all digit read finished */ +digi_finish: + + /* + Fast path 1: + + 1. The floating-point number calculation should be accurate, see the + comments of macro `YYJSON_DOUBLE_MATH_CORRECT`. + 2. Correct rounding should be performed (fegetround() == FE_TONEAREST). + 3. The input of floating point number calculation does not lose precision, + which means: 64 - leading_zero(input) - trailing_zero(input) < 53. + + We don't check all available inputs here, because that would make the code + more complicated, and not friendly to branch predictor. + */ +#if YYJSON_DOUBLE_MATH_CORRECT + if (sig < ((u64)1 << 53) && + exp >= -F64_POW10_MAX_EXACT_EXP && + exp <= +F64_POW10_MAX_EXACT_EXP) { + f64 dbl = (f64)sig; + if (exp < 0) { + dbl /= f64_pow10_table[-exp]; + } else { + dbl *= f64_pow10_table[+exp]; + } + return_f64(dbl); + } +#endif + + /* + Fast path 2: + + To keep it simple, we only accept normal number here, + let the slow path to handle subnormal and infinity number. + */ + if (likely(!sig_cut && + exp > -F64_MAX_DEC_EXP + 1 && + exp < +F64_MAX_DEC_EXP - 20)) { + /* + The result value is exactly equal to (sig * 10^exp), + the exponent part (10^exp) can be converted to (sig2 * 2^exp2). + + The sig2 can be an infinite length number, only the highest 128 bits + is cached in the pow10_sig_table. + + Now we have these bits: + sig1 (normalized 64bit) : aaaaaaaa + sig2 (higher 64bit) : bbbbbbbb + sig2_ext (lower 64bit) : cccccccc + sig2_cut (extra unknown bits) : dddddddddddd.... + + And the calculation process is: + ---------------------------------------- + aaaaaaaa * + bbbbbbbbccccccccdddddddddddd.... + ---------------------------------------- + abababababababab + + acacacacacacacac + + adadadadadadadadadad.... + ---------------------------------------- + [hi____][lo____] + + [hi2___][lo2___] + + [unknown___________....] + ---------------------------------------- + + The addition with carry may affect higher bits, but if there is a 0 + in higher bits, the bits higher than 0 will not be affected. + + `lo2` + `unknown` may get a carry bit and may affect `hi2`, the max + value of `hi2` is 0xFFFFFFFFFFFFFFFE, so `hi2` will not overflow. + + `lo` + `hi2` may also get a carry bit and may affect `hi`, but only + the highest significant 53 bits of `hi` is needed. If there is a 0 + in the lower bits of `hi`, then all the following bits can be dropped. + + To convert the result to IEEE-754 double number, we need to perform + correct rounding: + 1. if bit 54 is 0, round down, + 2. if bit 54 is 1 and any bit beyond bit 54 is 1, round up, + 3. if bit 54 is 1 and all bits beyond bit 54 are 0, round to even, + as the extra bits is unknown, this case will not be handled here. + */ + + u64 raw; + u64 sig1, sig2, sig2_ext, hi, lo, hi2, lo2, add, bits; + i32 exp2; + u32 lz; + bool exact = false, carry, round_up; + + /* convert (10^exp) to (sig2 * 2^exp2) */ + pow10_table_get_sig(exp, &sig2, &sig2_ext); + pow10_table_get_exp(exp, &exp2); + + /* normalize and multiply */ + lz = u64_lz_bits(sig); + sig1 = sig << lz; + exp2 -= (i32)lz; + u128_mul(sig1, sig2, &hi, &lo); + + /* + The `hi` is in range [0x4000000000000000, 0xFFFFFFFFFFFFFFFE], + To get normalized value, `hi` should be shifted to the left by 0 or 1. + + The highest significant 53 bits is used by IEEE-754 double number, + and the bit 54 is used to detect rounding direction. + + The lowest (64 - 54 - 1) bits is used to check whether it contains 0. + */ + bits = hi & (((u64)1 << (64 - 54 - 1)) - 1); + if (bits - 1 < (((u64)1 << (64 - 54 - 1)) - 2)) { + /* + (bits != 0 && bits != 0x1FF) => (bits - 1 < 0x1FF - 1) + The `bits` is not zero, so we don't need to check `round to even` + case. The `bits` contains bit `0`, so we can drop the extra bits + after `0`. + */ + exact = true; + + } else { + /* + (bits == 0 || bits == 0x1FF) + The `bits` is filled with all `0` or all `1`, so we need to check + lower bits with another 64-bit multiplication. + */ + u128_mul(sig1, sig2_ext, &hi2, &lo2); + + add = lo + hi2; + if (add + 1 > (u64)1) { + /* + (add != 0 && add != U64_MAX) => (add + 1 > 1) + The `add` is not zero, so we don't need to check `round to + even` case. The `add` contains bit `0`, so we can drop the + extra bits after `0`. The `hi` cannot be U64_MAX, so it will + not overflow. + */ + carry = add < lo || add < hi2; + hi += carry; + exact = true; + } + } + + if (exact) { + /* normalize */ + lz = hi < ((u64)1 << 63); + hi <<= lz; + exp2 -= (i32)lz; + exp2 += 64; + + /* test the bit 54 and get rounding direction */ + round_up = (hi & ((u64)1 << (64 - 54))) > (u64)0; + hi += (round_up ? ((u64)1 << (64 - 54)) : (u64)0); + + /* test overflow */ + if (hi < ((u64)1 << (64 - 54))) { + hi = ((u64)1 << 63); + exp2 += 1; + } + + /* This is a normal number, convert it to IEEE-754 format. */ + hi >>= F64_BITS - F64_SIG_FULL_BITS; + exp2 += F64_BITS - F64_SIG_FULL_BITS + F64_SIG_BITS; + exp2 += F64_EXP_BIAS; + raw = ((u64)exp2 << F64_SIG_BITS) | (hi & F64_SIG_MASK); + return_f64_bin(raw); + } + } + + /* + Slow path: read double number exactly with diyfp. + 1. Use cached diyfp to get an approximation value. + 2. Use bigcomp to check the approximation value if needed. + + This algorithm refers to google's double-conversion project: + https://github.com/google/double-conversion + */ + { + const i32 ERR_ULP_LOG = 3; + const i32 ERR_ULP = 1 << ERR_ULP_LOG; + const i32 ERR_CACHED_POW = ERR_ULP / 2; + const i32 ERR_MUL_FIXED = ERR_ULP / 2; + const i32 DIY_SIG_BITS = 64; + const i32 EXP_BIAS = F64_EXP_BIAS + F64_SIG_BITS; + const i32 EXP_SUBNORMAL = -EXP_BIAS + 1; + + u64 fp_err; + u32 bits; + i32 order_of_magnitude; + i32 effective_significand_size; + i32 precision_digits_count; + u64 precision_bits; + u64 half_way; + + u64 raw; + diy_fp fp, fp_upper; + bigint big_full, big_comp; + i32 cmp; + + fp.sig = sig; + fp.exp = 0; + fp_err = sig_cut ? (u64)(ERR_ULP / 2) : (u64)0; + + /* normalize */ + bits = u64_lz_bits(fp.sig); + fp.sig <<= bits; + fp.exp -= (i32)bits; + fp_err <<= bits; + + /* multiply and add error */ + fp = diy_fp_mul(fp, diy_fp_get_cached_pow10(exp)); + fp_err += (u64)ERR_CACHED_POW + (fp_err != 0) + (u64)ERR_MUL_FIXED; + + /* normalize */ + bits = u64_lz_bits(fp.sig); + fp.sig <<= bits; + fp.exp -= (i32)bits; + fp_err <<= bits; + + /* effective significand */ + order_of_magnitude = DIY_SIG_BITS + fp.exp; + if (likely(order_of_magnitude >= EXP_SUBNORMAL + F64_SIG_FULL_BITS)) { + effective_significand_size = F64_SIG_FULL_BITS; + } else if (order_of_magnitude <= EXP_SUBNORMAL) { + effective_significand_size = 0; + } else { + effective_significand_size = order_of_magnitude - EXP_SUBNORMAL; + } + + /* precision digits count */ + precision_digits_count = DIY_SIG_BITS - effective_significand_size; + if (unlikely(precision_digits_count + ERR_ULP_LOG >= DIY_SIG_BITS)) { + i32 shr = (precision_digits_count + ERR_ULP_LOG) - DIY_SIG_BITS + 1; + fp.sig >>= shr; + fp.exp += shr; + fp_err = (fp_err >> shr) + 1 + (u32)ERR_ULP; + precision_digits_count -= shr; + } + + /* half way */ + precision_bits = fp.sig & (((u64)1 << precision_digits_count) - 1); + precision_bits *= (u32)ERR_ULP; + half_way = (u64)1 << (precision_digits_count - 1); + half_way *= (u32)ERR_ULP; + + /* rounding */ + fp.sig >>= precision_digits_count; + fp.sig += (precision_bits >= half_way + fp_err); + fp.exp += precision_digits_count; + + /* get IEEE double raw value */ + raw = diy_fp_to_ieee_raw(fp); + if (unlikely(raw == F64_BITS_INF)) return_inf(); + if (likely(precision_bits <= half_way - fp_err || + precision_bits >= half_way + fp_err)) { + return_f64_bin(raw); /* number is accurate */ + } + /* now the number is the correct value, or the next lower value */ + + /* upper boundary */ + if (raw & F64_EXP_MASK) { + fp_upper.sig = (raw & F64_SIG_MASK) + ((u64)1 << F64_SIG_BITS); + fp_upper.exp = (i32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); + } else { + fp_upper.sig = (raw & F64_SIG_MASK); + fp_upper.exp = 1; + } + fp_upper.exp -= F64_EXP_BIAS + F64_SIG_BITS; + fp_upper.sig <<= 1; + fp_upper.exp -= 1; + fp_upper.sig += 1; /* add half ulp */ + + /* compare with bigint */ + bigint_set_buf(&big_full, sig, &exp, sig_cut, sig_end, dot_pos); + bigint_set_u64(&big_comp, fp_upper.sig); + if (exp >= 0) { + bigint_mul_pow10(&big_full, +exp); + } else { + bigint_mul_pow10(&big_comp, -exp); + } + if (fp_upper.exp > 0) { + bigint_mul_pow2(&big_comp, (u32)+fp_upper.exp); + } else { + bigint_mul_pow2(&big_full, (u32)-fp_upper.exp); + } + cmp = bigint_cmp(&big_full, &big_comp); + if (likely(cmp != 0)) { + /* round down or round up */ + raw += (cmp > 0); + } else { + /* falls midway, round to even */ + raw += (raw & 1); + } + + if (unlikely(raw == F64_BITS_INF)) return_inf(); + return_f64_bin(raw); + } + +#undef return_err +#undef return_inf +#undef return_0 +#undef return_i64 +#undef return_f64 +#undef return_f64_bin +#undef return_raw +} + + + +#else /* FP_READER */ + +/** + Read a JSON number. + This is a fallback function if the custom number reader is disabled. + This function use libc's strtod() to read floating-point number. + */ +static_inline bool read_num(u8 **ptr, u8 **pre, yyjson_read_flag flg, + yyjson_val *val, const char **msg) { +#define return_err(_pos, _msg) do { \ + *msg = _msg; \ + *end = _pos; \ + return false; \ +} while (false) + +#define return_0() do { \ + val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \ + val->uni.u64 = 0; \ + *end = cur; return true; \ +} while (false) + +#define return_i64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | (u64)((u8)sign << 3); \ + val->uni.u64 = (u64)(sign ? (u64)(~(_v) + 1) : (u64)(_v)); \ + *end = cur; return true; \ +} while (false) + +#define return_f64(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.f64 = sign ? -(f64)(_v) : (f64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_f64_bin(_v) do { \ + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; \ + val->uni.u64 = ((u64)sign << 63) | (u64)(_v); \ + *end = cur; return true; \ +} while (false) + +#define return_inf() do { \ + if (has_flg(BIGNUM_AS_RAW)) return_raw(); \ + if (has_allow(INF_AND_NAN)) return_f64_bin(F64_BITS_INF); \ + else return_err(hdr, "number is infinity when parsed as double"); \ +} while (false) + +#define return_raw() do { \ + val->tag = ((u64)(cur - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; \ + val->uni.str = (const char *)hdr; \ + **pre = '\0'; *pre = cur; *end = cur; return true; \ +} while (false) + + u64 sig, num; + u8 *hdr = *ptr; + u8 *cur = *ptr; + u8 **end = ptr; + u8 *dot = NULL; + u8 *f64_end = NULL; + bool sign; + + /* read number as raw string if has `YYJSON_READ_NUMBER_AS_RAW` flag */ + if (has_flg(NUMBER_AS_RAW)) { + return read_num_raw(ptr, pre, flg, val, msg); + } + + sign = (*hdr == '-'); + cur += sign; + sig = (u8)(*cur - '0'); + + /* read first digit, check leading zero */ + while (unlikely(!char_is_digit(*cur))) { + if (has_allow(EXT_NUMBER)) { + if (*cur == '+' && cur == hdr) { /* leading `+` sign */ + cur++; + sig = (u8)(*cur - '0'); + continue; + } + if (*cur == '.' && char_is_num(cur[1])) { /* no integer part */ + goto read_double; /* e.g. '.123' */ + } + } + if (has_allow(INF_AND_NAN)) { + if (read_inf_or_nan(ptr, pre, flg, val)) return true; + } + return_err(cur, "no digit after sign"); + } + if (*cur == '0') { + cur++; + if (unlikely(char_is_digit(*cur))) { + return_err(cur - 1, "number with leading zero is not allowed"); + } + if (!char_is_fp(*cur)) { + if (has_allow(EXT_NUMBER) && + (*cur == 'x' || *cur == 'X')) { /* hex integer */ + return read_num_hex(ptr, pre, flg, val, msg); + } + return_0(); + } + goto read_double; + } + + /* read continuous digits, up to 19 characters */ +#define expr_intg(i) \ + if (likely((num = (u64)(cur[i] - (u8)'0')) <= 9)) sig = num + sig * 10; \ + else { cur += i; goto intg_end; } + repeat_in_1_18(expr_intg) +#undef expr_intg + + /* here are 19 continuous digits, skip them */ + cur += 19; + if (char_is_digit(cur[0]) && !char_is_digit_or_fp(cur[1])) { + /* this number is an integer consisting of 20 digits */ + num = (u8)(*cur - '0'); + if ((sig < (U64_MAX / 10)) || + (sig == (U64_MAX / 10) && num <= (U64_MAX % 10))) { + sig = num + sig * 10; + cur++; + if (sign) { + if (has_flg(BIGNUM_AS_RAW)) return_raw(); + return_f64(unsafe_yyjson_u64_to_f64(sig)); + } + return_i64(sig); + } + } + +intg_end: + /* continuous digits ended */ + if (!char_is_digit_or_fp(*cur)) { + /* this number is an integer consisting of 1 to 19 digits */ + if (sign && (sig > ((u64)1 << 63))) { + if (has_flg(BIGNUM_AS_RAW)) return_raw(); + return_f64(unsafe_yyjson_u64_to_f64(sig)); + } + return_i64(sig); + } + +read_double: + /* this number should be read as double */ + while (char_is_digit(*cur)) cur++; + if (!char_is_fp(*cur) && has_flg(BIGNUM_AS_RAW)) { + return_raw(); /* it's a large integer */ + } + while (*cur == '.') { + /* skip fraction part */ + dot = cur; + cur++; + if (!char_is_digit(*cur)) { + if (has_allow(EXT_NUMBER)) { + break; + } else { + return_err(cur, "no digit after decimal point"); + } + } + cur++; + while (char_is_digit(*cur)) cur++; + break; + } + if (char_is_exp(*cur)) { + /* skip exponent part */ + cur += 1 + char_is_sign(cur[1]); + if (!char_is_digit(*cur)) { + return_err(cur, "no digit after exponent sign"); + } + cur++; + while (char_is_digit(*cur)) cur++; + } + + /* + libc's strtod() is used to parse the floating-point number. + + Note that the decimal point character used by strtod() is locale-dependent, + and the rounding direction may affected by fesetround(). + + For currently known locales, (en, zh, ja, ko, am, he, hi) use '.' as the + decimal point, while other locales use ',' as the decimal point. + + Here strtod() is called twice for different locales, but if another thread + happens calls setlocale() between two strtod(), parsing may still fail. + */ + val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end); + if (unlikely(f64_end != cur)) { + /* replace '.' with ',' for locale */ + bool cut = (*cur == ','); + if (cut) *cur = ' '; + if (dot) *dot = ','; + val->uni.f64 = strtod((const char *)hdr, (char **)&f64_end); + /* restore ',' to '.' */ + if (cut) *cur = ','; + if (dot) *dot = '.'; + if (unlikely(f64_end != cur)) { + return_err(hdr, "strtod() failed to parse the number"); + } + } + if (unlikely(val->uni.f64 >= HUGE_VAL || val->uni.f64 <= -HUGE_VAL)) { + return_inf(); + } + val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + *end = cur; + return true; + +#undef return_err +#undef return_0 +#undef return_i64 +#undef return_f64 +#undef return_f64_bin +#undef return_inf +#undef return_raw +} + +#endif /* FP_READER */ + + + +/*============================================================================== + * MARK: - String Reader (Private) + *============================================================================*/ + +/** Read unicode escape sequence. */ +static_inline bool read_uni_esc(u8 **src_ptr, u8 **dst_ptr, const char **msg) { +#define return_err(_end, _msg) *msg = _msg; *src_ptr = _end; return false + + u8 *src = *src_ptr; + u8 *dst = *dst_ptr; + u16 hi, lo; + u32 uni; + + src += 2; /* skip `\u` */ + if (unlikely(!hex_load_4(src, &hi))) { + return_err(src - 2, "invalid escaped sequence in string"); + } + src += 4; /* skip hex */ + if (likely((hi & 0xF800) != 0xD800)) { + /* a BMP character */ + if (hi >= 0x800) { + *dst++ = (u8)(0xE0 | (hi >> 12)); + *dst++ = (u8)(0x80 | ((hi >> 6) & 0x3F)); + *dst++ = (u8)(0x80 | (hi & 0x3F)); + } else if (hi >= 0x80) { + *dst++ = (u8)(0xC0 | (hi >> 6)); + *dst++ = (u8)(0x80 | (hi & 0x3F)); + } else { + *dst++ = (u8)hi; + } + } else { + /* a non-BMP character, represented as a surrogate pair */ + if (unlikely((hi & 0xFC00) != 0xD800)) { + return_err(src - 6, "invalid high surrogate in string"); + } + if (unlikely(!byte_match_2(src, "\\u"))) { + return_err(src - 6, "no low surrogate in string"); + } + if (unlikely(!hex_load_4(src + 2, &lo))) { + return_err(src - 6, "invalid escape in string"); + } + if (unlikely((lo & 0xFC00) != 0xDC00)) { + return_err(src - 6, "invalid low surrogate in string"); + } + uni = ((((u32)hi - 0xD800) << 10) | + ((u32)lo - 0xDC00)) + 0x10000; + *dst++ = (u8)(0xF0 | (uni >> 18)); + *dst++ = (u8)(0x80 | ((uni >> 12) & 0x3F)); + *dst++ = (u8)(0x80 | ((uni >> 6) & 0x3F)); + *dst++ = (u8)(0x80 | (uni & 0x3F)); + src += 6; + } + *src_ptr = src; + *dst_ptr = dst; + return true; +#undef return_err +} + +/** + Read a JSON string. + @param quo The quote character (single quote or double quote). + @param ptr The head pointer of string before quote (inout). + @param eof JSON end position. + @param flg JSON read flag. + @param val The string value to be written. + @param msg The error message pointer. + @param con Continuation for incremental parsing. + @return Whether success. + */ +static_inline bool read_str_opt(u8 quo, u8 **ptr, u8 *eof, yyjson_read_flag flg, + yyjson_val *val, const char **msg, u8 *con[2]) { + /* + GCC may sometimes load variables into registers too early, causing + unnecessary instructions and performance degradation. This inline assembly + serves as a hint to GCC: 'This variable will be modified, so avoid loading + it too early.' Other compilers like MSVC, Clang, and ICC can generate the + expected instructions without needing this hint. + + Check out this example: https://godbolt.org/z/YG6a5W5Ec + */ +#define return_err(_end, _msg) do { \ + *msg = _msg; \ + *end = _end; \ + if (con) { con[0] = _end; con[1] = dst; } \ + return false; \ +} while (false) + + u8 *hdr = *ptr + 1; + u8 **end = ptr; + u8 *src = hdr, *dst = NULL, *pos; + u16 hi, lo; + u32 uni, tmp; + + /* Resume incremental parsing. */ + if (con && unlikely(con[0])) { + src = con[0]; + dst = con[1]; + if (dst) goto copy_ascii; + } + +skip_ascii: + /* + Most strings have no escaped characters, so we can jump them quickly. + + We want to make loop unrolling, as shown in the following code. Some + compiler may not generate instructions as expected, so we rewrite it with + explicit goto statements. We hope the compiler can generate instructions + like this: https://godbolt.org/z/8vjsYq + + while (true) repeat16({ + if (likely((char_is_ascii_skip(*src)))) src++; + else break; + }) + */ + if (quo == '"') { +#define expr_jump(i) \ + if (likely(char_is_ascii_skip(src[i]))) {} \ + else goto skip_ascii_stop##i; + +#define expr_stop(i) \ + skip_ascii_stop##i: \ + src += i; \ + goto skip_ascii_end; + + repeat16_incr(expr_jump) + src += 16; + goto skip_ascii; + repeat16_incr(expr_stop) + +#undef expr_jump +#undef expr_stop + } else { +#define expr_jump(i) \ + if (likely(char_is_ascii_skip_sq(src[i]))) {} \ + else goto skip_ascii_stop_sq##i; + +#define expr_stop(i) \ + skip_ascii_stop_sq##i: \ + src += i; \ + goto skip_ascii_end; + + repeat16_incr(expr_jump) + src += 16; + goto skip_ascii; + repeat16_incr(expr_stop) + +#undef expr_jump +#undef expr_stop + } + +skip_ascii_end: + gcc_store_barrier(*src); + if (likely(*src == quo)) { + val->tag = ((u64)(src - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR | + (quo == '"' ? YYJSON_SUBTYPE_NOESC : 0); + val->uni.str = (const char *)hdr; + *src = '\0'; + *end = src + 1; + if (con) con[0] = con[1] = NULL; + return true; + } + +skip_utf8: + if (*src & 0x80) { /* non-ASCII character */ + /* + Non-ASCII character appears here, which means that the text is likely + to be written in non-English or emoticons. According to some common + data set statistics, byte sequences of the same length may appear + consecutively. We process the byte sequences of the same length in each + loop, which is more friendly to branch prediction. + */ + pos = src; +#if YYJSON_DISABLE_UTF8_VALIDATION + while (true) repeat8({ + if (likely((*src & 0xF0) == 0xE0)) src += 3; + else break; + }) + if (*src < 0x80) goto skip_ascii; + while (true) repeat8({ + if (likely((*src & 0xE0) == 0xC0)) src += 2; + else break; + }) + while (true) repeat8({ + if (likely((*src & 0xF8) == 0xF0)) src += 4; + else break; + }) +#else + uni = byte_load_4(src); + while (is_utf8_seq3(uni)) { + src += 3; + uni = byte_load_4(src); + } + if (is_utf8_seq1(uni)) goto skip_ascii; + while (is_utf8_seq2(uni)) { + src += 2; + uni = byte_load_4(src); + } + while (is_utf8_seq4(uni)) { + src += 4; + uni = byte_load_4(src); + } +#endif + if (unlikely(pos == src)) { + if (has_allow(INVALID_UNICODE)) ++src; + else return_err(src, "invalid UTF-8 encoding in string"); + } + goto skip_ascii; + } + + /* The escape character appears, we need to copy it. */ + dst = src; +copy_escape: + if (likely(*src == '\\')) { + switch (*++src) { + case '"': *dst++ = '"'; src++; break; + case '\\': *dst++ = '\\'; src++; break; + case '/': *dst++ = '/'; src++; break; + case 'b': *dst++ = '\b'; src++; break; + case 'f': *dst++ = '\f'; src++; break; + case 'n': *dst++ = '\n'; src++; break; + case 'r': *dst++ = '\r'; src++; break; + case 't': *dst++ = '\t'; src++; break; + case 'u': + src--; + if (!read_uni_esc(&src, &dst, msg)) return_err(src, *msg); + break; + default: { + if (has_allow(EXT_ESCAPE)) { + /* read extended escape (non-standard) */ + switch (*src) { + case '\'': *dst++ = '\''; src++; break; + case 'a': *dst++ = '\a'; src++; break; + case 'v': *dst++ = '\v'; src++; break; + case '?': *dst++ = '\?'; src++; break; + case 'e': *dst++ = 0x1B; src++; break; + case '0': + if (!char_is_digit(src[1])) { + *dst++ = '\0'; src++; break; + } + return_err(src - 1, "octal escape is not allowed"); + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return_err(src - 1, "invalid number escape"); + case 'x': { + u8 c; + if (hex_load_2(src + 1, &c)) { + src += 3; + if (c <= 0x7F) { /* 1-byte ASCII */ + *dst++ = c; + } else { /* 2-byte UTF-8 */ + *dst++ = (u8)(0xC0 | (c >> 6)); + *dst++ = (u8)(0x80 | (c & 0x3F)); + } + break; + } + return_err(src - 1, "invalid hex escape"); + } + case '\n': src++; break; + case '\r': src++; src += (*src == '\n'); break; + case 0xE2: /* Line terminator: U+2028, U+2029 */ + if ((src[1] == 0x80 && src[2] == 0xA8) || + (src[1] == 0x80 && src[2] == 0xA9)) { + src += 3; + } + break; + default: + break; /* skip */ + } + } else if (quo == '\'' && *src == '\'') { + *dst++ = '\''; src++; break; + } else { + return_err(src - 1, "invalid escaped sequence in string"); + } + } + } + } else if (likely(*src == quo)) { + val->tag = ((u64)(dst - hdr) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = (const char *)hdr; + *dst = '\0'; + *end = src + 1; + if (con) con[0] = con[1] = NULL; + return true; + } else { + if (!has_allow(INVALID_UNICODE)) { + return_err(src, "unexpected control character in string"); + } + if (src >= eof) return_err(src, "unclosed string"); + *dst++ = *src++; + } + +copy_ascii: + /* + Copy continuous ASCII, loop unrolling, same as the following code: + + while (true) repeat16({ + if (char_is_ascii_skip(*src)) *dst++ = *src++; + else break; + }) + */ + if (quo == '"') { +#define expr_jump(i) \ + if (likely((char_is_ascii_skip(src[i])))) {} \ + else { gcc_store_barrier(src[i]); goto copy_ascii_stop_##i; } + repeat16_incr(expr_jump) +#undef expr_jump + } else { +#define expr_jump(i) \ + if (likely((char_is_ascii_skip_sq(src[i])))) {} \ + else { gcc_store_barrier(src[i]); goto copy_ascii_stop_##i; } + repeat16_incr(expr_jump) +#undef expr_jump + } + + byte_move_16(dst, src); + dst += 16; src += 16; + goto copy_ascii; + + /* + The memory is copied forward since `dst < src`. + So it's safe to move one extra byte to reduce instruction count. + */ +#define expr_jump(i) \ + copy_ascii_stop_##i: \ + byte_move_forward(dst, src, i); \ + dst += i; src += i; \ + goto copy_utf8; + repeat16_incr(expr_jump) +#undef expr_jump + +copy_utf8: + if (*src & 0x80) { /* non-ASCII character */ + pos = src; + uni = byte_load_4(src); +#if YYJSON_DISABLE_UTF8_VALIDATION + while (true) repeat4({ + if ((uni & utf8_seq(b3_mask)) == utf8_seq(b3_patt)) { + byte_copy_4(dst, &uni); + dst += 3; src += 3; + uni = byte_load_4(src); + } else break; + }) + if ((uni & utf8_seq(b1_mask)) == utf8_seq(b1_patt)) goto copy_ascii; + while (true) repeat4({ + if ((uni & utf8_seq(b2_mask)) == utf8_seq(b2_patt)) { + byte_copy_2(dst, &uni); + dst += 2; src += 2; + uni = byte_load_4(src); + } else break; + }) + while (true) repeat4({ + if ((uni & utf8_seq(b4_mask)) == utf8_seq(b4_patt)) { + byte_copy_4(dst, &uni); + dst += 4; src += 4; + uni = byte_load_4(src); + } else break; + }) +#else + while (is_utf8_seq3(uni)) { + byte_copy_4(dst, &uni); + dst += 3; src += 3; + uni = byte_load_4(src); + } + if (is_utf8_seq1(uni)) goto copy_ascii; + while (is_utf8_seq2(uni)) { + byte_copy_2(dst, &uni); + dst += 2; src += 2; + uni = byte_load_4(src); + } + while (is_utf8_seq4(uni)) { + byte_copy_4(dst, &uni); + dst += 4; src += 4; + uni = byte_load_4(src); + } +#endif + if (unlikely(pos == src)) { + if (!has_allow(INVALID_UNICODE)) { + return_err(src, MSG_ERR_UTF8); + } + goto copy_ascii_stop_1; + } + goto copy_ascii; + } + goto copy_escape; + +#undef return_err +} + +static_inline bool read_str(u8 **ptr, u8 *eof, yyjson_read_flag flg, + yyjson_val *val, const char **msg) { + return read_str_opt('\"', ptr, eof, flg, val, msg, NULL); +} + +static_inline bool read_str_con(u8 **ptr, u8 *eof, yyjson_read_flag flg, + yyjson_val *val, const char **msg, u8 **con) { + return read_str_opt('\"', ptr, eof, flg, val, msg, con); +} + +static_noinline bool read_str_sq(u8 **ptr, u8 *eof, yyjson_read_flag flg, + yyjson_val *val, const char **msg) { + return read_str_opt('\'', ptr, eof, flg, val, msg, NULL); +} + +/** Read unquoted key (identifier name). */ +static_noinline bool read_str_id(u8 **ptr, u8 *eof, yyjson_read_flag flg, + u8 **pre, yyjson_val *val, const char **msg) { +#define return_err(_end, _msg) do { \ + *msg = _msg; \ + *end = _end; \ + return false; \ +} while (false) + +#define return_suc(_str_end, _cur_end) do { \ + val->tag = ((u64)(_str_end - hdr) << YYJSON_TAG_BIT) | \ + (u64)(YYJSON_TYPE_STR); \ + val->uni.str = (const char *)hdr; \ + *pre = _str_end; *end = _cur_end; \ + return true; \ +} while (false) + + u8 *hdr = *ptr; + u8 **end = ptr; + u8 *src = hdr, *dst = NULL; + u16 hi, lo; + u32 uni, tmp; + + /* add null-terminator for previous raw string */ + **pre = '\0'; + +skip_ascii: +#define expr_jump(i) \ + if (likely(char_is_id_ascii(src[i]))) {} \ + else goto skip_ascii_stop##i; + +#define expr_stop(i) \ + skip_ascii_stop##i: \ + src += i; \ + goto skip_ascii_end; + + repeat16_incr(expr_jump) + src += 16; + goto skip_ascii; + repeat16_incr(expr_stop) + +#undef expr_jump +#undef expr_stop + +skip_ascii_end: + gcc_store_barrier(*src); + if (likely(!char_is_id_next(*src))) { + return_suc(src, src); + } + +skip_utf8: + while (*src >= 0x80) { + if (has_allow(EXT_WHITESPACE)) { + if (char_is_space_ext(*src) && ext_space_len(src)) { + return_suc(src, src); + } + } + uni = byte_load_4(src); + if (is_utf8_seq2(uni)) { + src += 2; + } else if (is_utf8_seq3(uni)) { + src += 3; + } else if (is_utf8_seq4(uni)) { + src += 4; + } else { +#if !YYJSON_DISABLE_UTF8_VALIDATION + if (!has_allow(INVALID_UNICODE)) return_err(src, MSG_ERR_UTF8); +#endif + src += 1; + } + } + if (char_is_id_ascii(*src)) goto skip_ascii; + + /* The escape character appears, we need to copy it. */ + dst = src; +copy_escape: + if (byte_match_2(src, "\\u")) { + if (!read_uni_esc(&src, &dst, msg)) return_err(src, *msg); + } else { + if (!char_is_id_next(*src)) return_suc(dst, src); + return_err(src, "unexpected character in key"); + } + +copy_ascii: + /* + Copy continuous ASCII, loop unrolling, same as the following code: + + while (true) repeat16({ + if (char_is_ascii_skip(*src)) *dst++ = *src++; + else break; + }) + */ +#define expr_jump(i) \ + if (likely((char_is_id_ascii(src[i])))) {} \ + else { gcc_store_barrier(src[i]); goto copy_ascii_stop_##i; } + repeat16_incr(expr_jump) +#undef expr_jump + + byte_move_16(dst, src); + dst += 16; src += 16; + goto copy_ascii; + +#define expr_jump(i) \ + copy_ascii_stop_##i: \ + byte_move_forward(dst, src, i); \ + dst += i; src += i; \ + goto copy_utf8; + repeat16_incr(expr_jump) +#undef expr_jump + +copy_utf8: + while (*src >= 0x80) { /* non-ASCII character */ + if (has_allow(EXT_WHITESPACE)) { + if (char_is_space_ext(*src) && ext_space_len(src)) { + return_suc(dst, src); + } + } + uni = byte_load_4(src); + if (is_utf8_seq2(uni)) { + byte_copy_2(dst, &uni); + dst += 2; src += 2; + } else if (is_utf8_seq3(uni)) { + byte_copy_4(dst, &uni); + dst += 3; src += 3; + } else if (is_utf8_seq4(uni)) { + byte_copy_4(dst, &uni); + dst += 4; src += 4; + } else { +#if !YYJSON_DISABLE_UTF8_VALIDATION + if (!has_allow(INVALID_UNICODE)) return_err(src, MSG_ERR_UTF8); +#endif + *dst = *src; + dst += 1; src += 1; + } + } + if (char_is_id_ascii(*src)) goto copy_ascii; + goto copy_escape; + +#undef return_err +#undef return_suc +} + + + +/*============================================================================== + * MARK: - JSON Reader Implementation (Private) + * + * We use goto statements to build the finite state machine (FSM). + * The FSM's state was held by program counter (PC) and the 'goto' make the + * state transitions. + *============================================================================*/ + +/** Read single value JSON document. */ +static_noinline yyjson_doc *read_root_single(u8 *hdr, u8 *cur, u8 *eof, + yyjson_alc alc, + yyjson_read_flag flg, + yyjson_read_err *err) { +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, eof, YYJSON_READ_ERROR_##_code, flg)) { \ + err->pos = (usize)(eof - hdr); \ + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ + err->msg = MSG_NOT_END; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + if (val_hdr) alc.free(alc.ctx, val_hdr); \ + return NULL; \ +} while (false) + + usize hdr_len; /* value count used by doc */ + usize alc_num; /* value count capacity */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val; /* current value */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + u8 raw_end[1]; /* raw end for null-terminator */ + u8 *raw_ptr = raw_end; + u8 **pre = &raw_ptr; /* previous raw end pointer */ + + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + alc_num = hdr_len + 1; /* single value */ + + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_num * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val = val_hdr + hdr_len; + + if (char_is_num(*cur)) { + if (likely(read_num(&cur, pre, flg, val, &msg))) goto doc_end; + goto fail_number; + } + if (*cur == '"') { + if (likely(read_str(&cur, eof, flg, val, &msg))) goto doc_end; + goto fail_string; + } + if (*cur == 't') { + if (likely(read_true(&cur, val))) goto doc_end; + goto fail_literal_true; + } + if (*cur == 'f') { + if (likely(read_false(&cur, val))) goto doc_end; + goto fail_literal_false; + } + if (*cur == 'n') { + if (likely(read_null(&cur, val))) goto doc_end; + if (has_allow(INF_AND_NAN)) { + if (read_nan(&cur, pre, flg, val)) goto doc_end; + } + goto fail_literal_null; + } + if (has_allow(INF_AND_NAN)) { + if (read_inf_or_nan(&cur, pre, flg, val)) goto doc_end; + } + if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { + if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto doc_end; + goto fail_string; + } + goto fail_character; + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < eof) && !has_flg(STOP_WHEN_DONE)) { + while (char_is_space(*cur)) cur++; + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (!skip_trivia(&cur, eof, flg) && cur == eof) { + goto fail_comment; + } + } + if (unlikely(cur < eof)) goto fail_garbage; + } + + **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = 1; + doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr; + return doc; + +fail_string: return_err(cur, INVALID_STRING, msg); +fail_number: return_err(cur, INVALID_NUMBER, msg); +fail_alloc: return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); +fail_literal_true: return_err(cur, LITERAL, MSG_CHAR_T); +fail_literal_false: return_err(cur, LITERAL, MSG_CHAR_F); +fail_literal_null: return_err(cur, LITERAL, MSG_CHAR_N); +fail_character: return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR); +fail_comment: return_err(cur, INVALID_COMMENT, MSG_COMMENT); +fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE); + +#undef return_err +} + +/** Read JSON document (accept all style, but optimized for minify). */ +static_inline yyjson_doc *read_root_minify(u8 *hdr, u8 *cur, u8 *eof, + yyjson_alc alc, + yyjson_read_flag flg, + yyjson_read_err *err) { +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, eof, YYJSON_READ_ERROR_##_code, flg)) { \ + err->pos = (usize)(eof - hdr); \ + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ + err->msg = MSG_NOT_END; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + if (val_hdr) alc.free(alc.ctx, val_hdr); \ + return NULL; \ +} while (false) + +#define val_incr() do { \ + val++; \ + if (unlikely(val >= val_end)) { \ + usize alc_old = alc_len; \ + usize val_ofs = (usize)(val - val_hdr); \ + usize ctn_ofs = (usize)(ctn - val_hdr); \ + alc_len += alc_len / 2; \ + if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \ + val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ + alc_old * sizeof(yyjson_val), \ + alc_len * sizeof(yyjson_val)); \ + if ((!val_tmp)) goto fail_alloc; \ + val = val_tmp + val_ofs; \ + ctn = val_tmp + ctn_ofs; \ + val_hdr = val_tmp; \ + val_end = val_tmp + (alc_len - 2); \ + } \ +} while (false) + + usize dat_len; /* data length in bytes, hint for allocator */ + usize hdr_len; /* value count used by yyjson_doc */ + usize alc_len; /* value count allocated */ + usize alc_max; /* maximum value count for allocator */ + usize ctn_len; /* the number of elements in current container */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val_end; /* the end of allocated values */ + yyjson_val *val_tmp; /* temporary pointer for realloc */ + yyjson_val *val; /* current JSON value */ + yyjson_val *ctn; /* current container */ + yyjson_val *ctn_parent; /* parent of current container */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + u8 raw_end[1]; /* raw end for null-terminator */ + u8 *raw_ptr = raw_end; + u8 **pre = &raw_ptr; /* previous raw end pointer */ + + dat_len = has_flg(STOP_WHEN_DONE) ? 256 : (usize)(eof - cur); + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + alc_max = USIZE_MAX / sizeof(yyjson_val); + alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_MINIFY_RATIO) + 4; + alc_len = yyjson_min(alc_len, alc_max); + + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */ + val = val_hdr + hdr_len; + ctn = val; + ctn_len = 0; + + if (*cur++ == '{') { + ctn->tag = YYJSON_TYPE_OBJ; + ctn->uni.ofs = 0; + goto obj_key_begin; + } else { + ctn->tag = YYJSON_TYPE_ARR; + ctn->uni.ofs = 0; + goto arr_val_begin; + } + +arr_begin: + /* save current container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + + /* create a new array value, save parent container offset */ + val_incr(); + val->tag = YYJSON_TYPE_ARR; + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + + /* push the new array value as current container */ + ctn = val; + ctn_len = 0; + +arr_val_begin: + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (char_is_num(*cur)) { + val_incr(); + ctn_len++; + if (likely(read_num(&cur, pre, flg, val, &msg))) goto arr_val_end; + goto fail_number; + } + if (*cur == '"') { + val_incr(); + ctn_len++; + if (likely(read_str(&cur, eof, flg, val, &msg))) goto arr_val_end; + goto fail_string; + } + if (*cur == 't') { + val_incr(); + ctn_len++; + if (likely(read_true(&cur, val))) goto arr_val_end; + goto fail_literal_true; + } + if (*cur == 'f') { + val_incr(); + ctn_len++; + if (likely(read_false(&cur, val))) goto arr_val_end; + goto fail_literal_false; + } + if (*cur == 'n') { + val_incr(); + ctn_len++; + if (likely(read_null(&cur, val))) goto arr_val_end; + if (has_allow(INF_AND_NAN)) { + if (read_nan(&cur, pre, flg, val)) goto arr_val_end; + } + goto fail_literal_null; + } + if (*cur == ']') { + cur++; + if (likely(ctn_len == 0)) goto arr_end; + if (has_allow(TRAILING_COMMAS)) goto arr_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_begin; + } + if (has_allow(INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val_incr(); + ctn_len++; + if (read_inf_or_nan(&cur, pre, flg, val)) goto arr_val_end; + goto fail_character_val; + } + if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { + val_incr(); + ctn_len++; + if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto arr_val_end; + goto fail_string; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto arr_val_begin; + if (cur == eof) goto fail_comment; + } + goto fail_character_val; + +arr_val_end: + if (*cur == ',') { + cur++; + goto arr_val_begin; + } + if (*cur == ']') { + cur++; + goto arr_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_end; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto arr_val_end; + if (cur == eof) goto fail_comment; + } + goto fail_character_arr_end; + +arr_end: + /* get parent container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + + /* save the next sibling value offset */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; + if (unlikely(ctn == ctn_parent)) goto doc_end; + + /* pop parent as current container */ + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +obj_begin: + /* push container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + val_incr(); + val->tag = YYJSON_TYPE_OBJ; + /* offset to the parent */ + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + ctn = val; + ctn_len = 0; + +obj_key_begin: + if (likely(*cur == '"')) { + val_incr(); + ctn_len++; + if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_key_end; + goto fail_string; + } + if (likely(*cur == '}')) { + cur++; + if (likely(ctn_len == 0)) goto obj_end; + if (has_allow(TRAILING_COMMAS)) goto obj_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_begin; + } + if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { + val_incr(); + ctn_len++; + if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_key_end; + goto fail_string; + } + if (has_allow(UNQUOTED_KEY) && char_is_id_start(*cur)) { + val_incr(); + ctn_len++; + if (read_str_id(&cur, eof, flg, pre, val, &msg)) goto obj_key_end; + goto fail_string; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto obj_key_begin; + if (cur == eof) goto fail_comment; + } + goto fail_character_obj_key; + +obj_key_end: + if (*cur == ':') { + cur++; + goto obj_val_begin; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_end; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto obj_key_end; + if (cur == eof) goto fail_comment; + } + goto fail_character_obj_sep; + +obj_val_begin: + if (*cur == '"') { + val++; + ctn_len++; + if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_val_end; + goto fail_string; + } + if (char_is_num(*cur)) { + val++; + ctn_len++; + if (likely(read_num(&cur, pre, flg, val, &msg))) goto obj_val_end; + goto fail_number; + } + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (*cur == 't') { + val++; + ctn_len++; + if (likely(read_true(&cur, val))) goto obj_val_end; + goto fail_literal_true; + } + if (*cur == 'f') { + val++; + ctn_len++; + if (likely(read_false(&cur, val))) goto obj_val_end; + goto fail_literal_false; + } + if (*cur == 'n') { + val++; + ctn_len++; + if (likely(read_null(&cur, val))) goto obj_val_end; + if (has_allow(INF_AND_NAN)) { + if (read_nan(&cur, pre, flg, val)) goto obj_val_end; + } + goto fail_literal_null; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_begin; + } + if (has_allow(INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val++; + ctn_len++; + if (read_inf_or_nan(&cur, pre, flg, val)) goto obj_val_end; + goto fail_character_val; + } + if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { + val++; + ctn_len++; + if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_val_end; + goto fail_string; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto obj_val_begin; + if (cur == eof) goto fail_comment; + } + goto fail_character_val; + +obj_val_end: + if (likely(*cur == ',')) { + cur++; + goto obj_key_begin; + } + if (likely(*cur == '}')) { + cur++; + goto obj_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_end; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto obj_val_end; + if (cur == eof) goto fail_comment; + } + goto fail_character_obj_end; + +obj_end: + /* pop container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + /* point to the next value */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; + if (unlikely(ctn == ctn_parent)) goto doc_end; + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < eof) && !has_flg(STOP_WHEN_DONE)) { + while (char_is_space(*cur)) cur++; + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (!skip_trivia(&cur, eof, flg) && cur == eof) { + goto fail_comment; + } + } + if (unlikely(cur < eof)) goto fail_garbage; + } + + **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = (usize)((val - doc->root) + 1); + doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr; + return doc; + +fail_string: return_err(cur, INVALID_STRING, msg); +fail_number: return_err(cur, INVALID_NUMBER, msg); +fail_alloc: return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); +fail_trailing_comma: return_err(cur, JSON_STRUCTURE, MSG_COMMA); +fail_literal_true: return_err(cur, LITERAL, MSG_CHAR_T); +fail_literal_false: return_err(cur, LITERAL, MSG_CHAR_F); +fail_literal_null: return_err(cur, LITERAL, MSG_CHAR_N); +fail_character_val: return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR); +fail_character_arr_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_ARR_END); +fail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY); +fail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP); +fail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END); +fail_comment: return_err(cur, INVALID_COMMENT, MSG_COMMENT); +fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE); + +#undef val_incr +#undef return_err +} + +/** Read JSON document (accept all style, but optimized for pretty). */ +static_inline yyjson_doc *read_root_pretty(u8 *hdr, u8 *cur, u8 *eof, + yyjson_alc alc, + yyjson_read_flag flg, + yyjson_read_err *err) { +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, eof, YYJSON_READ_ERROR_##_code, flg)) { \ + err->pos = (usize)(eof - hdr); \ + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; \ + err->msg = MSG_NOT_END; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + if (val_hdr) alc.free(alc.ctx, val_hdr); \ + return NULL; \ +} while (false) + +#define val_incr() do { \ + val++; \ + if (unlikely(val >= val_end)) { \ + usize alc_old = alc_len; \ + usize val_ofs = (usize)(val - val_hdr); \ + usize ctn_ofs = (usize)(ctn - val_hdr); \ + alc_len += alc_len / 2; \ + if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \ + val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ + alc_old * sizeof(yyjson_val), \ + alc_len * sizeof(yyjson_val)); \ + if ((!val_tmp)) goto fail_alloc; \ + val = val_tmp + val_ofs; \ + ctn = val_tmp + ctn_ofs; \ + val_hdr = val_tmp; \ + val_end = val_tmp + (alc_len - 2); \ + } \ +} while (false) + + usize dat_len; /* data length in bytes, hint for allocator */ + usize hdr_len; /* value count used by yyjson_doc */ + usize alc_len; /* value count allocated */ + usize alc_max; /* maximum value count for allocator */ + usize ctn_len; /* the number of elements in current container */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val_end; /* the end of allocated values */ + yyjson_val *val_tmp; /* temporary pointer for realloc */ + yyjson_val *val; /* current JSON value */ + yyjson_val *ctn; /* current container */ + yyjson_val *ctn_parent; /* parent of current container */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + u8 raw_end[1]; /* raw end for null-terminator */ + u8 *raw_ptr = raw_end; + u8 **pre = &raw_ptr; /* previous raw end pointer */ + + dat_len = has_flg(STOP_WHEN_DONE) ? 256 : (usize)(eof - cur); + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + alc_max = USIZE_MAX / sizeof(yyjson_val); + alc_len = hdr_len + (dat_len / YYJSON_READER_ESTIMATED_PRETTY_RATIO) + 4; + alc_len = yyjson_min(alc_len, alc_max); + + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, alc_len * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val_end = val_hdr + (alc_len - 2); /* padding for key-value pair reading */ + val = val_hdr + hdr_len; + ctn = val; + ctn_len = 0; + + if (*cur++ == '{') { + ctn->tag = YYJSON_TYPE_OBJ; + ctn->uni.ofs = 0; + if (*cur == '\n') cur++; + goto obj_key_begin; + } else { + ctn->tag = YYJSON_TYPE_ARR; + ctn->uni.ofs = 0; + if (*cur == '\n') cur++; + goto arr_val_begin; + } + +arr_begin: + /* save current container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + + /* create a new array value, save parent container offset */ + val_incr(); + val->tag = YYJSON_TYPE_ARR; + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + + /* push the new array value as current container */ + ctn = val; + ctn_len = 0; + if (*cur == '\n') cur++; + +arr_val_begin: +#if YYJSON_IS_REAL_GCC + while (true) repeat16({ + if (byte_match_2(cur, " ")) cur += 2; + else break; + }) +#else + while (true) repeat16({ + if (likely(byte_match_2(cur, " "))) cur += 2; + else break; + }) +#endif + + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (char_is_num(*cur)) { + val_incr(); + ctn_len++; + if (likely(read_num(&cur, pre, flg, val, &msg))) goto arr_val_end; + goto fail_number; + } + if (*cur == '"') { + val_incr(); + ctn_len++; + if (likely(read_str(&cur, eof, flg, val, &msg))) goto arr_val_end; + goto fail_string; + } + if (*cur == 't') { + val_incr(); + ctn_len++; + if (likely(read_true(&cur, val))) goto arr_val_end; + goto fail_literal_true; + } + if (*cur == 'f') { + val_incr(); + ctn_len++; + if (likely(read_false(&cur, val))) goto arr_val_end; + goto fail_literal_false; + } + if (*cur == 'n') { + val_incr(); + ctn_len++; + if (likely(read_null(&cur, val))) goto arr_val_end; + if (has_allow(INF_AND_NAN)) { + if (read_nan(&cur, pre, flg, val)) goto arr_val_end; + } + goto fail_literal_null; + } + if (*cur == ']') { + cur++; + if (likely(ctn_len == 0)) goto arr_end; + if (has_allow(TRAILING_COMMAS)) goto arr_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_begin; + } + if (has_allow(INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val_incr(); + ctn_len++; + if (read_inf_or_nan(&cur, pre, flg, val)) goto arr_val_end; + goto fail_character_val; + } + if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { + val_incr(); + ctn_len++; + if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto arr_val_end; + goto fail_string; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto arr_val_begin; + if (cur == eof) goto fail_comment; + } + goto fail_character_val; + +arr_val_end: + if (byte_match_2(cur, ",\n")) { + cur += 2; + goto arr_val_begin; + } + if (*cur == ',') { + cur++; + goto arr_val_begin; + } + if (*cur == ']') { + cur++; + goto arr_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_end; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto arr_val_end; + if (cur == eof) goto fail_comment; + } + goto fail_character_arr_end; + +arr_end: + /* get parent container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + + /* save the next sibling value offset */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; + if (unlikely(ctn == ctn_parent)) goto doc_end; + + /* pop parent as current container */ + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if (*cur == '\n') cur++; + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +obj_begin: + /* push container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + val_incr(); + val->tag = YYJSON_TYPE_OBJ; + /* offset to the parent */ + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + ctn = val; + ctn_len = 0; + if (*cur == '\n') cur++; + +obj_key_begin: +#if YYJSON_IS_REAL_GCC + while (true) repeat16({ + if (byte_match_2(cur, " ")) cur += 2; + else break; + }) +#else + while (true) repeat16({ + if (likely(byte_match_2(cur, " "))) cur += 2; + else break; + }) +#endif + if (likely(*cur == '"')) { + val_incr(); + ctn_len++; + if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_key_end; + goto fail_string; + } + if (likely(*cur == '}')) { + cur++; + if (likely(ctn_len == 0)) goto obj_end; + if (has_allow(TRAILING_COMMAS)) goto obj_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_begin; + } + if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { + val_incr(); + ctn_len++; + if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_key_end; + goto fail_string; + } + if (has_allow(UNQUOTED_KEY) && char_is_id_start(*cur)) { + val_incr(); + ctn_len++; + if (read_str_id(&cur, eof, flg, pre, val, &msg)) goto obj_key_end; + goto fail_string; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto obj_key_begin; + if (cur == eof) goto fail_comment; + } + goto fail_character_obj_key; + +obj_key_end: + if (byte_match_2(cur, ": ")) { + cur += 2; + goto obj_val_begin; + } + if (*cur == ':') { + cur++; + goto obj_val_begin; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_end; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto obj_key_end; + if (cur == eof) goto fail_comment; + } + goto fail_character_obj_sep; + +obj_val_begin: + if (*cur == '"') { + val++; + ctn_len++; + if (likely(read_str(&cur, eof, flg, val, &msg))) goto obj_val_end; + goto fail_string; + } + if (char_is_num(*cur)) { + val++; + ctn_len++; + if (likely(read_num(&cur, pre, flg, val, &msg))) goto obj_val_end; + goto fail_number; + } + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (*cur == 't') { + val++; + ctn_len++; + if (likely(read_true(&cur, val))) goto obj_val_end; + goto fail_literal_true; + } + if (*cur == 'f') { + val++; + ctn_len++; + if (likely(read_false(&cur, val))) goto obj_val_end; + goto fail_literal_false; + } + if (*cur == 'n') { + val++; + ctn_len++; + if (likely(read_null(&cur, val))) goto obj_val_end; + if (has_allow(INF_AND_NAN)) { + if (read_nan(&cur, pre, flg, val)) goto obj_val_end; + } + goto fail_literal_null; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_begin; + } + if (has_allow(INF_AND_NAN) && + (*cur == 'i' || *cur == 'I' || *cur == 'N')) { + val++; + ctn_len++; + if (read_inf_or_nan(&cur, pre, flg, val)) goto obj_val_end; + goto fail_character_val; + } + if (has_allow(SINGLE_QUOTED_STR) && *cur == '\'') { + val++; + ctn_len++; + if (likely(read_str_sq(&cur, eof, flg, val, &msg))) goto obj_val_end; + goto fail_string; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto obj_val_begin; + if (cur == eof) goto fail_comment; + } + goto fail_character_val; + +obj_val_end: + if (byte_match_2(cur, ",\n")) { + cur += 2; + goto obj_key_begin; + } + if (likely(*cur == ',')) { + cur++; + goto obj_key_begin; + } + if (likely(*cur == '}')) { + cur++; + goto obj_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_end; + } + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (skip_trivia(&cur, eof, flg)) goto obj_val_end; + if (cur == eof) goto fail_comment; + } + goto fail_character_obj_end; + +obj_end: + /* pop container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + /* point to the next value */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; + if (unlikely(ctn == ctn_parent)) goto doc_end; + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if (*cur == '\n') cur++; + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < eof) && !has_flg(STOP_WHEN_DONE)) { + while (char_is_space(*cur)) cur++; + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (!skip_trivia(&cur, eof, flg) && cur == eof) { + goto fail_comment; + } + } + if (unlikely(cur < eof)) goto fail_garbage; + } + + **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = (usize)((val - doc->root) + 1); + doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr; + return doc; + +fail_string: return_err(cur, INVALID_STRING, msg); +fail_number: return_err(cur, INVALID_NUMBER, msg); +fail_alloc: return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); +fail_trailing_comma: return_err(cur, JSON_STRUCTURE, MSG_COMMA); +fail_literal_true: return_err(cur, LITERAL, MSG_CHAR_T); +fail_literal_false: return_err(cur, LITERAL, MSG_CHAR_F); +fail_literal_null: return_err(cur, LITERAL, MSG_CHAR_N); +fail_character_val: return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR); +fail_character_arr_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_ARR_END); +fail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY); +fail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP); +fail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END); +fail_comment: return_err(cur, INVALID_COMMENT, MSG_COMMENT); +fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE); + +#undef val_incr +#undef return_err +} + + + +/*============================================================================== + * MARK: - JSON Reader (Public) + *============================================================================*/ + +yyjson_doc *yyjson_read_opts(char *dat, usize len, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr, + yyjson_read_err *err) { +#define return_err(_pos, _code, _msg) do { \ + err->pos = (usize)(_pos); \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + if (!has_flg(INSITU) && hdr) alc.free(alc.ctx, (void *)hdr); \ + return NULL; \ +} while (false) + + yyjson_read_err tmp_err; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_doc *doc; + u8 *hdr = NULL, *eof, *cur; + + /* validate input parameters */ + if (!err) err = &tmp_err; + if (unlikely(!dat)) return_err(0, INVALID_PARAMETER, "input data is NULL"); + if (unlikely(!len)) return_err(0, INVALID_PARAMETER, "input length is 0"); + + /* add 4-byte zero padding for input data if necessary */ + if (has_flg(INSITU)) { + hdr = (u8 *)dat; + eof = (u8 *)dat + len; + cur = (u8 *)dat; + } else { + if (unlikely(len >= USIZE_MAX - YYJSON_PADDING_SIZE)) { + return_err(0, MEMORY_ALLOCATION, MSG_MALLOC); + } + hdr = (u8 *)alc.malloc(alc.ctx, len + YYJSON_PADDING_SIZE); + if (unlikely(!hdr)) { + return_err(0, MEMORY_ALLOCATION, MSG_MALLOC); + } + eof = hdr + len; + cur = hdr; + memcpy(hdr, dat, len); + } + memset(eof, 0, YYJSON_PADDING_SIZE); + + if (has_allow(BOM)) { + if (len >= 3 && is_utf8_bom(cur)) cur += 3; + } + + /* skip empty contents before json document */ + if (unlikely(!char_is_ctn(*cur))) { + while (char_is_space(*cur)) cur++; + if (unlikely(!char_is_ctn(*cur))) { + if (has_allow(TRIVIA) && char_is_trivia(*cur)) { + if (!skip_trivia(&cur, eof, flg) && cur == eof) { + return_err(cur - hdr, INVALID_COMMENT, MSG_COMMENT); + } + } + } + if (unlikely(cur >= eof)) { + return_err(0, EMPTY_CONTENT, "input data is empty"); + } + } + + /* read json document */ + if (likely(char_is_ctn(*cur))) { + if (char_is_space(cur[1]) && char_is_space(cur[2])) { + doc = read_root_pretty(hdr, cur, eof, alc, flg, err); + } else { + doc = read_root_minify(hdr, cur, eof, alc, flg, err); + } + } else { + doc = read_root_single(hdr, cur, eof, alc, flg, err); + } + + /* check result */ + if (likely(doc)) { + memset(err, 0, sizeof(yyjson_read_err)); + } else { + /* RFC 8259: JSON text MUST be encoded using UTF-8 */ + if (err->pos == 0 && err->code != YYJSON_READ_ERROR_MEMORY_ALLOCATION) { + if (is_utf8_bom(hdr)) err->msg = MSG_ERR_BOM; + else if (len >= 4 && is_utf32_bom(hdr)) err->msg = MSG_ERR_UTF32; + else if (len >= 2 && is_utf16_bom(hdr)) err->msg = MSG_ERR_UTF16; + } + if (!has_flg(INSITU)) alc.free(alc.ctx, hdr); + } + return doc; + +#undef return_err +} + +yyjson_doc *yyjson_read_file(const char *path, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr, + yyjson_read_err *err) { +#define return_err(_code, _msg) do { \ + err->pos = 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + return NULL; \ +} while (false) + + yyjson_read_err tmp_err; + yyjson_doc *doc; + FILE *file; + + if (!err) err = &tmp_err; + if (unlikely(!path)) return_err(INVALID_PARAMETER, "input path is NULL"); + + file = fopen_readonly(path); + if (unlikely(!file)) return_err(FILE_OPEN, MSG_FREAD); + + doc = yyjson_read_fp(file, flg, alc_ptr, err); + fclose(file); + return doc; + +#undef return_err +} + +yyjson_doc *yyjson_read_fp(FILE *file, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr, + yyjson_read_err *err) { +#define return_err(_code, _msg) do { \ + err->pos = 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + if (buf) alc.free(alc.ctx, buf); \ + return NULL; \ +} while (false) + + yyjson_read_err tmp_err; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_doc *doc; + + long file_size = 0, file_pos; + void *buf = NULL; + usize buf_size = 0; + + /* validate input parameters */ + if (!err) err = &tmp_err; + if (unlikely(!file)) return_err(INVALID_PARAMETER, "input file is NULL"); + + /* get current position */ + file_pos = ftell(file); + if (file_pos != -1) { + /* get total file size, may fail */ + if (fseek(file, 0, SEEK_END) == 0) file_size = ftell(file); + /* reset to original position, may fail */ + if (fseek(file, file_pos, SEEK_SET) != 0) file_size = 0; + /* get file size from current postion to end */ + if (file_size > 0) file_size -= file_pos; + } + + /* read file */ + if (file_size > 0) { + /* read the entire file in one call */ + buf_size = (usize)file_size + YYJSON_PADDING_SIZE; + buf = alc.malloc(alc.ctx, buf_size); + if (buf == NULL) { + return_err(MEMORY_ALLOCATION, MSG_MALLOC); + } + if (fread_safe(buf, (usize)file_size, file) != (usize)file_size) { + return_err(FILE_READ, MSG_FREAD); + } + } else { + /* failed to get file size, read it as a stream */ + usize chunk_min = (usize)64; + usize chunk_max = (usize)512 * 1024 * 1024; + usize chunk_now = chunk_min; + usize read_size; + void *tmp; + + buf_size = YYJSON_PADDING_SIZE; + while (true) { + if (buf_size + chunk_now < buf_size) { /* overflow */ + return_err(MEMORY_ALLOCATION, MSG_MALLOC); + } + buf_size += chunk_now; + if (!buf) { + buf = alc.malloc(alc.ctx, buf_size); + if (!buf) return_err(MEMORY_ALLOCATION, MSG_MALLOC); + } else { + tmp = alc.realloc(alc.ctx, buf, buf_size - chunk_now, buf_size); + if (!tmp) return_err(MEMORY_ALLOCATION, MSG_MALLOC); + buf = tmp; + } + tmp = ((u8 *)buf) + buf_size - YYJSON_PADDING_SIZE - chunk_now; + read_size = fread_safe(tmp, chunk_now, file); + file_size += (long)read_size; + if (read_size != chunk_now) break; + + chunk_now *= 2; + if (chunk_now > chunk_max) chunk_now = chunk_max; + } + } + + /* read JSON */ + memset((u8 *)buf + file_size, 0, YYJSON_PADDING_SIZE); + flg |= YYJSON_READ_INSITU; + doc = yyjson_read_opts((char *)buf, (usize)file_size, flg, &alc, err); + if (doc) { + doc->str_pool = (char *)buf; + return doc; + } else { + alc.free(alc.ctx, buf); + return NULL; + } + +#undef return_err +} + +const char *yyjson_read_number(const char *dat, + yyjson_val *val, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err) { +#define return_err(_pos, _code, _msg) do { \ + err->pos = _pos > hdr ? (usize)(_pos - hdr) : 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_##_code; \ + return NULL; \ +} while (false) + + u8 *hdr = constcast(u8 *)dat, *cur = hdr; + u8 raw_end[1]; /* raw end for null-terminator */ + u8 *raw_ptr = raw_end; + u8 **pre = &raw_ptr; /* previous raw end pointer */ + const char *msg; + yyjson_read_err tmp_err; + +#if YYJSON_DISABLE_FAST_FP_CONV + u8 buf[128]; + usize dat_len; +#endif + + if (!err) err = &tmp_err; + if (unlikely(!dat)) { + return_err(cur, INVALID_PARAMETER, "input data is NULL"); + } + if (unlikely(!val)) { + return_err(cur, INVALID_PARAMETER, "output value is NULL"); + } + +#if YYJSON_DISABLE_FAST_FP_CONV + if (!alc) alc = &YYJSON_DEFAULT_ALC; + dat_len = strlen(dat); + if (dat_len < sizeof(buf)) { + memcpy(buf, dat, dat_len + 1); + hdr = buf; + cur = hdr; + } else { + hdr = (u8 *)alc->malloc(alc->ctx, dat_len + 1); + cur = hdr; + if (unlikely(!hdr)) { + return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); + } + memcpy(hdr, dat, dat_len + 1); + } + hdr[dat_len] = 0; +#endif + +#if YYJSON_DISABLE_FAST_FP_CONV + if (!read_num(&cur, pre, flg, val, &msg)) { + if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr); + return_err(cur, INVALID_NUMBER, msg); + } + if (dat_len >= sizeof(buf)) alc->free(alc->ctx, hdr); + if (yyjson_is_raw(val)) val->uni.str = dat; + return dat + (cur - hdr); +#else + if (!read_num(&cur, pre, flg, val, &msg)) { + return_err(cur, INVALID_NUMBER, msg); + } + return (const char *)cur; +#endif + +#undef return_err +} + + + +/*============================================================================== + * MARK: - Incremental JSON Reader (Public) + *============================================================================*/ + +#if !YYJSON_DISABLE_INCR_READER + +/* labels within yyjson_incr_read() to resume incremental parsing */ +#define LABEL_doc_begin 0 +#define LABEL_arr_val_begin 1 +#define LABEL_arr_val_end 2 +#define LABEL_obj_key_begin 3 +#define LABEL_obj_key_end 4 +#define LABEL_obj_val_begin 5 +#define LABEL_obj_val_end 6 +#define LABEL_doc_end 7 + +/** State for incremental JSON reader, opaque in the API. */ +struct yyjson_incr_state { + u32 label; /* current parser goto label */ + yyjson_alc alc; /* allocator */ + yyjson_read_flag flg; /* read flags */ + u8 *hdr; /* JSON data header */ + u8 *cur; /* current position in JSON data */ + usize buf_len; /* total buffer length (without padding) */ + usize hdr_len; /* value count used by yyjson_doc */ + usize alc_len; /* value count allocated */ + usize ctn_len; /* the number of elements in current container */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val_end; /* the end of allocated values */ + yyjson_val *val; /* current JSON value */ + yyjson_val *ctn; /* current container */ + u8 *str_con[2]; /* string parser incremental state */ +}; + +yyjson_incr_state *yyjson_incr_new(char *buf, size_t buf_len, + yyjson_read_flag flg, + const yyjson_alc *alc_ptr) { + yyjson_incr_state *state = NULL; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + + /* remove non-standard flags */ + flg &= ~YYJSON_READ_JSON5; + flg &= ~YYJSON_READ_ALLOW_BOM; + flg &= ~YYJSON_READ_ALLOW_INVALID_UNICODE; + + if (unlikely(!buf)) return NULL; + if (unlikely(buf_len >= USIZE_MAX - YYJSON_PADDING_SIZE)) return NULL; + state = (yyjson_incr_state *)alc.malloc(alc.ctx, sizeof(*state)); + if (!state) return NULL; + memset(state, 0, sizeof(yyjson_incr_state)); + state->alc = alc; + state->flg = flg; + state->buf_len = buf_len; + + /* add 4-byte zero padding for input data if necessary */ + if (has_flg(INSITU)) { + state->hdr = (u8 *)buf; + } else { + state->hdr = (u8 *)alc.malloc(alc.ctx, buf_len + YYJSON_PADDING_SIZE); + if (unlikely(!state->hdr)) { + alc.free(alc.ctx, state); + return NULL; + } + memcpy(state->hdr, buf, buf_len); + } + memset(state->hdr + buf_len, 0, YYJSON_PADDING_SIZE); + state->cur = state->hdr; + state->label = LABEL_doc_begin; + return state; +} + +void yyjson_incr_free(yyjson_incr_state *state) { + if (state) { + yyjson_alc alc = state->alc; + memset(&state->alc, 0, sizeof(alc)); + if (state->val_hdr) { + alc.free(alc.ctx, (void *)state->val_hdr); + } + if (state->hdr && !(state->flg & YYJSON_READ_INSITU)) { + alc.free(alc.ctx, state->hdr); + } + alc.free(alc.ctx, state); + } +} + +yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len, + yyjson_read_err *err) { +#define return_err_inv_param(_msg) do { \ + err->pos = 0; \ + err->msg = _msg; \ + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; \ + return NULL; \ +} while (false) + +#define return_err(_pos, _code, _msg) do { \ + if (is_truncated_end(hdr, _pos, end, YYJSON_READ_ERROR_##_code, flg)) { \ + goto unexpected_end; \ + } else { \ + err->pos = (usize)(_pos - hdr); \ + err->code = YYJSON_READ_ERROR_##_code; \ + err->msg = _msg; \ + } \ + return NULL; \ +} while (false) + +#define val_incr() do { \ + val++; \ + if (unlikely(val >= val_end)) { \ + usize alc_old = alc_len; \ + alc_len += alc_len / 2; \ + if ((sizeof(usize) < 8) && (alc_len >= alc_max)) goto fail_alloc; \ + val_tmp = (yyjson_val *)alc.realloc(alc.ctx, (void *)val_hdr, \ + alc_old * sizeof(yyjson_val), \ + alc_len * sizeof(yyjson_val)); \ + if ((!val_tmp)) goto fail_alloc; \ + val = val_tmp + (usize)(val - val_hdr); \ + ctn = val_tmp + (usize)(ctn - val_hdr); \ + state->val = val_tmp + (usize)(state->val - val_hdr); \ + state->val_hdr = val_hdr = val_tmp; \ + val_end = val_tmp + (alc_len - 2); \ + state->val_end = val_end; \ + } \ +} while (false) + + /* save position where it's possible to resume incremental parsing */ +#define save_incr_state(_label) do { \ + state->label = LABEL_##_label; \ + state->cur = cur; \ + state->val = val; \ + state->ctn_len = ctn_len; \ + state->hdr_len = hdr_len; \ + if (unlikely(cur >= end)) goto unexpected_end; \ +} while (false) + +#define check_maybe_truncated_number() do { \ + if (unlikely(cur >= end)) { \ + if (unlikely(cur > state->cur + INCR_NUM_MAX_LEN)) { \ + msg = "number too long"; \ + goto fail_number; \ + } \ + goto unexpected_end; \ + } \ +} while (false) + + u8 *hdr = NULL, *end = NULL, *cur = NULL; + yyjson_read_flag flg; + yyjson_alc alc; + usize dat_len; /* data length in bytes, hint for allocator */ + usize hdr_len; /* value count used by yyjson_doc */ + usize alc_len; /* value count allocated */ + usize alc_max; /* maximum value count for allocator */ + usize ctn_len; /* the number of elements in current container */ + yyjson_val *val_hdr; /* the head of allocated values */ + yyjson_val *val_end; /* the end of allocated values */ + yyjson_val *val_tmp; /* temporary pointer for realloc */ + yyjson_val *val; /* current JSON value */ + yyjson_val *ctn; /* current container */ + yyjson_val *ctn_parent; /* parent of current container */ + yyjson_doc *doc; /* the JSON document, equals to val_hdr */ + const char *msg; /* error message */ + + yyjson_read_err tmp_err; + u8 raw_end[1]; /* raw end for null-terminator */ + u8 *raw_ptr = raw_end; + u8 **pre = &raw_ptr; /* previous raw end pointer */ + u8 **con = NULL; /* for incremental string parsing */ + u8 saved_end = '\0'; /* saved end char */ + + /* validate input parameters */ + if (!err) err = &tmp_err; + if (unlikely(!state)) { + return_err_inv_param("input state is NULL"); + } + if (unlikely(!len)) { + return_err_inv_param("input length is 0"); + } + if (unlikely(len > state->buf_len)) { + return_err_inv_param("length is greater than total input length"); + } + + /* restore state saved from the previous call */ + hdr = state->hdr; + end = state->hdr + len; + cur = state->cur; + flg = state->flg; + alc = state->alc; + ctn_len = state->ctn_len; + hdr_len = state->hdr_len; + alc_len = state->alc_len; + val = state->val; + val_hdr = state->val_hdr; + val_end = state->val_end; + ctn = state->ctn; + con = state->str_con; + alc_max = USIZE_MAX / sizeof(yyjson_val); + + /* insert null terminator to make us stop at the specified end, even if + the data contains more valid JSON */ + saved_end = *end; + *end = '\0'; + + /* resume parsing from the last save point */ + switch (state->label) { + case LABEL_doc_begin: goto doc_begin; + case LABEL_arr_val_begin: goto arr_val_begin; + case LABEL_arr_val_end: goto arr_val_end; + case LABEL_obj_key_begin: goto obj_key_begin; + case LABEL_obj_key_end: goto obj_key_end; + case LABEL_obj_val_begin: goto obj_val_begin; + case LABEL_obj_val_end: goto obj_val_end; + case LABEL_doc_end: goto doc_end; + default: return_err_inv_param("invalid incremental state"); + } + +doc_begin: + /* skip empty contents before json document */ + if (unlikely(!char_is_ctn(*cur))) { + while (char_is_space(*cur)) cur++; + if (unlikely(cur >= end)) goto unexpected_end; /* input data is empty */ + } + + /* allocate memory for document */ + if (!val_hdr) { + hdr_len = sizeof(yyjson_doc) / sizeof(yyjson_val); + hdr_len += (sizeof(yyjson_doc) % sizeof(yyjson_val)) > 0; + if (likely(char_is_ctn(*cur))) { + dat_len = has_flg(STOP_WHEN_DONE) ? 256 : state->buf_len; + alc_len = hdr_len + + (dat_len / YYJSON_READER_ESTIMATED_MINIFY_RATIO) + 4; + alc_len = yyjson_min(alc_len, alc_max); + } else { + alc_len = hdr_len + 1; /* single value */ + } + val_hdr = (yyjson_val *)alc.malloc(alc.ctx, + alc_len * sizeof(yyjson_val)); + if (unlikely(!val_hdr)) goto fail_alloc; + val_end = val_hdr + (alc_len - 2); /* padding for kv pair reading */ + val = val_hdr + hdr_len; + ctn = val; + ctn_len = 0; + state->val_hdr = val_hdr; + state->val_end = val_end; + save_incr_state(doc_begin); + } + + /* read json document */ + if (*cur == '{') { + cur++; + ctn->tag = YYJSON_TYPE_OBJ; + ctn->uni.ofs = 0; + goto obj_key_begin; + } + if (*cur == '[') { + cur++; + ctn->tag = YYJSON_TYPE_ARR; + ctn->uni.ofs = 0; + goto arr_val_begin; + } + if (char_is_num(*cur)) { + if (likely(read_num(&cur, pre, flg, val, &msg))) goto doc_end; + goto fail_number; + } + if (*cur == '"') { + if (likely(read_str_con(&cur, end, flg, val, &msg, con))) goto doc_end; + goto fail_string; + } + if (*cur == 't') { + if (likely(read_true(&cur, val))) goto doc_end; + goto fail_literal_true; + } + if (*cur == 'f') { + if (likely(read_false(&cur, val))) goto doc_end; + goto fail_literal_false; + } + if (*cur == 'n') { + if (likely(read_null(&cur, val))) goto doc_end; + goto fail_literal_null; + } + + msg = "unexpected character, expected a valid root value"; + if (cur == hdr) { + /* RFC 8259: JSON text MUST be encoded using UTF-8 */ + if (is_utf8_bom(hdr)) msg = MSG_ERR_BOM; + else if (len >= 4 && is_utf32_bom(hdr)) msg = MSG_ERR_UTF32; + else if (len >= 2 && is_utf16_bom(hdr)) msg = MSG_ERR_UTF16; + } + return_err(cur, UNEXPECTED_CHARACTER, msg); + +arr_begin: + /* save current container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + + /* create a new array value, save parent container offset */ + val_incr(); + val->tag = YYJSON_TYPE_ARR; + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + + /* push the new array value as current container */ + ctn = val; + ctn_len = 0; + +arr_val_begin: + save_incr_state(arr_val_begin); +arr_val_continue: + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (char_is_num(*cur)) { + val_incr(); + ctn_len++; + if (likely(read_num(&cur, pre, flg, val, &msg))) goto arr_val_maybe_end; + goto fail_number; + } + if (*cur == '"') { + val_incr(); + ctn_len++; + if (likely(read_str_con(&cur, end, flg, val, &msg, con))) + goto arr_val_end; + goto fail_string; + } + if (*cur == 't') { + val_incr(); + ctn_len++; + if (likely(read_true(&cur, val))) goto arr_val_end; + goto fail_literal_true; + } + if (*cur == 'f') { + val_incr(); + ctn_len++; + if (likely(read_false(&cur, val))) goto arr_val_end; + goto fail_literal_false; + } + if (*cur == 'n') { + val_incr(); + ctn_len++; + if (likely(read_null(&cur, val))) goto arr_val_end; + goto fail_literal_null; + } + if (*cur == ']') { + cur++; + if (likely(ctn_len == 0)) goto arr_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_continue; + } + goto fail_character_val; + +arr_val_maybe_end: + /* if incremental parsing stops in the middle of a number, it may continue + with more digits, so arr val maybe didn't end yet */ + check_maybe_truncated_number(); + +arr_val_end: + save_incr_state(arr_val_end); + if (*cur == ',') { + cur++; + goto arr_val_begin; + } + if (*cur == ']') { + cur++; + goto arr_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto arr_val_end; + } + goto fail_character_arr_end; + +arr_end: + /* get parent container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + + /* save the next sibling value offset */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = ((ctn_len) << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; + if (unlikely(ctn == ctn_parent)) goto doc_end; + + /* pop parent as current container */ + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +obj_begin: + /* push container */ + ctn->tag = (((u64)ctn_len + 1) << YYJSON_TAG_BIT) | + (ctn->tag & YYJSON_TAG_MASK); + val_incr(); + val->tag = YYJSON_TYPE_OBJ; + /* offset to the parent */ + val->uni.ofs = (usize)((u8 *)val - (u8 *)ctn); + ctn = val; + ctn_len = 0; + +obj_key_begin: + save_incr_state(obj_key_begin); +obj_key_continue: + if (likely(*cur == '"')) { + val_incr(); + ctn_len++; + if (likely(read_str_con(&cur, end, flg, val, &msg, con))) + goto obj_key_end; + goto fail_string; + } + if (likely(*cur == '}')) { + cur++; + if (likely(ctn_len == 0)) goto obj_end; + while (*cur != ',') cur--; + goto fail_trailing_comma; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_continue; + } + goto fail_character_obj_key; + +obj_key_end: + save_incr_state(obj_key_end); + if (*cur == ':') { + cur++; + goto obj_val_begin; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_key_end; + } + goto fail_character_obj_sep; + +obj_val_begin: + save_incr_state(obj_val_begin); +obj_val_continue: + if (*cur == '"') { + val++; + ctn_len++; + if (likely(read_str_con(&cur, end, flg, val, &msg, con))) + goto obj_val_end; + goto fail_string; + } + if (char_is_num(*cur)) { + val++; + ctn_len++; + if (likely(read_num(&cur, pre, flg, val, &msg))) goto obj_val_maybe_end; + goto fail_number; + } + if (*cur == '{') { + cur++; + goto obj_begin; + } + if (*cur == '[') { + cur++; + goto arr_begin; + } + if (*cur == 't') { + val++; + ctn_len++; + if (likely(read_true(&cur, val))) goto obj_val_end; + goto fail_literal_true; + } + if (*cur == 'f') { + val++; + ctn_len++; + if (likely(read_false(&cur, val))) goto obj_val_end; + goto fail_literal_false; + } + if (*cur == 'n') { + val++; + ctn_len++; + if (likely(read_null(&cur, val))) goto obj_val_end; + goto fail_literal_null; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_continue; + } + goto fail_character_val; + +obj_val_maybe_end: + /* if incremental parsing stops in the middle of a number, it may continue + with more digits, so obj val maybe didn't end yet */ + check_maybe_truncated_number(); + +obj_val_end: + save_incr_state(obj_val_end); + if (likely(*cur == ',')) { + cur++; + goto obj_key_begin; + } + if (likely(*cur == '}')) { + cur++; + goto obj_end; + } + if (char_is_space(*cur)) { + while (char_is_space(*++cur)); + goto obj_val_end; + } + goto fail_character_obj_end; + +obj_end: + /* pop container */ + ctn_parent = (yyjson_val *)(void *)((u8 *)ctn - ctn->uni.ofs); + /* point to the next value */ + ctn->uni.ofs = (usize)((u8 *)val - (u8 *)ctn) + sizeof(yyjson_val); + ctn->tag = (ctn_len << (YYJSON_TAG_BIT - 1)) | YYJSON_TYPE_OBJ; + if (unlikely(ctn == ctn_parent)) goto doc_end; + ctn = ctn_parent; + ctn_len = (usize)(ctn->tag >> YYJSON_TAG_BIT); + if ((ctn->tag & YYJSON_TYPE_MASK) == YYJSON_TYPE_OBJ) { + goto obj_val_end; + } else { + goto arr_val_end; + } + +doc_end: + /* check invalid contents after json document */ + if (unlikely(cur < end) && !has_flg(STOP_WHEN_DONE)) { + save_incr_state(doc_end); + while (char_is_space(*cur)) cur++; + if (unlikely(cur < end)) goto fail_garbage; + } + + **pre = '\0'; + doc = (yyjson_doc *)val_hdr; + doc->root = val_hdr + hdr_len; + doc->alc = alc; + doc->dat_read = (usize)(cur - hdr); + doc->val_read = (usize)((val - doc->root) + 1); + doc->str_pool = has_flg(INSITU) ? NULL : (char *)hdr; + state->hdr = NULL; + state->val_hdr = NULL; + memset(err, 0, sizeof(yyjson_read_err)); + return doc; + +unexpected_end: + err->pos = len; + /* if no nore data, stop the incr read */ + if (unlikely(len >= state->buf_len)) { + err->code = YYJSON_READ_ERROR_UNEXPECTED_END; + err->msg = MSG_NOT_END; + return NULL; + } + /* save parser state in extended error struct, in addition to what was + * stored in the last save_incr_state */ + err->code = YYJSON_READ_ERROR_MORE; + err->msg = "need more data"; + state->val_end = val_end; + state->ctn = ctn; + state->alc_len = alc_len; + /* restore the end where we've inserted a null terminator */ + *end = saved_end; + return NULL; + +fail_string: return_err(cur, INVALID_STRING, msg); +fail_number: return_err(cur, INVALID_NUMBER, msg); +fail_alloc: return_err(cur, MEMORY_ALLOCATION, MSG_MALLOC); +fail_trailing_comma: return_err(cur, JSON_STRUCTURE, MSG_COMMA); +fail_literal_true: return_err(cur, LITERAL, MSG_CHAR_T); +fail_literal_false: return_err(cur, LITERAL, MSG_CHAR_F); +fail_literal_null: return_err(cur, LITERAL, MSG_CHAR_N); +fail_character_val: return_err(cur, UNEXPECTED_CHARACTER, MSG_CHAR); +fail_character_arr_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_ARR_END); +fail_character_obj_key: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_KEY); +fail_character_obj_sep: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_SEP); +fail_character_obj_end: return_err(cur, UNEXPECTED_CHARACTER, MSG_OBJ_END); +fail_garbage: return_err(cur, UNEXPECTED_CONTENT, MSG_GARBAGE); + +#undef val_incr +#undef return_err +#undef return_err_inv_param +#undef save_incr_state +#undef check_maybe_truncated_number +} + +#endif /* YYJSON_DISABLE_INCR_READER */ + +#undef has_flg +#undef has_allow +#endif /* YYJSON_DISABLE_READER */ + + + +#if !YYJSON_DISABLE_WRITER /* writer begin */ + +/* Check write flag, avoids `always false` warning when disabled. */ +#define has_flg(_flg) unlikely(has_wflag(flg, YYJSON_WRITE_##_flg, 0)) +#define has_allow(_flg) unlikely(has_wflag(flg, YYJSON_WRITE_ALLOW_##_flg, 1)) +static_inline bool has_wflag(yyjson_write_flag flg, yyjson_write_flag chk, + bool non_standard) { +#if YYJSON_DISABLE_NON_STANDARD + if (non_standard) return false; +#endif + return (flg & chk) != 0; +} + +/*============================================================================== + * MARK: - Integer Writer (Private) + * + * The maximum value of uint32_t is 4294967295 (10 digits), + * these digits are named as 'aabbccddee' here. + * + * Although most compilers may convert the "division by constant value" into + * "multiply and shift", manual conversion can still help some compilers + * generate fewer and better instructions. + * + * Reference: + * Division by Invariant Integers using Multiplication, 1994. + * https://gmplib.org/~tege/divcnst-pldi94.pdf + * Improved division by invariant integers, 2011. + * https://gmplib.org/~tege/division-paper.pdf + *============================================================================*/ + +/** Digit table from 00 to 99. */ +yyjson_align(2) +static const char digit_table[200] = { + '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', + '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', + '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', + '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', + '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', + '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', + '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', + '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', + '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', + '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', + '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', + '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', + '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', + '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', + '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', + '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', + '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', + '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', + '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', + '9', '5', '9', '6', '9', '7', '9', '8', '9', '9' +}; + +static_inline u8 *write_u32_len_8(u32 val, u8 *buf) { + u32 aa, bb, cc, dd, aabb, ccdd; /* 8 digits: aabbccdd */ + aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ + ccdd = val - aabb * 10000; /* (val % 10000) */ + aa = (aabb * 5243) >> 19; /* (aabb / 100) */ + cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ + bb = aabb - aa * 100; /* (aabb % 100) */ + dd = ccdd - cc * 100; /* (ccdd % 100) */ + byte_copy_2(buf + 0, digit_table + aa * 2); + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + byte_copy_2(buf + 6, digit_table + dd * 2); + return buf + 8; +} + +static_inline u8 *write_u32_len_4(u32 val, u8 *buf) { + u32 aa, bb; /* 4 digits: aabb */ + aa = (val * 5243) >> 19; /* (val / 100) */ + bb = val - aa * 100; /* (val % 100) */ + byte_copy_2(buf + 0, digit_table + aa * 2); + byte_copy_2(buf + 2, digit_table + bb * 2); + return buf + 4; +} + +static_inline u8 *write_u32_len_1_to_8(u32 val, u8 *buf) { + u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; + + if (val < 100) { /* 1-2 digits: aa */ + lz = val < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + val * 2 + lz); + buf -= lz; + return buf + 2; + + } else if (val < 10000) { /* 3-4 digits: aabb */ + aa = (val * 5243) >> 19; /* (val / 100) */ + bb = val - aa * 100; /* (val % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + return buf + 4; + + } else if (val < 1000000) { /* 5-6 digits: aabbcc */ + aa = (u32)(((u64)val * 429497) >> 32); /* (val / 10000) */ + bbcc = val - aa * 10000; /* (val % 10000) */ + bb = (bbcc * 5243) >> 19; /* (bbcc / 100) */ + cc = bbcc - bb * 100; /* (bbcc % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + return buf + 6; + + } else { /* 7-8 digits: aabbccdd */ + aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ + ccdd = val - aabb * 10000; /* (val % 10000) */ + aa = (aabb * 5243) >> 19; /* (aabb / 100) */ + cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ + bb = aabb - aa * 100; /* (aabb % 100) */ + dd = ccdd - cc * 100; /* (ccdd % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + byte_copy_2(buf + 6, digit_table + dd * 2); + return buf + 8; + } +} + +static_inline u8 *write_u32_len_5_to_8(u32 val, u8 *buf) { + u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; + + if (val < 1000000) { /* 5-6 digits: aabbcc */ + aa = (u32)(((u64)val * 429497) >> 32); /* (val / 10000) */ + bbcc = val - aa * 10000; /* (val % 10000) */ + bb = (bbcc * 5243) >> 19; /* (bbcc / 100) */ + cc = bbcc - bb * 100; /* (bbcc % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + return buf + 6; + + } else { /* 7-8 digits: aabbccdd */ + aabb = (u32)(((u64)val * 109951163) >> 40); /* (val / 10000) */ + ccdd = val - aabb * 10000; /* (val % 10000) */ + aa = (aabb * 5243) >> 19; /* (aabb / 100) */ + cc = (ccdd * 5243) >> 19; /* (ccdd / 100) */ + bb = aabb - aa * 100; /* (aabb % 100) */ + dd = ccdd - cc * 100; /* (ccdd % 100) */ + lz = aa < 10; /* leading zero: 0 or 1 */ + byte_copy_2(buf + 0, digit_table + aa * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + bb * 2); + byte_copy_2(buf + 4, digit_table + cc * 2); + byte_copy_2(buf + 6, digit_table + dd * 2); + return buf + 8; + } +} + +static_inline u8 *write_u64(u64 val, u8 *buf) { + u64 tmp, hgh; + u32 mid, low; + + if (val < 100000000) { /* 1-8 digits */ + buf = write_u32_len_1_to_8((u32)val, buf); + return buf; + + } else if (val < (u64)100000000 * 100000000) { /* 9-16 digits */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + buf = write_u32_len_1_to_8((u32)hgh, buf); + buf = write_u32_len_8(low, buf); + return buf; + + } else { /* 17-20 digits */ + tmp = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - tmp * 100000000); /* (val % 100000000) */ + hgh = (u32)(tmp / 10000); /* (tmp / 10000) */ + mid = (u32)(tmp - hgh * 10000); /* (tmp % 10000) */ + buf = write_u32_len_5_to_8((u32)hgh, buf); + buf = write_u32_len_4(mid, buf); + buf = write_u32_len_8(low, buf); + return buf; + } +} + + + +/*============================================================================== + * MARK: - Number Writer (Private) + *============================================================================*/ + +#if !YYJSON_DISABLE_FAST_FP_CONV /* FP_WRITER */ + +/** Trailing zero count table for number 0 to 99. + (generate with misc/make_tables.c) */ +static const u8 dec_trailing_zero_table[] = { + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static_inline u8 *write_u64_len_1_to_16(u64 val, u8 *buf) { + u64 hgh; + u32 low; + if (val < 100000000) { /* 1-8 digits */ + buf = write_u32_len_1_to_8((u32)val, buf); + return buf; + } else { /* 9-16 digits */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + buf = write_u32_len_1_to_8((u32)hgh, buf); + buf = write_u32_len_8(low, buf); + return buf; + } +} + +static_inline u8 *write_u64_len_1_to_17(u64 val, u8 *buf) { + u64 hgh; + u32 mid, low, one; + if (val >= (u64)100000000 * 10000000) { /* len: 16 to 17 */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + one = (u32)(hgh / 100000000); /* (hgh / 100000000) */ + mid = (u32)(hgh - (u64)one * 100000000); /* (hgh % 100000000) */ + *buf = (u8)((u8)one + (u8)'0'); + buf += one > 0; + buf = write_u32_len_8(mid, buf); + buf = write_u32_len_8(low, buf); + return buf; + } else if (val >= (u64)100000000){ /* len: 9 to 15 */ + hgh = val / 100000000; /* (val / 100000000) */ + low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ + buf = write_u32_len_1_to_8((u32)hgh, buf); + buf = write_u32_len_8(low, buf); + return buf; + } else { /* len: 1 to 8 */ + buf = write_u32_len_1_to_8((u32)val, buf); + return buf; + } +} + +/** + Write an unsigned integer with a length of 7 to 9 with trailing zero trimmed. + These digits are named as "abbccddee" here. + For example, input 123456000, output "123456". + */ +static_inline u8 *write_u32_len_7_to_9_trim(u32 val, u8 *buf) { + bool lz; + u32 tz, tz1, tz2; + + u32 abbcc = val / 10000; /* (abbccddee / 10000) */ + u32 ddee = val - abbcc * 10000; /* (abbccddee % 10000) */ + u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ + u32 a = (abb * 41) >> 12; /* (abb / 100) */ + u32 bb = abb - a * 100; /* (abb % 100) */ + u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ + + /* write abbcc */ + buf[0] = (u8)(a + '0'); + buf += a > 0; + lz = bb < 10 && a == 0; + byte_copy_2(buf + 0, digit_table + bb * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + cc * 2); + + if (ddee) { + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ + byte_copy_2(buf + 4, digit_table + dd * 2); + byte_copy_2(buf + 6, digit_table + ee * 2); + tz1 = dec_trailing_zero_table[dd]; + tz2 = dec_trailing_zero_table[ee]; + tz = ee ? tz2 : (tz1 + 2); + buf += 8 - tz; + return buf; + } else { + tz1 = dec_trailing_zero_table[bb]; + tz2 = dec_trailing_zero_table[cc]; + tz = cc ? tz2 : (tz1 + tz2); + buf += 4 - tz; + return buf; + } +} + +/** + Write an unsigned integer with a length of 16 or 17 with trailing zero trimmed. + These digits are named as "abbccddeeffgghhii" here. + For example, input 1234567890123000, output "1234567890123". + */ +static_inline u8 *write_u64_len_16_to_17_trim(u64 val, u8 *buf) { + u32 tz, tz1, tz2; + + u32 abbccddee = (u32)(val / 100000000); + u32 ffgghhii = (u32)(val - (u64)abbccddee * 100000000); + u32 abbcc = abbccddee / 10000; + u32 ddee = abbccddee - abbcc * 10000; + u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ + u32 a = (abb * 41) >> 12; /* (abb / 100) */ + u32 bb = abb - a * 100; /* (abb % 100) */ + u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ + buf[0] = (u8)(a + '0'); + buf += a > 0; + byte_copy_2(buf + 0, digit_table + bb * 2); + byte_copy_2(buf + 2, digit_table + cc * 2); + + if (ffgghhii) { + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ + u32 ffgg = (u32)(((u64)ffgghhii * 109951163) >> 40); /* (val / 10000) */ + u32 hhii = ffgghhii - ffgg * 10000; /* (val % 10000) */ + u32 ff = (ffgg * 5243) >> 19; /* (aabb / 100) */ + u32 gg = ffgg - ff * 100; /* (aabb % 100) */ + byte_copy_2(buf + 4, digit_table + dd * 2); + byte_copy_2(buf + 6, digit_table + ee * 2); + byte_copy_2(buf + 8, digit_table + ff * 2); + byte_copy_2(buf + 10, digit_table + gg * 2); + if (hhii) { + u32 hh = (hhii * 5243) >> 19; /* (ccdd / 100) */ + u32 ii = hhii - hh * 100; /* (ccdd % 100) */ + byte_copy_2(buf + 12, digit_table + hh * 2); + byte_copy_2(buf + 14, digit_table + ii * 2); + tz1 = dec_trailing_zero_table[hh]; + tz2 = dec_trailing_zero_table[ii]; + tz = ii ? tz2 : (tz1 + 2); + return buf + 16 - tz; + } else { + tz1 = dec_trailing_zero_table[ff]; + tz2 = dec_trailing_zero_table[gg]; + tz = gg ? tz2 : (tz1 + 2); + return buf + 12 - tz; + } + } else { + if (ddee) { + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ + byte_copy_2(buf + 4, digit_table + dd * 2); + byte_copy_2(buf + 6, digit_table + ee * 2); + tz1 = dec_trailing_zero_table[dd]; + tz2 = dec_trailing_zero_table[ee]; + tz = ee ? tz2 : (tz1 + 2); + return buf + 8 - tz; + } else { + tz1 = dec_trailing_zero_table[bb]; + tz2 = dec_trailing_zero_table[cc]; + tz = cc ? tz2 : (tz1 + tz2); + return buf + 4 - tz; + } + } +} + +/** Write exponent part in range `e-45` to `e38`. */ +static_inline u8 *write_f32_exp(i32 exp, u8 *buf) { + bool lz; + byte_copy_2(buf, "e-"); + buf += 2 - (exp >= 0); + exp = exp < 0 ? -exp : exp; + lz = exp < 10; + byte_copy_2(buf + 0, digit_table + (u32)exp * 2 + lz); + return buf + 2 - lz; +} + +/** Write exponent part in range `e-324` to `e308`. */ +static_inline u8 *write_f64_exp(i32 exp, u8 *buf) { + byte_copy_2(buf, "e-"); + buf += 2 - (exp >= 0); + exp = exp < 0 ? -exp : exp; + if (exp < 100) { + bool lz = exp < 10; + byte_copy_2(buf + 0, digit_table + (u32)exp * 2 + lz); + return buf + 2 - lz; + } else { + u32 hi = ((u32)exp * 656) >> 16; /* exp / 100 */ + u32 lo = (u32)exp - hi * 100; /* exp % 100 */ + buf[0] = (u8)((u8)hi + (u8)'0'); + byte_copy_2(buf + 1, digit_table + lo * 2); + return buf + 3; + } +} + +/** Magic number for fast `divide by power of 10`. */ +typedef struct { + u64 p10, mul; + u32 shr1, shr2; +} div_pow10_magic; + +/** Generated with llvm, see https://github.com/llvm/llvm-project/ + blob/main/llvm/lib/Support/DivisionByConstantInfo.cpp */ +static const div_pow10_magic div_pow10_table[] = { + { U64(0x00000000, 0x00000001), U64(0x00000000, 0x00000000), 0, 0 }, + { U64(0x00000000, 0x0000000A), U64(0xCCCCCCCC, 0xCCCCCCCD), 0, 3 }, + { U64(0x00000000, 0x00000064), U64(0x28F5C28F, 0x5C28F5C3), 2, 2 }, + { U64(0x00000000, 0x000003E8), U64(0x20C49BA5, 0xE353F7CF), 3, 4 }, + { U64(0x00000000, 0x00002710), U64(0x346DC5D6, 0x3886594B), 0, 11 }, + { U64(0x00000000, 0x000186A0), U64(0x0A7C5AC4, 0x71B47843), 5, 7 }, + { U64(0x00000000, 0x000F4240), U64(0x431BDE82, 0xD7B634DB), 0, 18 }, + { U64(0x00000000, 0x00989680), U64(0xD6BF94D5, 0xE57A42BD), 0, 23 }, + { U64(0x00000000, 0x05F5E100), U64(0xABCC7711, 0x8461CEFD), 0, 26 }, + { U64(0x00000000, 0x3B9ACA00), U64(0x0044B82F, 0xA09B5A53), 9, 11 }, + { U64(0x00000002, 0x540BE400), U64(0xDBE6FECE, 0xBDEDD5BF), 0, 33 }, + { U64(0x00000017, 0x4876E800), U64(0xAFEBFF0B, 0xCB24AAFF), 0, 36 }, + { U64(0x000000E8, 0xD4A51000), U64(0x232F3302, 0x5BD42233), 0, 37 }, + { U64(0x00000918, 0x4E72A000), U64(0x384B84D0, 0x92ED0385), 0, 41 }, + { U64(0x00005AF3, 0x107A4000), U64(0x0B424DC3, 0x5095CD81), 0, 42 }, + { U64(0x00038D7E, 0xA4C68000), U64(0x00024075, 0xF3DCEAC3), 15, 20 }, + { U64(0x002386F2, 0x6FC10000), U64(0x39A5652F, 0xB1137857), 0, 51 }, + { U64(0x01634578, 0x5D8A0000), U64(0x00005C3B, 0xD5191B53), 17, 22 }, + { U64(0x0DE0B6B3, 0xA7640000), U64(0x000049C9, 0x7747490F), 18, 24 }, + { U64(0x8AC72304, 0x89E80000), U64(0x760F253E, 0xDB4AB0d3), 0, 62 }, +}; + +/** Divide a number by power of 10. */ +static_inline void div_pow10(u64 num, u32 exp, u64 *div, u64 *mod, u64 *p10) { + u64 hi, lo; + div_pow10_magic m = div_pow10_table[exp]; + u128_mul(num >> m.shr1, m.mul, &hi, &lo); + *div = hi >> m.shr2; + *mod = num - (*div * m.p10); + *p10 = m.p10; +} + +/** Multiplies 64-bit integer and returns highest 64-bit rounded value. */ +static_inline u32 u64_round_to_odd(u64 u, u32 cp) { + u64 hi, lo; + u32 y_hi, y_lo; + u128_mul(cp, u, &hi, &lo); + y_hi = (u32)hi; + y_lo = (u32)(lo >> 32); + return y_hi | (y_lo > 1); +} + +/** Multiplies 128-bit integer and returns highest 64-bit rounded value. */ +static_inline u64 u128_round_to_odd(u64 hi, u64 lo, u64 cp) { + u64 x_hi, x_lo, y_hi, y_lo; + u128_mul(cp, lo, &x_hi, &x_lo); + u128_mul_add(cp, hi, x_hi, &y_hi, &y_lo); + return y_hi | (y_lo > 1); +} + +/** Convert f32 from binary to decimal (shortest but may have trailing zeros). + The input should not be 0, inf or nan. */ +static_inline void f32_bin_to_dec(u32 sig_raw, u32 exp_raw, + u32 sig_bin, i32 exp_bin, + u32 *sig_dec, i32 *exp_dec) { + + bool is_even, irregular, round_up, trim; + bool u0_inside, u1_inside, w0_inside, w1_inside; + u64 p10_hi, p10_lo, hi, lo; + u32 s, sp, cb, cbl, cbr, vb, vbl, vbr, upper, lower, mid; + i32 k, h; + + /* Fast path, see f64_bin_to_dec(). */ + while (likely(sig_raw)) { + u32 mod, dec, add_1, add_10, s_hi, s_lo; + u32 c, half_ulp, t0, t1; + + /* k = floor(exp_bin * log10(2)); */ + /* h = exp_bin + floor(log2(10) * -k); (h = 0/1/2/3) */ + k = (i32)(exp_bin * 315653) >> 20; + h = exp_bin + ((-k * 217707) >> 16); + pow10_table_get_sig(-k, &p10_hi, &p10_lo); + + /* sig_bin << (1/2/3/4) */ + cb = sig_bin << (h + 1); + u128_mul(cb, p10_hi, &hi, &lo); + s_hi = (u32)(hi); + s_lo = (u32)(lo >> 32); + mod = s_hi % 10; + dec = s_hi - mod; + + /* right shift 4 to fit in u32 */ + c = (mod << (32 - 4)) | (s_lo >> 4); + half_ulp = (u32)(p10_hi >> (32 + 4 - h)); + + /* check w1, u0, w0 range */ + w1_inside = (s_lo >= ((u32)1 << 31)); + if (unlikely(s_lo == ((u32)1 << 31))) break; + u0_inside = (half_ulp >= c); + if (unlikely(half_ulp == c)) break; + t0 = (u32)10 << (32 - 4); + t1 = c + half_ulp; + w0_inside = (t1 >= t0); + if (unlikely(t0 - t1 <= (u32)1)) break; + + trim = (u0_inside | w0_inside); + add_10 = (w0_inside ? 10 : 0); + add_1 = mod + w1_inside; + *sig_dec = dec + (trim ? add_10 : add_1); + *exp_dec = k; + return; + } + + /* Schubfach algorithm, see f64_bin_to_dec(). */ + irregular = (sig_raw == 0 && exp_raw > 1); + is_even = !(sig_bin & 1); + cbl = 4 * sig_bin - 2 + irregular; + cb = 4 * sig_bin; + cbr = 4 * sig_bin + 2; + + /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */ + /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */ + k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; + h = exp_bin + ((-k * 217707) >> 16) + 1; + pow10_table_get_sig(-k, &p10_hi, &p10_lo); + p10_hi += 1; + + vbl = u64_round_to_odd(p10_hi, cbl << h); + vb = u64_round_to_odd(p10_hi, cb << h); + vbr = u64_round_to_odd(p10_hi, cbr << h); + lower = vbl + !is_even; + upper = vbr - !is_even; + + s = vb / 4; + if (s >= 10) { + sp = s / 10; + u0_inside = (lower <= 40 * sp); + w0_inside = (upper >= 40 * sp + 40); + if (u0_inside != w0_inside) { + *sig_dec = sp * 10 + (w0_inside ? 10 : 0); + *exp_dec = k; + return; + } + } + u1_inside = (lower <= 4 * s); + w1_inside = (upper >= 4 * s + 4); + mid = 4 * s + 2; + round_up = (vb > mid) || (vb == mid && (s & 1) != 0); + *sig_dec = s + ((u1_inside != w1_inside) ? w1_inside : round_up); + *exp_dec = k; +} + +/** Convert f64 from binary to decimal (shortest but may have trailing zeros). + The input should not be 0, inf or nan. */ +static_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw, + u64 sig_bin, i32 exp_bin, + u64 *sig_dec, i32 *exp_dec) { + + bool is_even, irregular, round_up, trim; + bool u0_inside, u1_inside, w0_inside, w1_inside; + u64 s, sp, cb, cbl, cbr, vb, vbl, vbr, p10_hi, p10_lo, upper, lower, mid; + i32 k, h; + + /* + Fast path: + For regular spacing significand 'c', there are 4 candidates: + + u0 u1 c w1 w0 + ----|----|----|----|----|-*--|----|----|----|----|----|----|----|---- + 9 0 1 2 3 4 5 6 7 8 9 0 1 + |___________________|___________________| + 1ulp + + The `1ulp` is in the range [1.0, 10.0). + If (c - 0.5ulp < u0), trim the last digit and round down. + If (c + 0.5ulp > w0), trim the last digit and round up. + If (c - 0.5ulp < u1), round down. + If (c + 0.5ulp > w1), round up. + */ + while (likely(sig_raw)) { + u64 mod, dec, add_1, add_10, s_hi, s_lo; + u64 c, half_ulp, t0, t1; + + /* k = floor(exp_bin * log10(2)); */ + /* h = exp_bin + floor(log2(10) * -k); (h = 0/1/2/3) */ + k = (i32)(exp_bin * 315653) >> 20; + h = exp_bin + ((-k * 217707) >> 16); + pow10_table_get_sig(-k, &p10_hi, &p10_lo); + + /* sig_bin << (1/2/3/4) */ + cb = sig_bin << (h + 1); + u128_mul(cb, p10_lo, &s_hi, &s_lo); + u128_mul_add(cb, p10_hi, s_hi, &s_hi, &s_lo); + mod = s_hi % 10; + dec = s_hi - mod; + + /* right shift 4 to fit in u64 */ + c = (mod << (64 - 4)) | (s_lo >> 4); + half_ulp = p10_hi >> (4 - h); + + /* check w1, u0, w0 range */ + w1_inside = (s_lo >= ((u64)1 << 63)); + if (unlikely(s_lo == ((u64)1 << 63))) break; + u0_inside = (half_ulp >= c); + if (unlikely(half_ulp == c)) break; + t0 = ((u64)10 << (64 - 4)); + t1 = c + half_ulp; + w0_inside = (t1 >= t0); + if (unlikely(t0 - t1 <= (u64)1)) break; + + trim = (u0_inside | w0_inside); + add_10 = (w0_inside ? 10 : 0); + add_1 = mod + w1_inside; + *sig_dec = dec + (trim ? add_10 : add_1); + *exp_dec = k; + return; + } + + /* + Schubfach algorithm: + Raffaello Giulietti, The Schubfach way to render doubles, 2022. + https://drive.google.com/file/d/1gp5xv4CAa78SVgCeWfGqqI4FfYYYuNFb (Paper) + https://github.com/openjdk/jdk/pull/3402 (Java implementation) + https://github.com/abolz/Drachennest (C++ implementation) + */ + irregular = (sig_raw == 0 && exp_raw > 1); + is_even = !(sig_bin & 1); + cbl = 4 * sig_bin - 2 + irregular; + cb = 4 * sig_bin; + cbr = 4 * sig_bin + 2; + + /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */ + /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */ + k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; + h = exp_bin + ((-k * 217707) >> 16) + 1; + pow10_table_get_sig(-k, &p10_hi, &p10_lo); + p10_lo += 1; + + vbl = u128_round_to_odd(p10_hi, p10_lo, cbl << h); + vb = u128_round_to_odd(p10_hi, p10_lo, cb << h); + vbr = u128_round_to_odd(p10_hi, p10_lo, cbr << h); + lower = vbl + !is_even; + upper = vbr - !is_even; + + s = vb / 4; + if (s >= 10) { + sp = s / 10; + u0_inside = (lower <= 40 * sp); + w0_inside = (upper >= 40 * sp + 40); + if (u0_inside != w0_inside) { + *sig_dec = sp * 10 + (w0_inside ? 10 : 0); + *exp_dec = k; + return; + } + } + u1_inside = (lower <= 4 * s); + w1_inside = (upper >= 4 * s + 4); + mid = 4 * s + 2; + round_up = (vb > mid) || (vb == mid && (s & 1) != 0); + *sig_dec = s + ((u1_inside != w1_inside) ? w1_inside : round_up); + *exp_dec = k; +} + +/** Convert f64 from binary to decimal (fast but not the shortest). + The input should not be 0, inf, nan. */ +static_inline void f64_bin_to_dec_fast(u64 sig_raw, u32 exp_raw, + u64 sig_bin, i32 exp_bin, + u64 *sig_dec, i32 *exp_dec, + bool *round_up) { + u64 cb, p10_hi, p10_lo, s_hi, s_lo; + i32 k, h; + bool irregular, u; + + irregular = (sig_raw == 0 && exp_raw > 1); + + /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */ + /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */ + k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; + h = exp_bin + ((-k * 217707) >> 16); + pow10_table_get_sig(-k, &p10_hi, &p10_lo); + + /* sig_bin << (1/2/3/4) */ + cb = sig_bin << (h + 1); + u128_mul(cb, p10_lo, &s_hi, &s_lo); + u128_mul_add(cb, p10_hi, s_hi, &s_hi, &s_lo); + + /* round up */ + u = s_lo >= (irregular ? U64(0x55555555, 0x55555555) : ((u64)1 << 63)); + + *sig_dec = s_hi + u; + *exp_dec = k; + *round_up = u; + return; +} + +/** Write inf/nan if allowed. */ +static_inline u8 *write_inf_or_nan(u8 *buf, yyjson_write_flag flg, + u64 sig_raw, bool sign) { + if (has_flg(INF_AND_NAN_AS_NULL)) { + byte_copy_4(buf, "null"); + return buf + 4; + } + if (has_allow(INF_AND_NAN)) { + if (sig_raw == 0) { + buf[0] = '-'; + buf += sign; + byte_copy_8(buf, "Infinity"); + return buf + 8; + } else { + byte_copy_4(buf, "NaN"); + return buf + 3; + } + } + return NULL; +} + +/** + Write a float number (requires 40 bytes buffer). + We follow the ECMAScript specification for printing floating-point numbers, + similar to `Number.prototype.toString()`, but with the following changes: + 1. Keep the negative sign of `-0.0` to preserve input information. + 2. Keep decimal point to indicate the number is floating point. + 3. Remove positive sign in the exponent part. + */ +static_noinline u8 *write_f32_raw(u8 *buf, u64 raw_f64, + yyjson_write_flag flg) { + u32 sig_bin, sig_dec, sig_raw; + i32 exp_bin, exp_dec, sig_len, dot_ofs; + u32 exp_raw, raw; + u8 *end; + bool sign; + + /* cast double to float */ + raw = f32_to_bits(f64_to_f32(f64_from_bits(raw_f64))); + + /* decode raw bytes from IEEE-754 double format. */ + sign = (bool)(raw >> (F32_BITS - 1)); + sig_raw = raw & F32_SIG_MASK; + exp_raw = (raw & F32_EXP_MASK) >> F32_SIG_BITS; + + /* return inf or nan */ + if (unlikely(exp_raw == ((u32)1 << F32_EXP_BITS) - 1)) { + return write_inf_or_nan(buf, flg, sig_raw, sign); + } + + /* add sign for all finite number */ + buf[0] = '-'; + buf += sign; + + /* return zero */ + if ((raw << 1) == 0) { + byte_copy_4(buf, "0.0"); + return buf + 3; + } + + if (likely(exp_raw != 0)) { + /* normal number */ + sig_bin = sig_raw | ((u32)1 << F32_SIG_BITS); + exp_bin = (i32)exp_raw - F32_EXP_BIAS - F32_SIG_BITS; + + /* fast path for small integer number without fraction */ + if ((-F32_SIG_BITS <= exp_bin && exp_bin <= 0) && + (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) { + sig_dec = sig_bin >> -exp_bin; /* range: [1, 0xFFFFFF] */ + buf = write_u32_len_1_to_8(sig_dec, buf); + byte_copy_2(buf, ".0"); + return buf + 2; + } + + /* binary to decimal */ + f32_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* the sig length is 7 or 9 */ + sig_len = 7 + (sig_dec >= (u32)10000000) + (sig_dec >= (u32)100000000); + + /* the decimal point offset relative to the first digit */ + dot_ofs = sig_len + exp_dec; + + if (-6 < dot_ofs && dot_ofs <= 21) { + i32 num_sep_pos, dot_set_pos, pre_ofs; + u8 *num_hdr, *num_end, *num_sep, *dot_end; + bool no_pre_zero; + + /* fill zeros */ + memset(buf, '0', 32); + + /* not prefixed with zero, e.g. 1.234, 1234.0 */ + no_pre_zero = (dot_ofs > 0); + + /* write the number as digits */ + pre_ofs = no_pre_zero ? 0 : (2 - dot_ofs); + num_hdr = buf + pre_ofs; + num_end = write_u32_len_7_to_9_trim(sig_dec, num_hdr); + + /* seperate these digits to leave a space for dot */ + num_sep_pos = no_pre_zero ? dot_ofs : 0; + num_sep = num_hdr + num_sep_pos; + byte_move_8(num_sep + no_pre_zero, num_sep); + num_end += no_pre_zero; + + /* write the dot */ + dot_set_pos = yyjson_max(dot_ofs, 1); + buf[dot_set_pos] = '.'; + + /* return the ending */ + dot_end = buf + dot_ofs + 2; + return yyjson_max(dot_end, num_end); + + } else { + /* write with scientific notation, e.g. 1.234e56 */ + end = write_u32_len_7_to_9_trim(sig_dec, buf + 1); + end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ + exp_dec += sig_len - 1; + buf[0] = buf[1]; + buf[1] = '.'; + return write_f32_exp(exp_dec, end); + } + + } else { + /* subnormal number */ + sig_bin = sig_raw; + exp_bin = 1 - F32_EXP_BIAS - F32_SIG_BITS; + + /* binary to decimal */ + f32_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* write significand part */ + end = write_u32_len_1_to_8(sig_dec, buf + 1); + buf[0] = buf[1]; + buf[1] = '.'; + exp_dec += (i32)(end - buf) - 2; + + /* trim trailing zeros */ + end -= *(end - 1) == '0'; /* branchless for last zero */ + end -= *(end - 1) == '0'; /* branchless for second last zero */ + while (*(end - 1) == '0') end--; /* for unlikely more zeros */ + end -= *(end - 1) == '.'; /* remove dot, e.g. 2.e-321 -> 2e-321 */ + + /* write exponent part */ + return write_f32_exp(exp_dec, end); + } +} + +/** + Write a double number (requires 40 bytes buffer). + We follow the ECMAScript specification for printing floating-point numbers, + similar to `Number.prototype.toString()`, but with the following changes: + 1. Keep the negative sign of `-0.0` to preserve input information. + 2. Keep decimal point to indicate the number is floating point. + 3. Remove positive sign in the exponent part. + */ +static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { + u64 sig_bin, sig_dec, sig_raw; + i32 exp_bin, exp_dec, sig_len, dot_ofs; + u32 exp_raw; + u8 *end; + bool sign; + + /* decode raw bytes from IEEE-754 double format. */ + sign = (bool)(raw >> (F64_BITS - 1)); + sig_raw = raw & F64_SIG_MASK; + exp_raw = (u32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); + + /* return inf or nan */ + if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) { + return write_inf_or_nan(buf, flg, sig_raw, sign); + } + + /* add sign for all finite number */ + buf[0] = '-'; + buf += sign; + + /* return zero */ + if ((raw << 1) == 0) { + byte_copy_4(buf, "0.0"); + return buf + 3; + } + + if (likely(exp_raw != 0)) { + /* normal number */ + sig_bin = sig_raw | ((u64)1 << F64_SIG_BITS); + exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS; + + /* fast path for small integer number without fraction */ + if ((-F64_SIG_BITS <= exp_bin && exp_bin <= 0) && + (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) { + sig_dec = sig_bin >> -exp_bin; /* range: [1, 0x1FFFFFFFFFFFFF] */ + buf = write_u64_len_1_to_16(sig_dec, buf); + byte_copy_2(buf, ".0"); + return buf + 2; + } + + /* binary to decimal */ + f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* the sig length is 16 or 17 */ + sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); + + /* the decimal point offset relative to the first digit */ + dot_ofs = sig_len + exp_dec; + + if (-6 < dot_ofs && dot_ofs <= 21) { + i32 num_sep_pos, dot_set_pos, pre_ofs; + u8 *num_hdr, *num_end, *num_sep, *dot_end; + bool no_pre_zero; + + /* fill zeros */ + memset(buf, '0', 32); + + /* not prefixed with zero, e.g. 1.234, 1234.0 */ + no_pre_zero = (dot_ofs > 0); + + /* write the number as digits */ + pre_ofs = no_pre_zero ? 0 : (2 - dot_ofs); + num_hdr = buf + pre_ofs; + num_end = write_u64_len_16_to_17_trim(sig_dec, num_hdr); + + /* seperate these digits to leave a space for dot */ + num_sep_pos = no_pre_zero ? dot_ofs : 0; + num_sep = num_hdr + num_sep_pos; + byte_move_16(num_sep + no_pre_zero, num_sep); + num_end += no_pre_zero; + + /* write the dot */ + dot_set_pos = yyjson_max(dot_ofs, 1); + buf[dot_set_pos] = '.'; + + /* return the ending */ + dot_end = buf + dot_ofs + 2; + return yyjson_max(dot_end, num_end); + + } else { + /* write with scientific notation, e.g. 1.234e56 */ + end = write_u64_len_16_to_17_trim(sig_dec, buf + 1); + end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ + exp_dec += sig_len - 1; + buf[0] = buf[1]; + buf[1] = '.'; + return write_f64_exp(exp_dec, end); + } + + } else { + /* subnormal number */ + sig_bin = sig_raw; + exp_bin = 1 - F64_EXP_BIAS - F64_SIG_BITS; + + /* binary to decimal */ + f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* write significand part */ + end = write_u64_len_1_to_17(sig_dec, buf + 1); + buf[0] = buf[1]; + buf[1] = '.'; + exp_dec += (i32)(end - buf) - 2; + + /* trim trailing zeros */ + end -= *(end - 1) == '0'; /* branchless for last zero */ + end -= *(end - 1) == '0'; /* branchless for second last zero */ + while (*(end - 1) == '0') end--; /* for unlikely more zeros */ + end -= *(end - 1) == '.'; /* remove dot, e.g. 2.e-321 -> 2e-321 */ + + /* write exponent part */ + return write_f64_exp(exp_dec, end); + } +} + +/** + Write a double number using fixed-point notation (requires 40 bytes buffer). + + We follow the ECMAScript specification for printing floating-point numbers, + similar to `Number.prototype.toFixed(prec)`, but with the following changes: + 1. Keep the negative sign of `-0.0` to preserve input information. + 2. Keep decimal point to indicate the number is floating point. + 3. Remove positive sign in the exponent part. + 4. Remove trailing zeros and reduce unnecessary precision. + */ +static_noinline u8 *write_f64_raw_fixed(u8 *buf, u64 raw, yyjson_write_flag flg, + u32 prec) { + u64 sig_bin, sig_dec, sig_raw; + i32 exp_bin, exp_dec, sig_len, dot_ofs; + u32 exp_raw; + u8 *end; + bool sign; + + /* decode raw bytes from IEEE-754 double format. */ + sign = (bool)(raw >> (F64_BITS - 1)); + sig_raw = raw & F64_SIG_MASK; + exp_raw = (u32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); + + /* return inf or nan */ + if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) { + return write_inf_or_nan(buf, flg, sig_raw, sign); + } + + /* add sign for all finite number */ + buf[0] = '-'; + buf += sign; + + /* return zero */ + if ((raw << 1) == 0) { + byte_copy_4(buf, "0.0"); + return buf + 3; + } + + if (likely(exp_raw != 0)) { + /* normal number */ + sig_bin = sig_raw | ((u64)1 << F64_SIG_BITS); + exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS; + + /* fast path for small integer number without fraction */ + if ((-F64_SIG_BITS <= exp_bin && exp_bin <= 0) && + (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) { + sig_dec = sig_bin >> -exp_bin; /* range: [1, 0x1FFFFFFFFFFFFF] */ + buf = write_u64_len_1_to_16(sig_dec, buf); + byte_copy_2(buf, ".0"); + return buf + 2; + } + + /* only `fabs(num) < 1e21` are processed here. */ + if ((raw << 1) < (U64(0x444B1AE4, 0xD6E2EF50) << 1)) { + i32 num_sep_pos, dot_set_pos, pre_ofs; + u8 *num_hdr, *num_end, *num_sep; + bool round_up, no_pre_zero; + + /* binary to decimal */ + f64_bin_to_dec_fast(sig_raw, exp_raw, sig_bin, exp_bin, + &sig_dec, &exp_dec, &round_up); + + /* the sig length is 16 or 17 */ + sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); + + /* limit the length of digits after the decimal point */ + if (exp_dec < -1) { + i32 sig_len_cut = -exp_dec - (i32)prec; + if (sig_len_cut > sig_len) { + byte_copy_4(buf, "0.0"); + return buf + 3; + } + if (sig_len_cut > 0) { + u64 div, mod, p10; + + /* remove round up */ + sig_dec -= round_up; + sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); + + /* cut off some digits */ + div_pow10(sig_dec, (u32)sig_len_cut, &div, &mod, &p10); + + /* add round up */ + sig_dec = div + (mod >= p10 / 2); + + /* update exp and sig length */ + exp_dec += sig_len_cut; + sig_len -= sig_len_cut; + sig_len += (sig_len >= 0) && + (sig_dec >= div_pow10_table[sig_len].p10); + } + if (sig_len <= 0) { + byte_copy_4(buf, "0.0"); + return buf + 3; + } + } + + /* fill zeros */ + memset(buf, '0', 32); + + /* the decimal point offset relative to the first digit */ + dot_ofs = sig_len + exp_dec; + + /* not prefixed with zero, e.g. 1.234, 1234.0 */ + no_pre_zero = (dot_ofs > 0); + + /* write the number as digits */ + pre_ofs = no_pre_zero ? 0 : (1 - dot_ofs); + num_hdr = buf + pre_ofs; + num_end = write_u64_len_1_to_17(sig_dec, num_hdr); + + /* seperate these digits to leave a space for dot */ + num_sep_pos = no_pre_zero ? dot_ofs : -dot_ofs; + num_sep = buf + num_sep_pos; + byte_move_16(num_sep + 1, num_sep); + num_end += (exp_dec < 0); + + /* write the dot */ + dot_set_pos = yyjson_max(dot_ofs, 1); + buf[dot_set_pos] = '.'; + + /* remove trailing zeros */ + buf += dot_set_pos + 2; + buf = yyjson_max(buf, num_end); + buf -= *(buf - 1) == '0'; /* branchless for last zero */ + buf -= *(buf - 1) == '0'; /* branchless for second last zero */ + while (*(buf - 1) == '0') buf--; /* for unlikely more zeros */ + buf += *(buf - 1) == '.'; /* keep a zero after dot */ + return buf; + + } else { + /* binary to decimal */ + f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, + &sig_dec, &exp_dec); + + /* the sig length is 16 or 17 */ + sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); + + /* write with scientific notation, e.g. 1.234e56 */ + end = write_u64_len_16_to_17_trim(sig_dec, buf + 1); + end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ + exp_dec += sig_len - 1; + buf[0] = buf[1]; + buf[1] = '.'; + return write_f64_exp(exp_dec, end); + } + } else { + /* subnormal number */ + byte_copy_4(buf, "0.0"); + return buf + 3; + } +} + +#else /* FP_WRITER */ + +#if YYJSON_MSC_VER >= 1400 +#define snprintf_num(buf, len, fmt, dig, val) \ + sprintf_s((char *)buf, len, fmt, dig, val) +#elif defined(snprintf) || (YYJSON_STDC_VER >= 199901L) +#define snprintf_num(buf, len, fmt, dig, val) \ + snprintf((char *)buf, len, fmt, dig, val) +#else +#define snprintf_num(buf, len, fmt, dig, val) \ + sprintf((char *)buf, fmt, dig, val) +#endif + +static_noinline u8 *write_fp_reformat(u8 *buf, int len, + yyjson_write_flag flg, bool fixed) { + u8 *cur = buf; + if (unlikely(len < 1)) return NULL; + cur += (*cur == '-'); + if (unlikely(!char_is_digit(*cur))) { + /* nan, inf, or bad output */ + if (has_flg(INF_AND_NAN_AS_NULL)) { + byte_copy_4(buf, "null"); + return buf + 4; + } else if (has_allow(INF_AND_NAN)) { + if (*cur == 'i') { + byte_copy_8(cur, "Infinity"); + return cur + 8; + } else if (*cur == 'n') { + byte_copy_4(buf, "NaN"); + return buf + 3; + } + } + return NULL; + } else { + /* finite number */ + u8 *end = buf + len, *dot = NULL, *exp = NULL; + + /* + The snprintf() function is locale-dependent. For currently known + locales, (en, zh, ja, ko, am, he, hi) use '.' as the decimal point, + while other locales use ',' as the decimal point. we need to replace + ',' with '.' to avoid the locale setting. + */ + for (; cur < end; cur++) { + switch (*cur) { + case ',': *cur = '.'; /* fallthrough */ + case '.': dot = cur; break; + case 'e': exp = cur; break; + default: break; + } + } + if (fixed) { + /* remove trailing zeros */ + while (*(end - 1) == '0') end--; + end += *(end - 1) == '.'; + } else { + if (!dot && !exp) { + /* add decimal point, e.g. 123 -> 123.0 */ + byte_copy_2(end, ".0"); + end += 2; + } else if (exp) { + cur = exp + 1; + /* remove positive sign in the exponent part */ + if (*cur == '+') { + memmove(cur, cur + 1, (usize)(end - cur - 1)); + end--; + } + cur += (*cur == '-'); + /* remove leading zeros in the exponent part */ + if (*cur == '0') { + u8 *hdr = cur++; + while (*cur == '0') cur++; + memmove(hdr, cur, (usize)(end - cur)); + end -= (usize)(cur - hdr); + } + } + } + return end; + } +} + +/** Write a double number (requires 40 bytes buffer). */ +static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { +#if defined(DBL_DECIMAL_DIG) && DBL_DECIMAL_DIG < F64_DEC_DIG + int dig = DBL_DECIMAL_DIG; +#else + int dig = F64_DEC_DIG; +#endif + f64 val = f64_from_bits(raw); + int len = snprintf_num(buf, FP_BUF_LEN, "%.*g", dig, val); + return write_fp_reformat(buf, len, flg, false); +} + +/** Write a double number (requires 40 bytes buffer). */ +static_noinline u8 *write_f32_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { +#if defined(FLT_DECIMAL_DIG) && FLT_DECIMAL_DIG < F32_DEC_DIG + int dig = FLT_DECIMAL_DIG; +#else + int dig = F32_DEC_DIG; +#endif + f64 val = (f64)f64_to_f32(f64_from_bits(raw)); + int len = snprintf_num(buf, FP_BUF_LEN, "%.*g", dig, val); + return write_fp_reformat(buf, len, flg, false); +} + +/** Write a double number (requires 40 bytes buffer). */ +static_noinline u8 *write_f64_raw_fixed(u8 *buf, u64 raw, + yyjson_write_flag flg, u32 prec) { + f64 val = (f64)f64_from_bits(raw); + if (-1e21 < val && val < 1e21) { + int len = snprintf_num(buf, FP_BUF_LEN, "%.*f", (int)prec, val); + return write_fp_reformat(buf, len, flg, true); + } else { + return write_f64_raw(buf, raw, flg); + } +} + +#endif /* FP_WRITER */ + +/** Write a JSON number (requires 40 bytes buffer). */ +static_inline u8 *write_num(u8 *cur, yyjson_val *val, yyjson_write_flag flg) { + if (!(val->tag & YYJSON_SUBTYPE_REAL)) { + u64 pos = val->uni.u64; + u64 neg = ~pos + 1; + usize sign = ((val->tag & YYJSON_SUBTYPE_SINT) > 0) & ((i64)pos < 0); + *cur = '-'; + return write_u64(sign ? neg : pos, cur + sign); + } else { + u64 raw = val->uni.u64; + u32 val_fmt = (u32)(val->tag >> 32); + u32 all_fmt = flg; + u32 fmt = val_fmt | all_fmt; + if (likely(!(fmt >> (32 - YYJSON_WRITE_FP_FLAG_BITS)))) { + /* double to shortest */ + return write_f64_raw(cur, raw, flg); + } else if (fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS)) { + /* double to fixed */ + u32 val_prec = val_fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS); + u32 all_prec = all_fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS); + u32 prec = val_prec ? val_prec : all_prec; + return write_f64_raw_fixed(cur, raw, flg, prec); + } else { + if (fmt & YYJSON_WRITE_FP_TO_FLOAT) { + /* float to shortest */ + return write_f32_raw(cur, raw, flg); + } else { + /* double to shortest */ + return write_f64_raw(cur, raw, flg); + } + } + } +} + +char *yyjson_write_number(const yyjson_val *val, char *buf) { + if (unlikely(!val || !buf)) return NULL; + switch (val->tag & YYJSON_TAG_MASK) { + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT: { + buf = (char *)write_u64(val->uni.u64, (u8 *)buf); + *buf = '\0'; + return buf; + } + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: { + u64 pos = val->uni.u64; + u64 neg = ~pos + 1; + usize sign = ((i64)pos < 0); + *buf = '-'; + buf = (char *)write_u64(sign ? neg : pos, (u8 *)buf + sign); + *buf = '\0'; + return buf; + } + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL: { + u64 raw = val->uni.u64; + u32 fmt = (u32)(val->tag >> 32); + u32 flg = YYJSON_WRITE_ALLOW_INF_AND_NAN; + if (likely(!(fmt >> (32 - YYJSON_WRITE_FP_FLAG_BITS)))) { + buf = (char *)write_f64_raw((u8 *)buf, raw, flg); + } else if (fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS)) { + u32 prec = fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS); + buf = (char *)write_f64_raw_fixed((u8 *)buf, raw, flg, prec); + } else { + if (fmt & YYJSON_WRITE_FP_TO_FLOAT) { + buf = (char *)write_f32_raw((u8 *)buf, raw, flg); + } else { + buf = (char *)write_f64_raw((u8 *)buf, raw, flg); + } + } + if (buf) *buf = '\0'; + return buf; + } + default: return NULL; + } +} + + + +/*============================================================================== + * MARK: - String Writer (Private) + *============================================================================*/ + +/** Character encode type, if (type > CHAR_ENC_ERR_1) bytes = type / 2; */ +typedef u8 char_enc_type; +#define CHAR_ENC_CPY_1 0 /* 1-byte UTF-8, copy. */ +#define CHAR_ENC_ERR_1 1 /* 1-byte UTF-8, error. */ +#define CHAR_ENC_ESC_A 2 /* 1-byte ASCII, escaped as '\x'. */ +#define CHAR_ENC_ESC_1 3 /* 1-byte UTF-8, escaped as '\uXXXX'. */ +#define CHAR_ENC_CPY_2 4 /* 2-byte UTF-8, copy. */ +#define CHAR_ENC_ESC_2 5 /* 2-byte UTF-8, escaped as '\uXXXX'. */ +#define CHAR_ENC_CPY_3 6 /* 3-byte UTF-8, copy. */ +#define CHAR_ENC_ESC_3 7 /* 3-byte UTF-8, escaped as '\uXXXX'. */ +#define CHAR_ENC_CPY_4 8 /* 4-byte UTF-8, copy. */ +#define CHAR_ENC_ESC_4 9 /* 4-byte UTF-8, escaped as '\uXXXX\uXXXX'. */ + +/** Character encode type table: don't escape unicode, don't escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_cpy[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Character encode type table: don't escape unicode, escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_cpy_slash[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Character encode type table: escape unicode, don't escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_esc[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Character encode type table: escape unicode, escape '/'. + (generate with misc/make_tables.c) */ +static const char_enc_type enc_table_esc_slash[256] = { + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +/** Escaped hex character table: ["00" "01" "02" ... "FD" "FE" "FF"]. + (generate with misc/make_tables.c) */ +yyjson_align(2) +static const u8 esc_hex_char_table[512] = { + '0', '0', '0', '1', '0', '2', '0', '3', + '0', '4', '0', '5', '0', '6', '0', '7', + '0', '8', '0', '9', '0', 'A', '0', 'B', + '0', 'C', '0', 'D', '0', 'E', '0', 'F', + '1', '0', '1', '1', '1', '2', '1', '3', + '1', '4', '1', '5', '1', '6', '1', '7', + '1', '8', '1', '9', '1', 'A', '1', 'B', + '1', 'C', '1', 'D', '1', 'E', '1', 'F', + '2', '0', '2', '1', '2', '2', '2', '3', + '2', '4', '2', '5', '2', '6', '2', '7', + '2', '8', '2', '9', '2', 'A', '2', 'B', + '2', 'C', '2', 'D', '2', 'E', '2', 'F', + '3', '0', '3', '1', '3', '2', '3', '3', + '3', '4', '3', '5', '3', '6', '3', '7', + '3', '8', '3', '9', '3', 'A', '3', 'B', + '3', 'C', '3', 'D', '3', 'E', '3', 'F', + '4', '0', '4', '1', '4', '2', '4', '3', + '4', '4', '4', '5', '4', '6', '4', '7', + '4', '8', '4', '9', '4', 'A', '4', 'B', + '4', 'C', '4', 'D', '4', 'E', '4', 'F', + '5', '0', '5', '1', '5', '2', '5', '3', + '5', '4', '5', '5', '5', '6', '5', '7', + '5', '8', '5', '9', '5', 'A', '5', 'B', + '5', 'C', '5', 'D', '5', 'E', '5', 'F', + '6', '0', '6', '1', '6', '2', '6', '3', + '6', '4', '6', '5', '6', '6', '6', '7', + '6', '8', '6', '9', '6', 'A', '6', 'B', + '6', 'C', '6', 'D', '6', 'E', '6', 'F', + '7', '0', '7', '1', '7', '2', '7', '3', + '7', '4', '7', '5', '7', '6', '7', '7', + '7', '8', '7', '9', '7', 'A', '7', 'B', + '7', 'C', '7', 'D', '7', 'E', '7', 'F', + '8', '0', '8', '1', '8', '2', '8', '3', + '8', '4', '8', '5', '8', '6', '8', '7', + '8', '8', '8', '9', '8', 'A', '8', 'B', + '8', 'C', '8', 'D', '8', 'E', '8', 'F', + '9', '0', '9', '1', '9', '2', '9', '3', + '9', '4', '9', '5', '9', '6', '9', '7', + '9', '8', '9', '9', '9', 'A', '9', 'B', + '9', 'C', '9', 'D', '9', 'E', '9', 'F', + 'A', '0', 'A', '1', 'A', '2', 'A', '3', + 'A', '4', 'A', '5', 'A', '6', 'A', '7', + 'A', '8', 'A', '9', 'A', 'A', 'A', 'B', + 'A', 'C', 'A', 'D', 'A', 'E', 'A', 'F', + 'B', '0', 'B', '1', 'B', '2', 'B', '3', + 'B', '4', 'B', '5', 'B', '6', 'B', '7', + 'B', '8', 'B', '9', 'B', 'A', 'B', 'B', + 'B', 'C', 'B', 'D', 'B', 'E', 'B', 'F', + 'C', '0', 'C', '1', 'C', '2', 'C', '3', + 'C', '4', 'C', '5', 'C', '6', 'C', '7', + 'C', '8', 'C', '9', 'C', 'A', 'C', 'B', + 'C', 'C', 'C', 'D', 'C', 'E', 'C', 'F', + 'D', '0', 'D', '1', 'D', '2', 'D', '3', + 'D', '4', 'D', '5', 'D', '6', 'D', '7', + 'D', '8', 'D', '9', 'D', 'A', 'D', 'B', + 'D', 'C', 'D', 'D', 'D', 'E', 'D', 'F', + 'E', '0', 'E', '1', 'E', '2', 'E', '3', + 'E', '4', 'E', '5', 'E', '6', 'E', '7', + 'E', '8', 'E', '9', 'E', 'A', 'E', 'B', + 'E', 'C', 'E', 'D', 'E', 'E', 'E', 'F', + 'F', '0', 'F', '1', 'F', '2', 'F', '3', + 'F', '4', 'F', '5', 'F', '6', 'F', '7', + 'F', '8', 'F', '9', 'F', 'A', 'F', 'B', + 'F', 'C', 'F', 'D', 'F', 'E', 'F', 'F' +}; + +/** Escaped single character table. (generate with misc/make_tables.c) */ +yyjson_align(2) +static const u8 esc_single_char_table[512] = { + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + '\\', 'b', '\\', 't', '\\', 'n', ' ', ' ', + '\\', 'f', '\\', 'r', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', '\\', '"', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', '\\', '/', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + '\\', '\\', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' +}; + +/** Returns the encode table with options. */ +static_inline const char_enc_type *get_enc_table_with_flag( + yyjson_write_flag flg) { + if (has_flg(ESCAPE_UNICODE)) { + if (has_flg(ESCAPE_SLASHES)) { + return enc_table_esc_slash; + } else { + return enc_table_esc; + } + } else { + if (has_flg(ESCAPE_SLASHES)) { + return enc_table_cpy_slash; + } else { + return enc_table_cpy; + } + } +} + +/** Write raw string. */ +static_inline u8 *write_raw(u8 *cur, const u8 *raw, usize raw_len) { + memcpy(cur, raw, raw_len); + return cur + raw_len; +} + +/** + Write string no-escape. + @param cur Buffer cursor. + @param str A UTF-8 string, null-terminator is not required. + @param str_len Length of string in bytes. + @return The buffer cursor after string. + */ +static_inline u8 *write_str_noesc(u8 *cur, const u8 *str, usize str_len) { + *cur++ = '"'; + while (str_len >= 16) { + byte_copy_16(cur, str); + cur += 16; + str += 16; + str_len -= 16; + } + while (str_len >= 4) { + byte_copy_4(cur, str); + cur += 4; + str += 4; + str_len -= 4; + } + while (str_len) { + *cur++ = *str++; + str_len -= 1; + } + *cur++ = '"'; + return cur; +} + +/** + Write UTF-8 string (requires len * 6 + 2 bytes buffer). + @param cur Buffer cursor. + @param esc Escape unicode. + @param inv Allow invalid unicode. + @param str A UTF-8 string, null-terminator is not required. + @param str_len Length of string in bytes. + @param enc_table Encode type table for character. + @return The buffer cursor after string, or NULL on invalid unicode. + */ +static_inline u8 *write_str(u8 *cur, bool esc, bool inv, + const u8 *str, usize str_len, + const char_enc_type *enc_table) { + /* The replacement character U+FFFD, used to indicate invalid character. */ + const v32 rep = {{ 'F', 'F', 'F', 'D' }}; + const v32 pre = {{ '\\', 'u', '0', '0' }}; + + const u8 *src = str; + const u8 *end = str + str_len; + *cur++ = '"'; + +copy_ascii: + /* + Copy continuous ASCII, loop unrolling, same as the following code: + + while (end > src) ( + if (unlikely(enc_table[*src])) break; + *cur++ = *src++; + ); + */ +#define expr_jump(i) \ + if (unlikely(enc_table[src[i]])) goto stop_char_##i; + +#define expr_stop(i) \ + stop_char_##i: \ + memcpy(cur, src, i); \ + cur += i; src += i; goto copy_utf8; + + while (end - src >= 16) { + repeat16_incr(expr_jump) + byte_copy_16(cur, src); + cur += 16; src += 16; + } + + while (end - src >= 4) { + repeat4_incr(expr_jump) + byte_copy_4(cur, src); + cur += 4; src += 4; + } + + while (end > src) { + expr_jump(0) + *cur++ = *src++; + } + + *cur++ = '"'; + return cur; + + repeat16_incr(expr_stop) + +#undef expr_jump +#undef expr_stop + +copy_utf8: + if (unlikely(src + 4 > end)) { + if (end == src) goto copy_end; + if (end - src < enc_table[*src] / 2) goto err_one; + } + switch (enc_table[*src]) { + case CHAR_ENC_CPY_1: { + *cur++ = *src++; + goto copy_ascii; + } + case CHAR_ENC_CPY_2: { +#if YYJSON_DISABLE_UTF8_VALIDATION + byte_copy_2(cur, src); +#else + u32 uni = 0; + byte_copy_2(&uni, src); + if (unlikely(!is_utf8_seq2(uni))) goto err_cpy; + byte_copy_2(cur, &uni); +#endif + cur += 2; + src += 2; + goto copy_utf8; + } + case CHAR_ENC_CPY_3: { +#if YYJSON_DISABLE_UTF8_VALIDATION + if (likely(src + 4 <= end)) { + byte_copy_4(cur, src); + } else { + byte_copy_2(cur, src); + cur[2] = src[2]; + } +#else + u32 uni, tmp; + if (likely(src + 4 <= end)) { + uni = byte_load_4(src); + if (unlikely(!is_utf8_seq3(uni))) goto err_cpy; + byte_copy_4(cur, src); + } else { + uni = byte_load_3(src); + if (unlikely(!is_utf8_seq3(uni))) goto err_cpy; + byte_copy_4(cur, &uni); + } +#endif + cur += 3; + src += 3; + goto copy_utf8; + } + case CHAR_ENC_CPY_4: { +#if YYJSON_DISABLE_UTF8_VALIDATION + byte_copy_4(cur, src); +#else + u32 uni, tmp; + uni = byte_load_4(src); + if (unlikely(!is_utf8_seq4(uni))) goto err_cpy; + byte_copy_4(cur, src); +#endif + cur += 4; + src += 4; + goto copy_utf8; + } + case CHAR_ENC_ESC_A: { + byte_copy_2(cur, &esc_single_char_table[*src * 2]); + cur += 2; + src += 1; + goto copy_utf8; + } + case CHAR_ENC_ESC_1: { + byte_copy_4(cur + 0, &pre); + byte_copy_2(cur + 4, &esc_hex_char_table[*src * 2]); + cur += 6; + src += 1; + goto copy_utf8; + } + case CHAR_ENC_ESC_2: { + u16 u; +#if !YYJSON_DISABLE_UTF8_VALIDATION + u32 v4 = 0; + u16 v2 = byte_load_2(src); + byte_copy_2(&v4, &v2); + if (unlikely(!is_utf8_seq2(v4))) goto err_esc; +#endif + u = (u16)(((u16)(src[0] & 0x1F) << 6) | + ((u16)(src[1] & 0x3F) << 0)); + byte_copy_2(cur + 0, &pre); + byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]); + byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]); + cur += 6; + src += 2; + goto copy_utf8; + } + case CHAR_ENC_ESC_3: { + u16 u; + u32 v, tmp; +#if !YYJSON_DISABLE_UTF8_VALIDATION + v = byte_load_3(src); + if (unlikely(!is_utf8_seq3(v))) goto err_esc; +#endif + u = (u16)(((u16)(src[0] & 0x0F) << 12) | + ((u16)(src[1] & 0x3F) << 6) | + ((u16)(src[2] & 0x3F) << 0)); + byte_copy_2(cur + 0, &pre); + byte_copy_2(cur + 2, &esc_hex_char_table[(u >> 8) * 2]); + byte_copy_2(cur + 4, &esc_hex_char_table[(u & 0xFF) * 2]); + cur += 6; + src += 3; + goto copy_utf8; + } + case CHAR_ENC_ESC_4: { + u32 hi, lo, u, v, tmp; +#if !YYJSON_DISABLE_UTF8_VALIDATION + v = byte_load_4(src); + if (unlikely(!is_utf8_seq4(v))) goto err_esc; +#endif + u = ((u32)(src[0] & 0x07) << 18) | + ((u32)(src[1] & 0x3F) << 12) | + ((u32)(src[2] & 0x3F) << 6) | + ((u32)(src[3] & 0x3F) << 0); + u -= 0x10000; + hi = (u >> 10) + 0xD800; + lo = (u & 0x3FF) + 0xDC00; + byte_copy_2(cur + 0, &pre); + byte_copy_2(cur + 2, &esc_hex_char_table[(hi >> 8) * 2]); + byte_copy_2(cur + 4, &esc_hex_char_table[(hi & 0xFF) * 2]); + byte_copy_2(cur + 6, &pre); + byte_copy_2(cur + 8, &esc_hex_char_table[(lo >> 8) * 2]); + byte_copy_2(cur + 10, &esc_hex_char_table[(lo & 0xFF) * 2]); + cur += 12; + src += 4; + goto copy_utf8; + } + case CHAR_ENC_ERR_1: { + goto err_one; + } + default: break; /* unreachable */ + } + +copy_end: + *cur++ = '"'; + return cur; + +err_one: + if (esc) goto err_esc; + else goto err_cpy; + +err_cpy: + if (!inv) return NULL; + *cur++ = *src++; + goto copy_utf8; + +err_esc: + if (!inv) return NULL; + byte_copy_2(cur + 0, &pre); + byte_copy_4(cur + 2, &rep); + cur += 6; + src += 1; + goto copy_utf8; +} + + + +/*============================================================================== + * MARK: - JSON Writer Utilities (Private) + *============================================================================*/ + +/** Write null (requires 8 bytes buffer). */ +static_inline u8 *write_null(u8 *cur) { + v64 v = {{ 'n', 'u', 'l', 'l', ',', '\n', 0, 0 }}; + byte_copy_8(cur, &v); + return cur + 4; +} + +/** Write bool (requires 8 bytes buffer). */ +static_inline u8 *write_bool(u8 *cur, bool val) { + v64 v0 = {{ 'f', 'a', 'l', 's', 'e', ',', '\n', 0 }}; + v64 v1 = {{ 't', 'r', 'u', 'e', ',', '\n', 0, 0 }}; + if (val) { + byte_copy_8(cur, &v1); + } else { + byte_copy_8(cur, &v0); + } + return cur + 5 - val; +} + +/** Write indent (requires level x 4 bytes buffer). + Param spaces should not larger than 4. */ +static_inline u8 *write_indent(u8 *cur, usize level, usize spaces) { + while (level-- > 0) { + byte_copy_4(cur, " "); + cur += spaces; + } + return cur; +} + +/** Write data to file pointer. */ +static bool write_dat_to_fp(FILE *fp, u8 *dat, usize len, + yyjson_write_err *err) { + if (fwrite(dat, len, 1, fp) != 1) { + err->msg = "file writing failed"; + err->code = YYJSON_WRITE_ERROR_FILE_WRITE; + return false; + } + return true; +} + +/** Write data to file. */ +static bool write_dat_to_file(const char *path, u8 *dat, usize len, + yyjson_write_err *err) { +#define return_err(_code, _msg) do { \ + err->msg = _msg; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + if (file) fclose(file); \ + return false; \ +} while (false) + + FILE *file = fopen_writeonly(path); + if (file == NULL) { + return_err(FILE_OPEN, MSG_FOPEN); + } + if (fwrite(dat, len, 1, file) != 1) { + return_err(FILE_WRITE, MSG_FWRITE); + } + if (fclose(file) != 0) { + file = NULL; + return_err(FILE_WRITE, MSG_FCLOSE); + } + return true; + +#undef return_err +} + + + +/*============================================================================== + * MARK: - JSON Writer Implementation (Private) + *============================================================================*/ + +typedef struct yyjson_write_ctx { + usize tag; +} yyjson_write_ctx; + +static_inline void yyjson_write_ctx_set(yyjson_write_ctx *ctx, + usize size, bool is_obj) { + ctx->tag = (size << 1) | (usize)is_obj; +} + +static_inline void yyjson_write_ctx_get(yyjson_write_ctx *ctx, + usize *size, bool *is_obj) { + usize tag = ctx->tag; + *size = tag >> 1; + *is_obj = (bool)(tag & 1); +} + +/** Write single JSON value. */ +static_inline u8 *yyjson_write_single(yyjson_val *val, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { +#define return_err(_code, _msg) do { \ + if (hdr) alc.free(alc.ctx, (void *)hdr); \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + hdr = (u8 *)alc.malloc(alc.ctx, _len); \ + if (!hdr) goto fail_alloc; \ + cur = hdr; \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + u8 *hdr = NULL, *cur; + usize str_len; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_flg(ESCAPE_UNICODE) != 0; + bool inv = has_allow(INVALID_UNICODE) != 0; + bool newline = has_flg(NEWLINE_AT_END) != 0; + const usize end_len = 2; /* '\n' and '\0' */ + + switch (unsafe_yyjson_get_type(val)) { + case YYJSON_TYPE_RAW: + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + end_len); + cur = write_raw(cur, str_ptr, str_len); + break; + + case YYJSON_TYPE_STR: + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 2 + end_len); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_str_noesc(cur, str_ptr, str_len); + } else { + cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + break; + + case YYJSON_TYPE_NUM: + incr_len(FP_BUF_LEN + end_len); + cur = write_num(cur, val, flg); + if (unlikely(!cur)) goto fail_num; + break; + + case YYJSON_TYPE_BOOL: + incr_len(8); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + break; + + case YYJSON_TYPE_NULL: + incr_len(8); + cur = write_null(cur); + break; + + case YYJSON_TYPE_ARR: + incr_len(2 + end_len); + byte_copy_2(cur, "[]"); + cur += 2; + break; + + case YYJSON_TYPE_OBJ: + incr_len(2 + end_len); + byte_copy_2(cur, "{}"); + cur += 2; + break; + + default: + goto fail_type; + } + + if (newline) *cur++ = '\n'; + *cur = '\0'; + *dat_len = (usize)(cur - hdr); + memset(err, 0, sizeof(yyjson_write_err)); + return hdr; + +fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); +fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); +fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); +fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); + +#undef return_err +#undef check_str_len +#undef incr_len +} + +/** Write JSON document minify. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_write_minify(const yyjson_val *root, + const yyjson_write_flag flg, + const yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + usize ctx_pos = (usize)((u8 *)ctx - hdr); \ + usize cur_pos = (usize)(cur - hdr); \ + ctx_len = (usize)(end - (u8 *)ctx); \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \ + if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ + goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + cur_pos; \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_val *val; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key; + u8 *hdr, *cur, *end, *tmp; + yyjson_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_flg(ESCAPE_UNICODE) != 0; + bool inv = has_allow(INVALID_UNICODE) != 0; + bool newline = has_flg(NEWLINE_AT_END) != 0; + + alc_len = root->uni.ofs / sizeof(yyjson_val); + alc_len = alc_len * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + val++; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = ((u8)ctn_obj & (u8)~ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_str_noesc(cur, str_ptr, str_len); + } else { + cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + *cur++ = is_key ? ':' : ','; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + incr_len(FP_BUF_LEN); + cur = write_num(cur, val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + incr_len(16); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + goto val_end; + } else { + /* push context, setup new container */ + yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + val++; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + incr_len(16); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + incr_len(16); + cur = write_null(cur); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 2); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + goto val_end; + } + goto fail_type; + +val_end: + val++; + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + goto val_begin; + +ctn_end: + cur--; + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + *cur++ = ','; + if (unlikely((u8 *)ctx >= end)) goto doc_end; + yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj); + ctn_len--; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + if (newline) { + incr_len(2); + *(cur - 1) = '\n'; + cur++; + } + *--cur = '\0'; + *dat_len = (usize)(cur - hdr); + memset(err, 0, sizeof(yyjson_write_err)); + return hdr; + +fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); +fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); +fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); +fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +/** Write JSON document pretty. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_write_pretty(const yyjson_val *root, + const yyjson_write_flag flg, + const yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + usize ctx_pos = (usize)((u8 *)ctx - hdr); \ + usize cur_pos = (usize)(cur - hdr); \ + ctx_len = (usize)(end - (u8 *)ctx); \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_write_ctx)); \ + if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ + goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_tmp = (yyjson_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + cur_pos; \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_val *val; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key, no_indent; + u8 *hdr, *cur, *end, *tmp; + yyjson_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len, level; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_flg(ESCAPE_UNICODE) != 0; + bool inv = has_allow(INVALID_UNICODE) != 0; + usize spaces = has_flg(PRETTY_TWO_SPACES) ? 2 : 4; + bool newline = has_flg(NEWLINE_AT_END) != 0; + + alc_len = root->uni.ofs / sizeof(yyjson_val); + alc_len = alc_len * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + val++; + level = 1; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = (bool)((u8)ctn_obj & (u8)~ctn_len); + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_str_noesc(cur, str_ptr, str_len); + } else { + cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + *cur++ = is_key ? ':' : ','; + *cur++ = is_key ? ' ' : '\n'; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(FP_BUF_LEN + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_num(cur, val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } else { + /* push context, setup new container */ + incr_len(32 + (no_indent ? 0 : level * 4)); + yyjson_write_ctx_set(--ctx, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + cur = write_indent(cur, no_indent ? 0 : level, spaces); + level++; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + val++; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_null(cur); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 3 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + goto fail_type; + +val_end: + val++; + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + goto val_begin; + +ctn_end: + cur -= 2; + *cur++ = '\n'; + incr_len(level * 4); + cur = write_indent(cur, --level, spaces); + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + if (unlikely((u8 *)ctx >= end)) goto doc_end; + yyjson_write_ctx_get(ctx++, &ctn_len, &ctn_obj); + ctn_len--; + *cur++ = ','; + *cur++ = '\n'; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + if (newline) { + incr_len(2); + *cur++ = '\n'; + } + *cur = '\0'; + *dat_len = (usize)(cur - hdr); + memset(err, 0, sizeof(yyjson_write_err)); + return hdr; + +fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); +fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); +fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); +fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); + +#undef return_err +#undef incr_len +#undef check_str_len +} + + + +/*============================================================================== + * MARK: - JSON Writer (Public) + *============================================================================*/ + +char *yyjson_val_write_opts(const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_write_err tmp_err; + usize tmp_dat_len; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_val *root = constcast(yyjson_val *)val; + + if (!err) err = &tmp_err; + if (!dat_len) dat_len = &tmp_dat_len; + + if (unlikely(!root)) { + *dat_len = 0; + err->msg = "input JSON is NULL"; + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; + return NULL; + } + + if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) { + return (char *)yyjson_write_single(root, flg, alc, dat_len, err); + } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { + return (char *)yyjson_write_pretty(root, flg, alc, dat_len, err); + } else { + return (char *)yyjson_write_minify(root, flg, alc, dat_len, err); + } +} + +char *yyjson_write_opts(const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_opts(root, flg, alc_ptr, dat_len, err); +} + +bool yyjson_val_write_file(const char *path, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err tmp_err; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + u8 *dat; + usize dat_len = 0; + yyjson_val *root = constcast(yyjson_val *)val; + bool suc; + + if (!err) err = &tmp_err; + if (unlikely(!path || !*path)) { + err->msg = "input path is invalid"; + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_val_write_opts(root, flg, &alc, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_file(path, dat, dat_len, err); + alc.free(alc.ctx, dat); + return suc; +} + +bool yyjson_val_write_fp(FILE *fp, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err tmp_err; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + u8 *dat; + usize dat_len = 0; + yyjson_val *root = constcast(yyjson_val *)val; + bool suc; + + if (!err) err = &tmp_err; + if (unlikely(!fp)) { + err->msg = "input fp is invalid"; + err->code = YYJSON_READ_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_val_write_opts(root, flg, &alc, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_fp(fp, dat, dat_len, err); + alc.free(alc.ctx, dat); + return suc; +} + +bool yyjson_write_file(const char *path, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_file(path, root, flg, alc_ptr, err); +} + +bool yyjson_write_fp(FILE *fp, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_fp(fp, root, flg, alc_ptr, err); +} + + + +/*============================================================================== + * MARK: - Mutable JSON Writer Implementation (Private) + *============================================================================*/ + +typedef struct yyjson_mut_write_ctx { + usize tag; + yyjson_mut_val *ctn; +} yyjson_mut_write_ctx; + +static_inline void yyjson_mut_write_ctx_set(yyjson_mut_write_ctx *ctx, + yyjson_mut_val *ctn, + usize size, bool is_obj) { + ctx->tag = (size << 1) | (usize)is_obj; + ctx->ctn = ctn; +} + +static_inline void yyjson_mut_write_ctx_get(yyjson_mut_write_ctx *ctx, + yyjson_mut_val **ctn, + usize *size, bool *is_obj) { + usize tag = ctx->tag; + *size = tag >> 1; + *is_obj = (bool)(tag & 1); + *ctn = ctx->ctn; +} + +/** Get the estimated number of values for the mutable JSON document. */ +static_inline usize yyjson_mut_doc_estimated_val_num( + const yyjson_mut_doc *doc) { + usize sum = 0; + yyjson_val_chunk *chunk = doc->val_pool.chunks; + while (chunk) { + sum += chunk->chunk_size / sizeof(yyjson_mut_val) - 1; + if (chunk == doc->val_pool.chunks) { + sum -= (usize)(doc->val_pool.end - doc->val_pool.cur); + } + chunk = chunk->next; + } + return sum; +} + +/** Write single JSON value. */ +static_inline u8 *yyjson_mut_write_single(yyjson_mut_val *val, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { + return yyjson_write_single((yyjson_val *)val, flg, alc, dat_len, err); +} + +/** Write JSON document minify. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_mut_write_minify(const yyjson_mut_val *root, + usize estimated_val_num, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + usize ctx_pos = (usize)((u8 *)ctx - hdr); \ + usize cur_pos = (usize)(cur - hdr); \ + ctx_len = (usize)(end - (u8 *)ctx); \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \ + if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ + goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + cur_pos; \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_mut_val *val, *ctn; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key; + u8 *hdr, *cur, *end, *tmp; + yyjson_mut_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_flg(ESCAPE_UNICODE) != 0; + bool inv = has_allow(INVALID_UNICODE) != 0; + bool newline = has_flg(NEWLINE_AT_END) != 0; + + alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_MINIFY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_mut_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_mut_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + ctn = val; + val = (yyjson_mut_val *)val->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = ((u8)ctn_obj & (u8)~ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_str_noesc(cur, str_ptr, str_len); + } else { + cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + *cur++ = is_key ? ':' : ','; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + incr_len(FP_BUF_LEN); + cur = write_num(cur, (yyjson_val *)val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + incr_len(16); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + goto val_end; + } else { + /* push context, setup new container */ + yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + ctn = val; + val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + incr_len(16); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + incr_len(16); + cur = write_null(cur); + cur++; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 2); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + goto val_end; + } + goto fail_type; + +val_end: + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + val = val->next; + goto val_begin; + +ctn_end: + cur--; + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + *cur++ = ','; + if (unlikely((u8 *)ctx >= end)) goto doc_end; + val = ctn->next; + yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj); + ctn_len--; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + if (newline) { + incr_len(2); + *(cur - 1) = '\n'; + cur++; + } + *--cur = '\0'; + *dat_len = (usize)(cur - hdr); + err->code = YYJSON_WRITE_SUCCESS; + err->msg = NULL; + return hdr; + +fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); +fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); +fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); +fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +/** Write JSON document pretty. + The root of this document should be a non-empty container. */ +static_inline u8 *yyjson_mut_write_pretty(const yyjson_mut_val *root, + usize estimated_val_num, + yyjson_write_flag flg, + yyjson_alc alc, + usize *dat_len, + yyjson_write_err *err) { +#define return_err(_code, _msg) do { \ + *dat_len = 0; \ + err->code = YYJSON_WRITE_ERROR_##_code; \ + err->msg = _msg; \ + if (hdr) alc.free(alc.ctx, hdr); \ + return NULL; \ +} while (false) + +#define incr_len(_len) do { \ + ext_len = (usize)(_len); \ + if (unlikely((u8 *)(cur + ext_len) >= (u8 *)ctx)) { \ + usize ctx_pos = (usize)((u8 *)ctx - hdr); \ + usize cur_pos = (usize)(cur - hdr); \ + ctx_len = (usize)(end - (u8 *)ctx); \ + alc_inc = yyjson_max(alc_len / 2, ext_len); \ + alc_inc = size_align_up(alc_inc, sizeof(yyjson_mut_write_ctx)); \ + if ((sizeof(usize) < 8) && size_add_is_overflow(alc_len, alc_inc)) \ + goto fail_alloc; \ + alc_len += alc_inc; \ + tmp = (u8 *)alc.realloc(alc.ctx, hdr, alc_len - alc_inc, alc_len); \ + if (unlikely(!tmp)) goto fail_alloc; \ + ctx_tmp = (yyjson_mut_write_ctx *)(void *)(tmp + (alc_len - ctx_len)); \ + memmove((void *)ctx_tmp, (void *)(tmp + ctx_pos), ctx_len); \ + ctx = ctx_tmp; \ + cur = tmp + cur_pos; \ + end = tmp + alc_len; \ + hdr = tmp; \ + } \ +} while (false) + +#define check_str_len(_len) do { \ + if ((sizeof(usize) < 8) && (_len >= (USIZE_MAX - 16) / 6)) \ + goto fail_alloc; \ +} while (false) + + yyjson_mut_val *val, *ctn; + yyjson_type val_type; + usize ctn_len, ctn_len_tmp; + bool ctn_obj, ctn_obj_tmp, is_key, no_indent; + u8 *hdr, *cur, *end, *tmp; + yyjson_mut_write_ctx *ctx, *ctx_tmp; + usize alc_len, alc_inc, ctx_len, ext_len, str_len, level; + const u8 *str_ptr; + const char_enc_type *enc_table = get_enc_table_with_flag(flg); + bool cpy = (enc_table == enc_table_cpy); + bool esc = has_flg(ESCAPE_UNICODE) != 0; + bool inv = has_allow(INVALID_UNICODE) != 0; + usize spaces = has_flg(PRETTY_TWO_SPACES) ? 2 : 4; + bool newline = has_flg(NEWLINE_AT_END) != 0; + + alc_len = estimated_val_num * YYJSON_WRITER_ESTIMATED_PRETTY_RATIO + 64; + alc_len = size_align_up(alc_len, sizeof(yyjson_mut_write_ctx)); + hdr = (u8 *)alc.malloc(alc.ctx, alc_len); + if (!hdr) goto fail_alloc; + cur = hdr; + end = hdr + alc_len; + ctx = (yyjson_mut_write_ctx *)(void *)end; + +doc_begin: + val = constcast(yyjson_mut_val *)root; + val_type = unsafe_yyjson_get_type(val); + ctn_obj = (val_type == YYJSON_TYPE_OBJ); + ctn_len = unsafe_yyjson_get_len(val) << (u8)ctn_obj; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + ctn = val; + val = (yyjson_mut_val *)val->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + level = 1; + +val_begin: + val_type = unsafe_yyjson_get_type(val); + if (val_type == YYJSON_TYPE_STR) { + is_key = (bool)((u8)ctn_obj & (u8)~ctn_len); + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len * 6 + 16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + if (likely(cpy) && unsafe_yyjson_get_subtype(val)) { + cur = write_str_noesc(cur, str_ptr, str_len); + } else { + cur = write_str(cur, esc, inv, str_ptr, str_len, enc_table); + if (unlikely(!cur)) goto fail_str; + } + *cur++ = is_key ? ':' : ','; + *cur++ = is_key ? ' ' : '\n'; + goto val_end; + } + if (val_type == YYJSON_TYPE_NUM) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(FP_BUF_LEN + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_num(cur, (yyjson_val *)val, flg); + if (unlikely(!cur)) goto fail_num; + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + if ((val_type & (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) == + (YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ)) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + ctn_len_tmp = unsafe_yyjson_get_len(val); + ctn_obj_tmp = (val_type == YYJSON_TYPE_OBJ); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + *cur++ = (u8)('[' | ((u8)ctn_obj_tmp << 5)); + *cur++ = (u8)(']' | ((u8)ctn_obj_tmp << 5)); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } else { + /* push context, setup new container */ + incr_len(32 + (no_indent ? 0 : level * 4)); + yyjson_mut_write_ctx_set(--ctx, ctn, ctn_len, ctn_obj); + ctn_len = ctn_len_tmp << (u8)ctn_obj_tmp; + ctn_obj = ctn_obj_tmp; + cur = write_indent(cur, no_indent ? 0 : level, spaces); + level++; + *cur++ = (u8)('[' | ((u8)ctn_obj << 5)); + *cur++ = '\n'; + ctn = val; + val = (yyjson_mut_val *)ctn->uni.ptr; /* tail */ + val = ctn_obj ? val->next->next : val->next; + goto val_begin; + } + } + if (val_type == YYJSON_TYPE_BOOL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_bool(cur, unsafe_yyjson_get_bool(val)); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_NULL) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + incr_len(16 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_null(cur); + cur += 2; + goto val_end; + } + if (val_type == YYJSON_TYPE_RAW) { + no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); + str_len = unsafe_yyjson_get_len(val); + str_ptr = (const u8 *)unsafe_yyjson_get_str(val); + check_str_len(str_len); + incr_len(str_len + 3 + (no_indent ? 0 : level * 4)); + cur = write_indent(cur, no_indent ? 0 : level, spaces); + cur = write_raw(cur, str_ptr, str_len); + *cur++ = ','; + *cur++ = '\n'; + goto val_end; + } + goto fail_type; + +val_end: + ctn_len--; + if (unlikely(ctn_len == 0)) goto ctn_end; + val = val->next; + goto val_begin; + +ctn_end: + cur -= 2; + *cur++ = '\n'; + incr_len(level * 4); + cur = write_indent(cur, --level, spaces); + *cur++ = (u8)(']' | ((u8)ctn_obj << 5)); + if (unlikely((u8 *)ctx >= end)) goto doc_end; + val = ctn->next; + yyjson_mut_write_ctx_get(ctx++, &ctn, &ctn_len, &ctn_obj); + ctn_len--; + *cur++ = ','; + *cur++ = '\n'; + if (likely(ctn_len > 0)) { + goto val_begin; + } else { + goto ctn_end; + } + +doc_end: + if (newline) { + incr_len(2); + *cur++ = '\n'; + } + *cur = '\0'; + *dat_len = (usize)(cur - hdr); + err->code = YYJSON_WRITE_SUCCESS; + err->msg = NULL; + return hdr; + +fail_alloc: return_err(MEMORY_ALLOCATION, MSG_MALLOC); +fail_type: return_err(INVALID_VALUE_TYPE, MSG_ERR_TYPE); +fail_num: return_err(NAN_OR_INF, MSG_NAN_INF); +fail_str: return_err(INVALID_STRING, MSG_ERR_UTF8); + +#undef return_err +#undef incr_len +#undef check_str_len +} + +static char *yyjson_mut_write_opts_impl(const yyjson_mut_val *val, + usize estimated_val_num, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_write_err tmp_err; + usize tmp_dat_len; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + yyjson_mut_val *root = constcast(yyjson_mut_val *)val; + + if (!err) err = &tmp_err; + if (!dat_len) dat_len = &tmp_dat_len; + + if (unlikely(!root)) { + *dat_len = 0; + err->msg = "input JSON is NULL"; + err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + return NULL; + } + + if (!unsafe_yyjson_is_ctn(root) || unsafe_yyjson_get_len(root) == 0) { + return (char *)yyjson_mut_write_single(root, flg, alc, dat_len, err); + } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { + return (char *)yyjson_mut_write_pretty(root, estimated_val_num, + flg, alc, dat_len, err); + } else { + return (char *)yyjson_mut_write_minify(root, estimated_val_num, + flg, alc, dat_len, err); + } +} + + + +/*============================================================================== + * MARK: - Mutable JSON Writer (Public) + *============================================================================*/ + +char *yyjson_mut_val_write_opts(const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + return yyjson_mut_write_opts_impl(val, 0, flg, alc_ptr, dat_len, err); +} + +char *yyjson_mut_write_opts(const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + usize *dat_len, + yyjson_write_err *err) { + yyjson_mut_val *root; + usize estimated_val_num; + if (likely(doc)) { + root = doc->root; + estimated_val_num = yyjson_mut_doc_estimated_val_num(doc); + } else { + root = NULL; + estimated_val_num = 0; + } + return yyjson_mut_write_opts_impl(root, estimated_val_num, + flg, alc_ptr, dat_len, err); +} + +bool yyjson_mut_val_write_file(const char *path, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err tmp_err; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + u8 *dat; + usize dat_len = 0; + yyjson_mut_val *root = constcast(yyjson_mut_val *)val; + bool suc; + + if (!err) err = &tmp_err; + if (unlikely(!path || !*path)) { + err->msg = "input path is invalid"; + err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_mut_val_write_opts(root, flg, &alc, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_file(path, dat, dat_len, err); + alc.free(alc.ctx, dat); + return suc; +} + +bool yyjson_mut_val_write_fp(FILE *fp, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_write_err tmp_err; + yyjson_alc alc = alc_ptr ? *alc_ptr : YYJSON_DEFAULT_ALC; + u8 *dat; + usize dat_len = 0; + yyjson_mut_val *root = constcast(yyjson_mut_val *)val; + bool suc; + + if (!err) err = &tmp_err; + if (unlikely(!fp)) { + err->msg = "input fp is invalid"; + err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + return false; + } + + dat = (u8 *)yyjson_mut_val_write_opts(root, flg, &alc, &dat_len, err); + if (unlikely(!dat)) return false; + suc = write_dat_to_fp(fp, dat, dat_len, err); + alc.free(alc.ctx, dat); + return suc; +} + +bool yyjson_mut_write_file(const char *path, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_mut_val *root = doc ? doc->root : NULL; + return yyjson_mut_val_write_file(path, root, flg, alc_ptr, err); +} + +bool yyjson_mut_write_fp(FILE *fp, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + yyjson_write_err *err) { + yyjson_mut_val *root = doc ? doc->root : NULL; + return yyjson_mut_val_write_fp(fp, root, flg, alc_ptr, err); +} + +#undef has_flg +#undef has_allow +#endif /* YYJSON_DISABLE_WRITER */ + + + +#if !YYJSON_DISABLE_UTILS + +/*============================================================================== + * MARK: - JSON Pointer API (RFC 6901) (Public) + *============================================================================*/ + +/** + Get a token from JSON pointer string. + @param ptr [in] string that points to current token prefix `/` + [out] string that points to next token prefix `/`, or string end + @param end [in] end of the entire JSON Pointer string + @param len [out] unescaped token length + @param esc [out] number of escaped characters in this token + @return head of the token, or NULL if syntax error + */ +static_inline const char *ptr_next_token(const char **ptr, const char *end, + usize *len, usize *esc) { + const char *hdr = *ptr + 1; + const char *cur = hdr; + /* skip unescaped characters */ + while (cur < end && *cur != '/' && *cur != '~') cur++; + if (likely(cur == end || *cur != '~')) { + /* no escaped characters, return */ + *ptr = cur; + *len = (usize)(cur - hdr); + *esc = 0; + return hdr; + } else { + /* handle escaped characters */ + usize esc_num = 0; + while (cur < end && *cur != '/') { + if (*cur++ == '~') { + if (cur == end || (*cur != '0' && *cur != '1')) { + *ptr = cur - 1; + return NULL; + } + esc_num++; + } + } + *ptr = cur; + *len = (usize)(cur - hdr) - esc_num; + *esc = esc_num; + return hdr; + } +} + +/** + Convert token string to index. + @param cur [in] token head + @param len [in] token length + @param idx [out] the index number, or USIZE_MAX if token is '-' + @return true if token is a valid array index + */ +static_inline bool ptr_token_to_idx(const char *cur, usize len, usize *idx) { + const char *end = cur + len; + usize num = 0, add; + if (unlikely(len == 0 || len > USIZE_SAFE_DIG)) return false; + if (*cur == '0') { + if (unlikely(len > 1)) return false; + *idx = 0; + return true; + } + if (*cur == '-') { + if (unlikely(len > 1)) return false; + *idx = USIZE_MAX; + return true; + } + for (; cur < end && (add = (usize)((u8)*cur - (u8)'0')) <= 9; cur++) { + num = num * 10 + add; + } + if (unlikely(num == 0 || cur < end)) return false; + *idx = num; + return true; +} + +/** + Compare JSON key with token. + @param key a string key (yyjson_val or yyjson_mut_val) + @param token a JSON pointer token + @param len unescaped token length + @param esc number of escaped characters in this token + @return true if `str` is equals to `token` + */ +static_inline bool ptr_token_eq(void *key, + const char *token, usize len, usize esc) { + yyjson_val *val = (yyjson_val *)key; + if (unsafe_yyjson_get_len(val) != len) return false; + if (likely(!esc)) { + return memcmp(val->uni.str, token, len) == 0; + } else { + const char *str = val->uni.str; + for (; len-- > 0; token++, str++) { + if (*token == '~') { + if (*str != (*++token == '0' ? '~' : '/')) return false; + } else { + if (*str != *token) return false; + } + } + return true; + } +} + +/** + Get a value from array by token. + @param arr an array, should not be NULL or non-array type + @param token a JSON pointer token + @param len unescaped token length + @param esc number of escaped characters in this token + @return value at index, or NULL if token is not index or index is out of range + */ +static_inline yyjson_val *ptr_arr_get(yyjson_val *arr, const char *token, + usize len, usize esc) { + yyjson_val *val = unsafe_yyjson_get_first(arr); + usize num = unsafe_yyjson_get_len(arr), idx = 0; + if (unlikely(num == 0)) return NULL; + if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL; + if (unlikely(idx >= num)) return NULL; + if (unsafe_yyjson_arr_is_flat(arr)) { + return val + idx; + } else { + while (idx-- > 0) val = unsafe_yyjson_get_next(val); + return val; + } +} + +/** + Get a value from object by token. + @param obj [in] an object, should not be NULL or non-object type + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @return value associated with the token, or NULL if no value + */ +static_inline yyjson_val *ptr_obj_get(yyjson_val *obj, const char *token, + usize len, usize esc) { + yyjson_val *key = unsafe_yyjson_get_first(obj); + usize num = unsafe_yyjson_get_len(obj); + if (unlikely(num == 0)) return NULL; + for (; num > 0; num--, key = unsafe_yyjson_get_next(key + 1)) { + if (ptr_token_eq(key, token, len, esc)) return key + 1; + } + return NULL; +} + +/** + Get a value from array by token. + @param arr [in] an array, should not be NULL or non-array type + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @param pre [out] previous (sibling) value of the returned value + @param last [out] whether index is last + @return value at index, or NULL if token is not index or index is out of range + */ +static_inline yyjson_mut_val *ptr_mut_arr_get(yyjson_mut_val *arr, + const char *token, + usize len, usize esc, + yyjson_mut_val **pre, + bool *last) { + yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; /* last (tail) */ + usize num = unsafe_yyjson_get_len(arr), idx; + if (last) *last = false; + if (pre) *pre = NULL; + if (unlikely(num == 0)) { + if (last && len == 1 && (*token == '0' || *token == '-')) *last = true; + return NULL; + } + if (unlikely(!ptr_token_to_idx(token, len, &idx))) return NULL; + if (last) *last = (idx == num || idx == USIZE_MAX); + if (unlikely(idx >= num)) return NULL; + while (idx-- > 0) val = val->next; + if (pre) *pre = val; + return val->next; +} + +/** + Get a value from object by token. + @param obj [in] an object, should not be NULL or non-object type + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @param pre [out] previous (sibling) key of the returned value's key + @return value associated with the token, or NULL if no value + */ +static_inline yyjson_mut_val *ptr_mut_obj_get(yyjson_mut_val *obj, + const char *token, + usize len, usize esc, + yyjson_mut_val **pre) { + yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr, *key; + usize num = unsafe_yyjson_get_len(obj); + if (pre) *pre = NULL; + if (unlikely(num == 0)) return NULL; + for (; num > 0; num--, pre_key = key) { + key = pre_key->next->next; + if (ptr_token_eq(key, token, len, esc)) { + if (pre) *pre = pre_key; + return key->next; + } + } + return NULL; +} + +/** + Create a string value with JSON pointer token. + @param token [in] a JSON pointer token + @param len [in] unescaped token length + @param esc [in] number of escaped characters in this token + @param doc [in] used for memory allocation when creating value + @return new string value, or NULL if memory allocation failed + */ +static_inline yyjson_mut_val *ptr_new_key(const char *token, + usize len, usize esc, + yyjson_mut_doc *doc) { + const char *src = token; + if (likely(!esc)) { + return yyjson_mut_strncpy(doc, src, len); + } else { + const char *end = src + len + esc; + char *dst = unsafe_yyjson_mut_str_alc(doc, len + esc); + char *str = dst; + if (unlikely(!dst)) return NULL; + for (; src < end; src++, dst++) { + if (*src != '~') *dst = *src; + else *dst = (*++src == '0' ? '~' : '/'); + } + *dst = '\0'; + return yyjson_mut_strn(doc, str, len); + } +} + +/* macros for yyjson_ptr */ +#define return_err(_ret, _code, _pos, _msg) do { \ + if (err) { \ + err->code = YYJSON_PTR_ERR_##_code; \ + err->msg = _msg; \ + err->pos = (usize)(_pos); \ + } \ + return _ret; \ +} while (false) + +#define return_err_resolve(_ret, _pos) \ + return_err(_ret, RESOLVE, _pos, "JSON pointer cannot be resolved") +#define return_err_syntax(_ret, _pos) \ + return_err(_ret, SYNTAX, _pos, "invalid escaped character") +#define return_err_alloc(_ret) \ + return_err(_ret, MEMORY_ALLOCATION, 0, "failed to create value") + +yyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t ptr_len, + yyjson_ptr_err *err) { + + const char *hdr = ptr, *end = ptr + ptr_len, *token; + usize len, esc; + yyjson_type type; + + while (true) { + token = ptr_next_token(&ptr, end, &len, &esc); + if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr); + type = unsafe_yyjson_get_type(val); + if (type == YYJSON_TYPE_OBJ) { + val = ptr_obj_get(val, token, len, esc); + } else if (type == YYJSON_TYPE_ARR) { + val = ptr_arr_get(val, token, len, esc); + } else { + val = NULL; + } + if (!val) return_err_resolve(NULL, token - hdr); + if (ptr == end) return val; + } +} + +yyjson_mut_val *unsafe_yyjson_mut_ptr_getx( + yyjson_mut_val *val, const char *ptr, size_t ptr_len, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + const char *hdr = ptr, *end = ptr + ptr_len, *token; + usize len, esc; + yyjson_mut_val *ctn, *pre = NULL; + yyjson_type type; + bool idx_is_last = false; + + while (true) { + token = ptr_next_token(&ptr, end, &len, &esc); + if (unlikely(!token)) return_err_syntax(NULL, ptr - hdr); + ctn = val; + type = unsafe_yyjson_get_type(val); + if (type == YYJSON_TYPE_OBJ) { + val = ptr_mut_obj_get(val, token, len, esc, &pre); + } else if (type == YYJSON_TYPE_ARR) { + val = ptr_mut_arr_get(val, token, len, esc, &pre, &idx_is_last); + } else { + val = NULL; + } + if (ctx && (ptr == end)) { + if (type == YYJSON_TYPE_OBJ || + (type == YYJSON_TYPE_ARR && (val || idx_is_last))) { + ctx->ctn = ctn; + ctx->pre = pre; + } + } + if (!val) return_err_resolve(NULL, token - hdr); + if (ptr == end) return val; + } +} + +bool unsafe_yyjson_mut_ptr_putx( + yyjson_mut_val *val, const char *ptr, size_t ptr_len, + yyjson_mut_val *new_val, yyjson_mut_doc *doc, bool create_parent, + bool insert_new, yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + const char *hdr = ptr, *end = ptr + ptr_len, *token; + usize token_len, esc, ctn_len; + yyjson_mut_val *ctn, *key, *pre = NULL; + yyjson_mut_val *sep_ctn = NULL, *sep_key = NULL, *sep_val = NULL; + yyjson_type ctn_type; + bool idx_is_last = false; + + /* skip exist parent nodes */ + while (true) { + token = ptr_next_token(&ptr, end, &token_len, &esc); + if (unlikely(!token)) return_err_syntax(false, ptr - hdr); + ctn = val; + ctn_type = unsafe_yyjson_get_type(ctn); + if (ctn_type == YYJSON_TYPE_OBJ) { + val = ptr_mut_obj_get(ctn, token, token_len, esc, &pre); + } else if (ctn_type == YYJSON_TYPE_ARR) { + val = ptr_mut_arr_get(ctn, token, token_len, esc, &pre, + &idx_is_last); + } else return_err_resolve(false, token - hdr); + if (!val) break; + if (ptr == end) break; /* is last token */ + } + + /* create parent nodes if not exist */ + if (unlikely(ptr != end)) { /* not last token */ + if (!create_parent) return_err_resolve(false, token - hdr); + + /* add value at last index if container is array */ + if (ctn_type == YYJSON_TYPE_ARR) { + if (!idx_is_last || !insert_new) { + return_err_resolve(false, token - hdr); + } + val = yyjson_mut_obj(doc); + if (!val) return_err_alloc(false); + + /* delay attaching until all operations are completed */ + sep_ctn = ctn; + sep_key = NULL; + sep_val = val; + + /* move to next token */ + ctn = val; + val = NULL; + ctn_type = YYJSON_TYPE_OBJ; + token = ptr_next_token(&ptr, end, &token_len, &esc); + if (unlikely(!token)) return_err_resolve(false, token - hdr); + } + + /* container is object, create parent nodes */ + while (ptr != end) { /* not last token */ + key = ptr_new_key(token, token_len, esc, doc); + if (!key) return_err_alloc(false); + val = yyjson_mut_obj(doc); + if (!val) return_err_alloc(false); + + /* delay attaching until all operations are completed */ + if (!sep_ctn) { + sep_ctn = ctn; + sep_key = key; + sep_val = val; + } else { + yyjson_mut_obj_add(ctn, key, val); + } + + /* move to next token */ + ctn = val; + val = NULL; + token = ptr_next_token(&ptr, end, &token_len, &esc); + if (unlikely(!token)) return_err_syntax(false, ptr - hdr); + } + } + + /* JSON pointer is resolved, insert or replace target value */ + ctn_len = unsafe_yyjson_get_len(ctn); + if (ctn_type == YYJSON_TYPE_OBJ) { + if (ctx) ctx->ctn = ctn; + if (!val || insert_new) { + /* insert new key-value pair */ + key = ptr_new_key(token, token_len, esc, doc); + if (unlikely(!key)) return_err_alloc(false); + if (ctx) ctx->pre = ctn_len ? (yyjson_mut_val *)ctn->uni.ptr : key; + unsafe_yyjson_mut_obj_add(ctn, key, new_val, ctn_len); + } else { + /* replace exist value */ + key = pre->next->next; + if (ctx) ctx->pre = pre; + if (ctx) ctx->old = val; + yyjson_mut_obj_put(ctn, key, new_val); + } + } else { + /* array */ + if (ctx && (val || idx_is_last)) ctx->ctn = ctn; + if (insert_new) { + /* append new value */ + if (val) { + pre->next = new_val; + new_val->next = val; + if (ctx) ctx->pre = pre; + unsafe_yyjson_set_len(ctn, ctn_len + 1); + } else if (idx_is_last) { + if (ctx) ctx->pre = ctn_len ? + (yyjson_mut_val *)ctn->uni.ptr : new_val; + yyjson_mut_arr_append(ctn, new_val); + } else { + return_err_resolve(false, token - hdr); + } + } else { + /* replace exist value */ + if (!val) return_err_resolve(false, token - hdr); + if (ctn_len > 1) { + new_val->next = val->next; + pre->next = new_val; + if (ctn->uni.ptr == val) ctn->uni.ptr = new_val; + } else { + new_val->next = new_val; + ctn->uni.ptr = new_val; + pre = new_val; + } + if (ctx) ctx->pre = pre; + if (ctx) ctx->old = val; + } + } + + /* all operations are completed, attach the new components to the target */ + if (unlikely(sep_ctn)) { + if (sep_key) yyjson_mut_obj_add(sep_ctn, sep_key, sep_val); + else yyjson_mut_arr_append(sep_ctn, sep_val); + } + return true; +} + +yyjson_mut_val *unsafe_yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_mut_val *cur_val; + yyjson_ptr_ctx cur_ctx; + memset(&cur_ctx, 0, sizeof(cur_ctx)); + if (!ctx) ctx = &cur_ctx; + cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); + if (!cur_val) return NULL; + + if (yyjson_mut_is_obj(ctx->ctn)) { + yyjson_mut_val *key = ctx->pre->next->next; + yyjson_mut_obj_put(ctx->ctn, key, new_val); + } else { + yyjson_ptr_ctx_replace(ctx, new_val); + } + ctx->old = cur_val; + return cur_val; +} + +yyjson_mut_val *unsafe_yyjson_mut_ptr_removex( + yyjson_mut_val *val, const char *ptr, size_t len, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_mut_val *cur_val; + yyjson_ptr_ctx cur_ctx; + memset(&cur_ctx, 0, sizeof(cur_ctx)); + if (!ctx) ctx = &cur_ctx; + cur_val = unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); + if (cur_val) { + if (yyjson_mut_is_obj(ctx->ctn)) { + yyjson_mut_val *key = ctx->pre->next->next; + yyjson_mut_obj_put(ctx->ctn, key, NULL); + } else { + yyjson_ptr_ctx_remove(ctx); + } + ctx->pre = NULL; + ctx->old = cur_val; + } + return cur_val; +} + +/* macros for yyjson_ptr */ +#undef return_err +#undef return_err_resolve +#undef return_err_syntax +#undef return_err_alloc + + + +/*============================================================================== + * MARK: - JSON Patch API (RFC 6902) (Public) + *============================================================================*/ + +/* JSON Patch operation */ +typedef enum patch_op { + PATCH_OP_ADD, /* path, value */ + PATCH_OP_REMOVE, /* path */ + PATCH_OP_REPLACE, /* path, value */ + PATCH_OP_MOVE, /* from, path */ + PATCH_OP_COPY, /* from, path */ + PATCH_OP_TEST, /* path, value */ + PATCH_OP_NONE /* invalid */ +} patch_op; + +static patch_op patch_op_get(yyjson_val *op) { + const char *str = op->uni.str; + switch (unsafe_yyjson_get_len(op)) { + case 3: + if (!memcmp(str, "add", 3)) return PATCH_OP_ADD; + return PATCH_OP_NONE; + case 4: + if (!memcmp(str, "move", 4)) return PATCH_OP_MOVE; + if (!memcmp(str, "copy", 4)) return PATCH_OP_COPY; + if (!memcmp(str, "test", 4)) return PATCH_OP_TEST; + return PATCH_OP_NONE; + case 6: + if (!memcmp(str, "remove", 6)) return PATCH_OP_REMOVE; + return PATCH_OP_NONE; + case 7: + if (!memcmp(str, "replace", 7)) return PATCH_OP_REPLACE; + return PATCH_OP_NONE; + default: + return PATCH_OP_NONE; + } +} + +/* macros for yyjson_patch */ +#define return_err(_code, _msg) do { \ + if (err->ptr.code == YYJSON_PTR_ERR_MEMORY_ALLOCATION) { \ + err->code = YYJSON_PATCH_ERROR_MEMORY_ALLOCATION; \ + err->msg = _msg; \ + memset(&err->ptr, 0, sizeof(yyjson_ptr_err)); \ + } else { \ + err->code = YYJSON_PATCH_ERROR_##_code; \ + err->msg = _msg; \ + err->idx = iter.idx ? iter.idx - 1 : 0; \ + } \ + return NULL; \ +} while (false) + +#define return_err_copy() \ + return_err(MEMORY_ALLOCATION, "failed to copy value") +#define return_err_key(_key) \ + return_err(MISSING_KEY, "missing key " _key) +#define return_err_val(_key) \ + return_err(INVALID_MEMBER, "invalid member " _key) + +#define ptr_get(_ptr) yyjson_mut_ptr_getx( \ + root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr) +#define ptr_add(_ptr, _val) yyjson_mut_ptr_addx( \ + root, _ptr->uni.str, _ptr##_len, _val, doc, false, NULL, &err->ptr) +#define ptr_remove(_ptr) yyjson_mut_ptr_removex( \ + root, _ptr->uni.str, _ptr##_len, NULL, &err->ptr) +#define ptr_replace(_ptr, _val)yyjson_mut_ptr_replacex( \ + root, _ptr->uni.str, _ptr##_len, _val, NULL, &err->ptr) + +yyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch, + yyjson_patch_err *err) { + + yyjson_mut_val *root; + yyjson_val *obj; + yyjson_arr_iter iter; + yyjson_patch_err err_tmp; + if (!err) err = &err_tmp; + memset(err, 0, sizeof(*err)); + memset(&iter, 0, sizeof(iter)); + + if (unlikely(!doc || !orig || !patch)) { + return_err(INVALID_PARAMETER, "input parameter is NULL"); + } + if (unlikely(!yyjson_is_arr(patch))) { + return_err(INVALID_PARAMETER, "input patch is not array"); + } + root = yyjson_val_mut_copy(doc, orig); + if (unlikely(!root)) return_err_copy(); + + /* iterate through the patch array */ + yyjson_arr_iter_init(patch, &iter); + while ((obj = yyjson_arr_iter_next(&iter))) { + patch_op op_enum; + yyjson_val *op, *path, *from = NULL, *value; + yyjson_mut_val *val = NULL, *test; + usize path_len, from_len = 0; + if (unlikely(!unsafe_yyjson_is_obj(obj))) { + return_err(INVALID_OPERATION, "JSON patch operation is not object"); + } + + /* get required member: op */ + op = yyjson_obj_get(obj, "op"); + if (unlikely(!op)) return_err_key("`op`"); + if (unlikely(!yyjson_is_str(op))) return_err_val("`op`"); + op_enum = patch_op_get(op); + + /* get required member: path */ + path = yyjson_obj_get(obj, "path"); + if (unlikely(!path)) return_err_key("`path`"); + if (unlikely(!yyjson_is_str(path))) return_err_val("`path`"); + path_len = unsafe_yyjson_get_len(path); + + /* get required member: value, from */ + switch ((int)op_enum) { + case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST: + value = yyjson_obj_get(obj, "value"); + if (unlikely(!value)) return_err_key("`value`"); + val = yyjson_val_mut_copy(doc, value); + if (unlikely(!val)) return_err_copy(); + break; + case PATCH_OP_MOVE: case PATCH_OP_COPY: + from = yyjson_obj_get(obj, "from"); + if (unlikely(!from)) return_err_key("`from`"); + if (unlikely(!yyjson_is_str(from))) return_err_val("`from`"); + from_len = unsafe_yyjson_get_len(from); + break; + default: + break; + } + + /* perform an operation */ + switch ((int)op_enum) { + case PATCH_OP_ADD: /* add(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_REMOVE: /* remove(path) */ + if (unlikely(!ptr_remove(path))) { + return_err(POINTER, "failed to remove `path`"); + } + break; + case PATCH_OP_REPLACE: /* replace(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_replace(path, val))) { + return_err(POINTER, "failed to replace `path`"); + } + break; + case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */ + if (unlikely(from_len == 0 && path_len == 0)) break; + val = ptr_remove(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to remove `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */ + val = ptr_get(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to get `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + val = yyjson_mut_val_mut_copy(doc, val); + if (unlikely(!val)) return_err_copy(); + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_TEST: /* test = get(path), test.eq(val) */ + test = ptr_get(path); + if (unlikely(!test)) { + return_err(POINTER, "failed to get `path`"); + } + if (unlikely(!yyjson_mut_equals(val, test))) { + return_err(EQUAL, "failed to test equal"); + } + break; + default: + return_err(INVALID_MEMBER, "unsupported `op`"); + } + } + return root; +} + +yyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch, + yyjson_patch_err *err) { + yyjson_mut_val *root, *obj; + yyjson_mut_arr_iter iter; + yyjson_patch_err err_tmp; + if (!err) err = &err_tmp; + memset(err, 0, sizeof(*err)); + memset(&iter, 0, sizeof(iter)); + + if (unlikely(!doc || !orig || !patch)) { + return_err(INVALID_PARAMETER, "input parameter is NULL"); + } + if (unlikely(!yyjson_mut_is_arr(patch))) { + return_err(INVALID_PARAMETER, "input patch is not array"); + } + root = yyjson_mut_val_mut_copy(doc, orig); + if (unlikely(!root)) return_err_copy(); + + /* iterate through the patch array */ + yyjson_mut_arr_iter_init(patch, &iter); + while ((obj = yyjson_mut_arr_iter_next(&iter))) { + patch_op op_enum; + yyjson_mut_val *op, *path, *from = NULL, *value; + yyjson_mut_val *val = NULL, *test; + usize path_len, from_len = 0; + if (!unsafe_yyjson_is_obj(obj)) { + return_err(INVALID_OPERATION, "JSON patch operation is not object"); + } + + /* get required member: op */ + op = yyjson_mut_obj_get(obj, "op"); + if (unlikely(!op)) return_err_key("`op`"); + if (unlikely(!yyjson_mut_is_str(op))) return_err_val("`op`"); + op_enum = patch_op_get((yyjson_val *)(void *)op); + + /* get required member: path */ + path = yyjson_mut_obj_get(obj, "path"); + if (unlikely(!path)) return_err_key("`path`"); + if (unlikely(!yyjson_mut_is_str(path))) return_err_val("`path`"); + path_len = unsafe_yyjson_get_len(path); + + /* get required member: value, from */ + switch ((int)op_enum) { + case PATCH_OP_ADD: case PATCH_OP_REPLACE: case PATCH_OP_TEST: + value = yyjson_mut_obj_get(obj, "value"); + if (unlikely(!value)) return_err_key("`value`"); + val = yyjson_mut_val_mut_copy(doc, value); + if (unlikely(!val)) return_err_copy(); + break; + case PATCH_OP_MOVE: case PATCH_OP_COPY: + from = yyjson_mut_obj_get(obj, "from"); + if (unlikely(!from)) return_err_key("`from`"); + if (unlikely(!yyjson_mut_is_str(from))) { + return_err_val("`from`"); + } + from_len = unsafe_yyjson_get_len(from); + break; + default: + break; + } + + /* perform an operation */ + switch ((int)op_enum) { + case PATCH_OP_ADD: /* add(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_REMOVE: /* remove(path) */ + if (unlikely(!ptr_remove(path))) { + return_err(POINTER, "failed to remove `path`"); + } + break; + case PATCH_OP_REPLACE: /* replace(path, val) */ + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_replace(path, val))) { + return_err(POINTER, "failed to replace `path`"); + } + break; + case PATCH_OP_MOVE: /* val = remove(from), add(path, val) */ + if (unlikely(from_len == 0 && path_len == 0)) break; + val = ptr_remove(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to remove `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_COPY: /* val = get(from).copy, add(path, val) */ + val = ptr_get(from); + if (unlikely(!val)) { + return_err(POINTER, "failed to get `from`"); + } + if (unlikely(path_len == 0)) { root = val; break; } + val = yyjson_mut_val_mut_copy(doc, val); + if (unlikely(!val)) return_err_copy(); + if (unlikely(!ptr_add(path, val))) { + return_err(POINTER, "failed to add `path`"); + } + break; + case PATCH_OP_TEST: /* test = get(path), test.eq(val) */ + test = ptr_get(path); + if (unlikely(!test)) { + return_err(POINTER, "failed to get `path`"); + } + if (unlikely(!yyjson_mut_equals(val, test))) { + return_err(EQUAL, "failed to test equal"); + } + break; + default: + return_err(INVALID_MEMBER, "unsupported `op`"); + } + } + return root; +} + +/* macros for yyjson_patch */ +#undef return_err +#undef return_err_copy +#undef return_err_key +#undef return_err_val +#undef ptr_get +#undef ptr_add +#undef ptr_remove +#undef ptr_replace + + + +/*============================================================================== + * MARK: - JSON Merge-Patch API (RFC 7386) (Public) + *============================================================================*/ + +yyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch) { + usize idx, max; + yyjson_val *key, *orig_val, *patch_val, local_orig; + yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val; + + if (unlikely(!yyjson_is_obj(patch))) { + return yyjson_val_mut_copy(doc, patch); + } + + builder = yyjson_mut_obj(doc); + if (unlikely(!builder)) return NULL; + + memset(&local_orig, 0, sizeof(local_orig)); + if (!yyjson_is_obj(orig)) { + orig = &local_orig; + orig->tag = builder->tag; + orig->uni = builder->uni; + } + + /* If orig is contributing, copy any items not modified by the patch */ + if (orig != &local_orig) { + yyjson_obj_foreach(orig, idx, max, key, orig_val) { + patch_val = yyjson_obj_getn(patch, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + if (!patch_val) { + mut_key = yyjson_val_mut_copy(doc, key); + mut_val = yyjson_val_mut_copy(doc, orig_val); + if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL; + } + } + } + + /* Merge items modified by the patch. */ + yyjson_obj_foreach(patch, idx, max, key, patch_val) { + /* null indicates the field is removed. */ + if (unsafe_yyjson_is_null(patch_val)) { + continue; + } + mut_key = yyjson_val_mut_copy(doc, key); + orig_val = yyjson_obj_getn(orig, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + merged_val = yyjson_merge_patch(doc, orig_val, patch_val); + if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL; + } + + return builder; +} + +yyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch) { + usize idx, max; + yyjson_mut_val *key, *orig_val, *patch_val, local_orig; + yyjson_mut_val *builder, *mut_key, *mut_val, *merged_val; + + if (unlikely(!yyjson_mut_is_obj(patch))) { + return yyjson_mut_val_mut_copy(doc, patch); + } + + builder = yyjson_mut_obj(doc); + if (unlikely(!builder)) return NULL; + + memset(&local_orig, 0, sizeof(local_orig)); + if (!yyjson_mut_is_obj(orig)) { + orig = &local_orig; + orig->tag = builder->tag; + orig->uni = builder->uni; + } + + /* If orig is contributing, copy any items not modified by the patch */ + if (orig != &local_orig) { + yyjson_mut_obj_foreach(orig, idx, max, key, orig_val) { + patch_val = yyjson_mut_obj_getn(patch, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + if (!patch_val) { + mut_key = yyjson_mut_val_mut_copy(doc, key); + mut_val = yyjson_mut_val_mut_copy(doc, orig_val); + if (!yyjson_mut_obj_add(builder, mut_key, mut_val)) return NULL; + } + } + } + + /* Merge items modified by the patch. */ + yyjson_mut_obj_foreach(patch, idx, max, key, patch_val) { + /* null indicates the field is removed. */ + if (unsafe_yyjson_is_null(patch_val)) { + continue; + } + mut_key = yyjson_mut_val_mut_copy(doc, key); + orig_val = yyjson_mut_obj_getn(orig, + unsafe_yyjson_get_str(key), + unsafe_yyjson_get_len(key)); + merged_val = yyjson_mut_merge_patch(doc, orig_val, patch_val); + if (!yyjson_mut_obj_add(builder, mut_key, merged_val)) return NULL; + } + + return builder; +} + +#endif /* YYJSON_DISABLE_UTILS */ diff --git a/vendor/yyjson.h b/vendor/yyjson.h new file mode 100644 index 0000000..5eb6d46 --- /dev/null +++ b/vendor/yyjson.h @@ -0,0 +1,8230 @@ +/*============================================================================== + Copyright (c) 2020 YaoYuan + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + *============================================================================*/ + +/** + @file yyjson.h + @date 2019-03-09 + @author YaoYuan + */ + +#ifndef YYJSON_H +#define YYJSON_H + + + +/*============================================================================== + * MARK: - Header Files + *============================================================================*/ + +#include +#include +#include +#include +#include +#include + + + +/*============================================================================== + * MARK: - Compile-time Options + *============================================================================*/ + +/* + Define as 1 to disable JSON reader at compile-time. + This disables functions with "read" in their name. + Reduces binary size by about 60%. + */ +#ifndef YYJSON_DISABLE_READER +#endif + +/* + Define as 1 to disable JSON writer at compile-time. + This disables functions with "write" in their name. + Reduces binary size by about 30%. + */ +#ifndef YYJSON_DISABLE_WRITER +#endif + +/* + Define as 1 to disable JSON incremental reader at compile-time. + This disables functions with "incr" in their name. + */ +#ifndef YYJSON_DISABLE_INCR_READER +#endif + +/* + Define as 1 to disable JSON Pointer, JSON Patch and JSON Merge Patch supports. + This disables functions with "ptr" or "patch" in their name. + */ +#ifndef YYJSON_DISABLE_UTILS +#endif + +/* + Define as 1 to disable the fast floating-point number conversion in yyjson. + Libc's `strtod/snprintf` will be used instead. + + This reduces binary size by about 30%, but significantly slows down the + floating-point read/write speed. + */ +#ifndef YYJSON_DISABLE_FAST_FP_CONV +#endif + +/* + Define as 1 to disable non-standard JSON features support at compile-time, + such as YYJSON_READ_ALLOW_XXX and YYJSON_WRITE_ALLOW_XXX. + + This reduces binary size by about 10%, and slightly improves performance. + */ +#ifndef YYJSON_DISABLE_NON_STANDARD +#endif + +/* + Define as 1 to disable UTF-8 validation at compile-time. + + Use this if all input strings are guaranteed to be valid UTF-8 + (e.g. language-level String types are already validated). + + Disabling UTF-8 validation improves performance for non-ASCII strings by about + 3% to 7%. + + Note: If this flag is enabled while passing illegal UTF-8 strings, + the following errors may occur: + - Escaped characters may be ignored when parsing JSON strings. + - Ending quotes may be ignored when parsing JSON strings, causing the + string to merge with the next value. + - When serializing with `yyjson_mut_val`, the string's end may be accessed + out of bounds, potentially causing a segmentation fault. + */ +#ifndef YYJSON_DISABLE_UTF8_VALIDATION +#endif + +/* + Define as 1 to improve performance on architectures that do not support + unaligned memory access. + + Normally, this does not need to be set manually. See the C file for details. + */ +#ifndef YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS +#endif + +/* Define as 1 to export symbols when building this library as a Windows DLL. */ +#ifndef YYJSON_EXPORTS +#endif + +/* Define as 1 to import symbols when using this library as a Windows DLL. */ +#ifndef YYJSON_IMPORTS +#endif + +/* Define as 1 to include for compilers without C99 support. */ +#ifndef YYJSON_HAS_STDINT_H +#endif + +/* Define as 1 to include for compilers without C99 support. */ +#ifndef YYJSON_HAS_STDBOOL_H +#endif + + + +/*============================================================================== + * MARK: - Compiler Macros + *============================================================================*/ + +/** compiler version (MSVC) */ +#ifdef _MSC_VER +# define YYJSON_MSC_VER _MSC_VER +#else +# define YYJSON_MSC_VER 0 +#endif + +/** compiler version (GCC) */ +#ifdef __GNUC__ +# define YYJSON_GCC_VER __GNUC__ +# if defined(__GNUC_PATCHLEVEL__) +# define yyjson_gcc_available(major, minor, patch) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) \ + >= (major * 10000 + minor * 100 + patch)) +# else +# define yyjson_gcc_available(major, minor, patch) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100) \ + >= (major * 10000 + minor * 100 + patch)) +# endif +#else +# define YYJSON_GCC_VER 0 +# define yyjson_gcc_available(major, minor, patch) 0 +#endif + +/** real gcc check */ +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ + !defined(__clang__) && !defined(__llvm__) && \ + !defined(__INTEL_COMPILER) && !defined(__ICC) && \ + !defined(__NVCC__) && !defined(__PGI) && !defined(__TINYC__) +# define YYJSON_IS_REAL_GCC 1 +#else +# define YYJSON_IS_REAL_GCC 0 +#endif + +/** C version (STDC) */ +#if defined(__STDC__) && (__STDC__ >= 1) && defined(__STDC_VERSION__) +# define YYJSON_STDC_VER __STDC_VERSION__ +#else +# define YYJSON_STDC_VER 0 +#endif + +/** C++ version */ +#if defined(__cplusplus) +# define YYJSON_CPP_VER __cplusplus +#else +# define YYJSON_CPP_VER 0 +#endif + +/** compiler builtin check (since gcc 10.0, clang 2.6, icc 2021) */ +#ifndef yyjson_has_builtin +# ifdef __has_builtin +# define yyjson_has_builtin(x) __has_builtin(x) +# else +# define yyjson_has_builtin(x) 0 +# endif +#endif + +/** compiler attribute check (since gcc 5.0, clang 2.9, icc 17) */ +#ifndef yyjson_has_attribute +# ifdef __has_attribute +# define yyjson_has_attribute(x) __has_attribute(x) +# else +# define yyjson_has_attribute(x) 0 +# endif +#endif + +/** compiler feature check (since clang 2.6, icc 17) */ +#ifndef yyjson_has_feature +# ifdef __has_feature +# define yyjson_has_feature(x) __has_feature(x) +# else +# define yyjson_has_feature(x) 0 +# endif +#endif + +/** include check (since gcc 5.0, clang 2.7, icc 16, msvc 2017 15.3) */ +#ifndef yyjson_has_include +# ifdef __has_include +# define yyjson_has_include(x) __has_include(x) +# else +# define yyjson_has_include(x) 0 +# endif +#endif + +/** inline for compiler */ +#ifndef yyjson_inline +# if YYJSON_MSC_VER >= 1200 +# define yyjson_inline __forceinline +# elif defined(_MSC_VER) +# define yyjson_inline __inline +# elif yyjson_has_attribute(always_inline) || YYJSON_GCC_VER >= 4 +# define yyjson_inline __inline__ __attribute__((always_inline)) +# elif defined(__clang__) || defined(__GNUC__) +# define yyjson_inline __inline__ +# elif defined(__cplusplus) || YYJSON_STDC_VER >= 199901L +# define yyjson_inline inline +# else +# define yyjson_inline +# endif +#endif + +/** noinline for compiler */ +#ifndef yyjson_noinline +# if YYJSON_MSC_VER >= 1400 +# define yyjson_noinline __declspec(noinline) +# elif yyjson_has_attribute(noinline) || YYJSON_GCC_VER >= 4 +# define yyjson_noinline __attribute__((noinline)) +# else +# define yyjson_noinline +# endif +#endif + +/** align for compiler */ +#ifndef yyjson_align +# if YYJSON_MSC_VER >= 1300 +# define yyjson_align(x) __declspec(align(x)) +# elif yyjson_has_attribute(aligned) || defined(__GNUC__) +# define yyjson_align(x) __attribute__((aligned(x))) +# elif YYJSON_CPP_VER >= 201103L +# define yyjson_align(x) alignas(x) +# else +# define yyjson_align(x) +# endif +#endif + +/** likely for compiler */ +#ifndef yyjson_likely +# if yyjson_has_builtin(__builtin_expect) || \ + (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5) +# define yyjson_likely(expr) __builtin_expect(!!(expr), 1) +# else +# define yyjson_likely(expr) (expr) +# endif +#endif + +/** unlikely for compiler */ +#ifndef yyjson_unlikely +# if yyjson_has_builtin(__builtin_expect) || \ + (YYJSON_GCC_VER >= 4 && YYJSON_GCC_VER != 5) +# define yyjson_unlikely(expr) __builtin_expect(!!(expr), 0) +# else +# define yyjson_unlikely(expr) (expr) +# endif +#endif + +/** compile-time constant check for compiler */ +#ifndef yyjson_constant_p +# if yyjson_has_builtin(__builtin_constant_p) || (YYJSON_GCC_VER >= 3) +# define YYJSON_HAS_CONSTANT_P 1 +# define yyjson_constant_p(value) __builtin_constant_p(value) +# else +# define YYJSON_HAS_CONSTANT_P 0 +# define yyjson_constant_p(value) 0 +# endif +#endif + +/** deprecate warning */ +#ifndef yyjson_deprecated +# if YYJSON_MSC_VER >= 1400 +# define yyjson_deprecated(msg) __declspec(deprecated(msg)) +# elif yyjson_has_feature(attribute_deprecated_with_message) || \ + (YYJSON_GCC_VER > 4 || (YYJSON_GCC_VER == 4 && __GNUC_MINOR__ >= 5)) +# define yyjson_deprecated(msg) __attribute__((deprecated(msg))) +# elif YYJSON_GCC_VER >= 3 +# define yyjson_deprecated(msg) __attribute__((deprecated)) +# else +# define yyjson_deprecated(msg) +# endif +#endif + +/** function export */ +#ifndef yyjson_api +# if defined(_WIN32) +# if defined(YYJSON_EXPORTS) && YYJSON_EXPORTS +# define yyjson_api __declspec(dllexport) +# elif defined(YYJSON_IMPORTS) && YYJSON_IMPORTS +# define yyjson_api __declspec(dllimport) +# else +# define yyjson_api +# endif +# elif yyjson_has_attribute(visibility) || YYJSON_GCC_VER >= 4 +# define yyjson_api __attribute__((visibility("default"))) +# else +# define yyjson_api +# endif +#endif + +/** inline function export */ +#ifndef yyjson_api_inline +# define yyjson_api_inline static yyjson_inline +#endif + +/** stdint (C89 compatible) */ +#if (defined(YYJSON_HAS_STDINT_H) && YYJSON_HAS_STDINT_H) || \ + YYJSON_MSC_VER >= 1600 || YYJSON_STDC_VER >= 199901L || \ + defined(_STDINT_H) || defined(_STDINT_H_) || \ + defined(__CLANG_STDINT_H) || defined(_STDINT_H_INCLUDED) || \ + yyjson_has_include() +# include +#elif defined(_MSC_VER) +# if _MSC_VER < 1300 + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; +# else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; + typedef signed __int64 int64_t; + typedef unsigned __int64 uint64_t; +# endif +#else +# if UCHAR_MAX == 0xFFU + typedef signed char int8_t; + typedef unsigned char uint8_t; +# else +# error cannot find 8-bit integer type +# endif +# if USHRT_MAX == 0xFFFFU + typedef unsigned short uint16_t; + typedef signed short int16_t; +# elif UINT_MAX == 0xFFFFU + typedef unsigned int uint16_t; + typedef signed int int16_t; +# else +# error cannot find 16-bit integer type +# endif +# if UINT_MAX == 0xFFFFFFFFUL + typedef unsigned int uint32_t; + typedef signed int int32_t; +# elif ULONG_MAX == 0xFFFFFFFFUL + typedef unsigned long uint32_t; + typedef signed long int32_t; +# elif USHRT_MAX == 0xFFFFFFFFUL + typedef unsigned short uint32_t; + typedef signed short int32_t; +# else +# error cannot find 32-bit integer type +# endif +# if defined(__INT64_TYPE__) && defined(__UINT64_TYPE__) + typedef __INT64_TYPE__ int64_t; + typedef __UINT64_TYPE__ uint64_t; +# elif defined(__GNUC__) || defined(__clang__) +# if !defined(_SYS_TYPES_H) && !defined(__int8_t_defined) + __extension__ typedef long long int64_t; +# endif + __extension__ typedef unsigned long long uint64_t; +# elif defined(_LONG_LONG) || defined(__MWERKS__) || defined(_CRAYC) || \ + defined(__SUNPRO_C) || defined(__SUNPRO_CC) + typedef long long int64_t; + typedef unsigned long long uint64_t; +# elif (defined(__BORLANDC__) && __BORLANDC__ > 0x460) || \ + defined(__WATCOM_INT64__) || defined (__alpha) || defined (__DECC) + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; +# else +# error cannot find 64-bit integer type +# endif +#endif + +/** stdbool (C89 compatible) */ +#if (defined(YYJSON_HAS_STDBOOL_H) && YYJSON_HAS_STDBOOL_H) || \ + (yyjson_has_include() && !defined(__STRICT_ANSI__)) || \ + YYJSON_MSC_VER >= 1800 || YYJSON_STDC_VER >= 199901L +# include +#elif !defined(__bool_true_false_are_defined) +# define __bool_true_false_are_defined 1 +# if defined(__cplusplus) +# if defined(__GNUC__) && !defined(__STRICT_ANSI__) +# define _Bool bool +# if __cplusplus < 201103L +# define bool bool +# define false false +# define true true +# endif +# endif +# else +# define bool unsigned char +# define true 1 +# define false 0 +# endif +#endif + +/** char bit check */ +#if defined(CHAR_BIT) +# if CHAR_BIT != 8 +# error non 8-bit char is not supported +# endif +#endif + +/** + Microsoft Visual C++ 6.0 doesn't support converting number from u64 to f64: + error C2520: conversion from unsigned __int64 to double not implemented. + */ +#ifndef YYJSON_U64_TO_F64_NO_IMPL +# if (0 < YYJSON_MSC_VER) && (YYJSON_MSC_VER <= 1200) +# define YYJSON_U64_TO_F64_NO_IMPL 1 +# else +# define YYJSON_U64_TO_F64_NO_IMPL 0 +# endif +#endif + + + +/*============================================================================== + * MARK: - Compile Hint Begin + *============================================================================*/ + +/* extern "C" begin */ +#ifdef __cplusplus +extern "C" { +#endif + +/* warning suppress begin */ +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-parameter" +#elif defined(__GNUC__) +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic push +# endif +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wunused-parameter" +#elif defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable:4800) /* 'int': forcing value to 'true' or 'false' */ +#endif + + + +/*============================================================================== + * MARK: - Version + *============================================================================*/ + +/** The major version of yyjson. */ +#define YYJSON_VERSION_MAJOR 0 + +/** The minor version of yyjson. */ +#define YYJSON_VERSION_MINOR 12 + +/** The patch version of yyjson. */ +#define YYJSON_VERSION_PATCH 0 + +/** The version of yyjson in hex: `(major << 16) | (minor << 8) | (patch)`. */ +#define YYJSON_VERSION_HEX 0x000C00 + +/** The version string of yyjson. */ +#define YYJSON_VERSION_STRING "0.12.0" + +/** The version of yyjson in hex, same as `YYJSON_VERSION_HEX`. */ +yyjson_api uint32_t yyjson_version(void); + + + +/*============================================================================== + * MARK: - JSON Types + *============================================================================*/ + +/** Type of a JSON value (3 bit). */ +typedef uint8_t yyjson_type; +/** No type, invalid. */ +#define YYJSON_TYPE_NONE ((uint8_t)0) /* _____000 */ +/** Raw string type, no subtype. */ +#define YYJSON_TYPE_RAW ((uint8_t)1) /* _____001 */ +/** Null type: `null` literal, no subtype. */ +#define YYJSON_TYPE_NULL ((uint8_t)2) /* _____010 */ +/** Boolean type, subtype: TRUE, FALSE. */ +#define YYJSON_TYPE_BOOL ((uint8_t)3) /* _____011 */ +/** Number type, subtype: UINT, SINT, REAL. */ +#define YYJSON_TYPE_NUM ((uint8_t)4) /* _____100 */ +/** String type, subtype: NONE, NOESC. */ +#define YYJSON_TYPE_STR ((uint8_t)5) /* _____101 */ +/** Array type, no subtype. */ +#define YYJSON_TYPE_ARR ((uint8_t)6) /* _____110 */ +/** Object type, no subtype. */ +#define YYJSON_TYPE_OBJ ((uint8_t)7) /* _____111 */ + +/** Subtype of a JSON value (2 bit). */ +typedef uint8_t yyjson_subtype; +/** No subtype. */ +#define YYJSON_SUBTYPE_NONE ((uint8_t)(0 << 3)) /* ___00___ */ +/** False subtype: `false` literal. */ +#define YYJSON_SUBTYPE_FALSE ((uint8_t)(0 << 3)) /* ___00___ */ +/** True subtype: `true` literal. */ +#define YYJSON_SUBTYPE_TRUE ((uint8_t)(1 << 3)) /* ___01___ */ +/** Unsigned integer subtype: `uint64_t`. */ +#define YYJSON_SUBTYPE_UINT ((uint8_t)(0 << 3)) /* ___00___ */ +/** Signed integer subtype: `int64_t`. */ +#define YYJSON_SUBTYPE_SINT ((uint8_t)(1 << 3)) /* ___01___ */ +/** Real number subtype: `double`. */ +#define YYJSON_SUBTYPE_REAL ((uint8_t)(2 << 3)) /* ___10___ */ +/** String that do not need to be escaped for writing (internal use). */ +#define YYJSON_SUBTYPE_NOESC ((uint8_t)(1 << 3)) /* ___01___ */ + +/** The mask used to extract the type of a JSON value. */ +#define YYJSON_TYPE_MASK ((uint8_t)0x07) /* _____111 */ +/** The number of bits used by the type. */ +#define YYJSON_TYPE_BIT ((uint8_t)3) +/** The mask used to extract the subtype of a JSON value. */ +#define YYJSON_SUBTYPE_MASK ((uint8_t)0x18) /* ___11___ */ +/** The number of bits used by the subtype. */ +#define YYJSON_SUBTYPE_BIT ((uint8_t)2) +/** The mask used to extract the reserved bits of a JSON value. */ +#define YYJSON_RESERVED_MASK ((uint8_t)0xE0) /* 111_____ */ +/** The number of reserved bits. */ +#define YYJSON_RESERVED_BIT ((uint8_t)3) +/** The mask used to extract the tag of a JSON value. */ +#define YYJSON_TAG_MASK ((uint8_t)0xFF) /* 11111111 */ +/** The number of bits used by the tag. */ +#define YYJSON_TAG_BIT ((uint8_t)8) + +/** Padding size for JSON reader. */ +#define YYJSON_PADDING_SIZE 4 + + + +/*============================================================================== + * MARK: - Allocator + *============================================================================*/ + +/** + A memory allocator. + + Typically you don't need to use it, unless you want to customize your own + memory allocator. + */ +typedef struct yyjson_alc { + /** Same as libc's malloc(size), should not be NULL. */ + void *(*malloc)(void *ctx, size_t size); + /** Same as libc's realloc(ptr, size), should not be NULL. */ + void *(*realloc)(void *ctx, void *ptr, size_t old_size, size_t size); + /** Same as libc's free(ptr), should not be NULL. */ + void (*free)(void *ctx, void *ptr); + /** A context for malloc/realloc/free, can be NULL. */ + void *ctx; +} yyjson_alc; + +/** + A pool allocator uses fixed length pre-allocated memory. + + This allocator may be used to avoid malloc/realloc calls. The pre-allocated + memory should be held by the caller. The maximum amount of memory required to + read a JSON can be calculated using the `yyjson_read_max_memory_usage()` + function, but the amount of memory required to write a JSON cannot be directly + calculated. + + This is not a general-purpose allocator. It is designed to handle a single JSON + data at a time. If it is used for overly complex memory tasks, such as parsing + multiple JSON documents using the same allocator but releasing only a few of + them, it may cause memory fragmentation, resulting in performance degradation + and memory waste. + + @param alc The allocator to be initialized. + If this parameter is NULL, the function will fail and return false. + If `buf` or `size` is invalid, this will be set to an empty allocator. + @param buf The buffer memory for this allocator. + If this parameter is NULL, the function will fail and return false. + @param size The size of `buf`, in bytes. + If this parameter is less than 8 words (32/64 bytes on 32/64-bit OS), the + function will fail and return false. + @return true if the `alc` has been successfully initialized. + + @b Example + @code + // parse JSON with stack memory + char buf[1024]; + yyjson_alc alc; + yyjson_alc_pool_init(&alc, buf, 1024); + + const char *json = "{\"name\":\"Helvetica\",\"size\":16}" + yyjson_doc *doc = yyjson_read_opts(json, strlen(json), 0, &alc, NULL); + // the memory of `doc` is on the stack + @endcode + + @warning This Allocator is not thread-safe. + */ +yyjson_api bool yyjson_alc_pool_init(yyjson_alc *alc, void *buf, size_t size); + +/** + A dynamic allocator. + + This allocator has a similar usage to the pool allocator above. However, when + there is not enough memory, this allocator will dynamically request more memory + using libc's `malloc` function, and frees it all at once when it is destroyed. + + @return A new dynamic allocator, or NULL if memory allocation failed. + @note The returned value should be freed with `yyjson_alc_dyn_free()`. + + @warning This Allocator is not thread-safe. + */ +yyjson_api yyjson_alc *yyjson_alc_dyn_new(void); + +/** + Free a dynamic allocator which is created by `yyjson_alc_dyn_new()`. + @param alc The dynamic allocator to be destroyed. + */ +yyjson_api void yyjson_alc_dyn_free(yyjson_alc *alc); + + + +/*============================================================================== + * MARK: - Text Locating + *============================================================================*/ + +/** + Locate the line and column number for a byte position in a string. + This can be used to get better description for error position. + + @param str The input string. + @param len The byte length of the input string. + @param pos The byte position within the input string. + @param line A pointer to receive the line number, starting from 1. + @param col A pointer to receive the column number, starting from 1. + @param chr A pointer to receive the character index, starting from 0. + @return true on success, false if `str` is NULL or `pos` is out of bounds. + @note Line/column/character are calculated based on Unicode characters for + compatibility with text editors. For multi-byte UTF-8 characters, + the returned value may not directly correspond to the byte position. + */ +yyjson_api bool yyjson_locate_pos(const char *str, size_t len, size_t pos, + size_t *line, size_t *col, size_t *chr); + + + +/*============================================================================== + * MARK: - JSON Structure + *============================================================================*/ + +/** + An immutable document for reading JSON. + This document holds memory for all its JSON values and strings. When it is no + longer used, the user should call `yyjson_doc_free()` to free its memory. + */ +typedef struct yyjson_doc yyjson_doc; + +/** + An immutable value for reading JSON. + A JSON Value has the same lifetime as its document. The memory is held by its + document and and cannot be freed alone. + */ +typedef struct yyjson_val yyjson_val; + +/** + A mutable document for building JSON. + This document holds memory for all its JSON values and strings. When it is no + longer used, the user should call `yyjson_mut_doc_free()` to free its memory. + */ +typedef struct yyjson_mut_doc yyjson_mut_doc; + +/** + A mutable value for building JSON. + A JSON Value has the same lifetime as its document. The memory is held by its + document and and cannot be freed alone. + */ +typedef struct yyjson_mut_val yyjson_mut_val; + + + +/*============================================================================== + * MARK: - JSON Reader API + *============================================================================*/ + +/** Run-time options for JSON reader. */ +typedef uint32_t yyjson_read_flag; + +/** Default option (RFC 8259 compliant): + - Read positive integer as uint64_t. + - Read negative integer as int64_t. + - Read floating-point number as double with round-to-nearest mode. + - Read integer which cannot fit in uint64_t or int64_t as double. + - Report error if double number is infinity. + - Report error if string contains invalid UTF-8 character or BOM. + - Report error on trailing commas, comments, inf and nan literals. */ +static const yyjson_read_flag YYJSON_READ_NOFLAG = 0; + +/** Read the input data in-situ. + This option allows the reader to modify and use input data to store string + values, which can increase reading speed slightly. + The caller should hold the input data before free the document. + The input data must be padded by at least `YYJSON_PADDING_SIZE` bytes. + For example: `[1,2]` should be `[1,2]\0\0\0\0`, input length should be 5. */ +static const yyjson_read_flag YYJSON_READ_INSITU = 1 << 0; + +/** Stop when done instead of issuing an error if there's additional content + after a JSON document. This option may be used to parse small pieces of JSON + in larger data, such as `NDJSON`. */ +static const yyjson_read_flag YYJSON_READ_STOP_WHEN_DONE = 1 << 1; + +/** Allow single trailing comma at the end of an object or array, + such as `[1,2,3,]`, `{"a":1,"b":2,}` (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_TRAILING_COMMAS = 1 << 2; + +/** Allow C-style single-line and mult-line comments (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_COMMENTS = 1 << 3; + +/** Allow inf/nan number and literal, case-insensitive, + such as 1e999, NaN, inf, -Infinity (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_INF_AND_NAN = 1 << 4; + +/** Read all numbers as raw strings (value with `YYJSON_TYPE_RAW` type), + inf/nan literal is also read as raw with `ALLOW_INF_AND_NAN` flag. */ +static const yyjson_read_flag YYJSON_READ_NUMBER_AS_RAW = 1 << 5; + +/** Allow reading invalid unicode when parsing string values (non-standard). + Invalid characters will be allowed to appear in the string values, but + invalid escape sequences will still be reported as errors. + This flag does not affect the performance of correctly encoded strings. + + @warning Strings in JSON values may contain incorrect encoding when this + option is used, you need to handle these strings carefully to avoid security + risks. */ +static const yyjson_read_flag YYJSON_READ_ALLOW_INVALID_UNICODE = 1 << 6; + +/** Read big numbers as raw strings. These big numbers include integers that + cannot be represented by `int64_t` and `uint64_t`, and floating-point + numbers that cannot be represented by finite `double`. + The flag will be overridden by `YYJSON_READ_NUMBER_AS_RAW` flag. */ +static const yyjson_read_flag YYJSON_READ_BIGNUM_AS_RAW = 1 << 7; + +/** Allow UTF-8 BOM and skip it before parsing if any (non-standard). */ +static const yyjson_read_flag YYJSON_READ_ALLOW_BOM = 1 << 8; + +/** Allow extended number formats (non-standard): + - Hexadecimal numbers, such as `0x7B`. + - Numbers with leading or trailing decimal point, such as `.123`, `123.`. + - Numbers with a leading plus sign, such as `+123`. */ +static const yyjson_read_flag YYJSON_READ_ALLOW_EXT_NUMBER = 1 << 9; + +/** Allow extended escape sequences in strings (non-standard): + - Additional escapes: `\a`, `\e`, `\v`, ``\'``, `\?`, `\0`. + - Hex escapes: `\xNN`, such as `\x7B`. + - Line continuation: backslash followed by line terminator sequences. + - Unknown escape: if backslash is followed by an unsupported character, + the backslash will be removed and the character will be kept as-is. + However, `\1`-`\9` will still trigger an error. */ +static const yyjson_read_flag YYJSON_READ_ALLOW_EXT_ESCAPE = 1 << 10; + +/** Allow extended whitespace characters (non-standard): + - Vertical tab `\v` and form feed `\f`. + - Line separator `\u2028` and paragraph separator `\u2029`. + - Non-breaking space `\xA0`. + - Byte order mark: `\uFEFF`. + - Other Unicode characters in the Zs (Separator, space) category. */ +static const yyjson_read_flag YYJSON_READ_ALLOW_EXT_WHITESPACE = 1 << 11; + +/** Allow strings enclosed in single quotes (non-standard), such as ``'ab'``. */ +static const yyjson_read_flag YYJSON_READ_ALLOW_SINGLE_QUOTED_STR = 1 << 12; + +/** Allow object keys without quotes (non-standard), such as `{a:1,b:2}`. + This extends the ECMAScript IdentifierName rule by allowing any + non-whitespace character with code point above `U+007F`. */ +static const yyjson_read_flag YYJSON_READ_ALLOW_UNQUOTED_KEY = 1 << 13; + +/** Allow JSON5 format, see: [https://json5.org]. + This flag supports all JSON5 features with some additional extensions: + - Accepts more escape sequences than JSON5 (e.g. `\a`, `\e`). + - Unquoted keys are not limited to ECMAScript IdentifierName. + - Allow case-insensitive `NaN`, `Inf` and `Infinity` literals. */ +static const yyjson_read_flag YYJSON_READ_JSON5 = + (1 << 2) | /* YYJSON_READ_ALLOW_TRAILING_COMMAS */ + (1 << 3) | /* YYJSON_READ_ALLOW_COMMENTS */ + (1 << 4) | /* YYJSON_READ_ALLOW_INF_AND_NAN */ + (1 << 9) | /* YYJSON_READ_ALLOW_EXT_NUMBER */ + (1 << 10) | /* YYJSON_READ_ALLOW_EXT_ESCAPE */ + (1 << 11) | /* YYJSON_READ_ALLOW_EXT_WHITESPACE */ + (1 << 12) | /* YYJSON_READ_ALLOW_SINGLE_QUOTED_STR */ + (1 << 13); /* YYJSON_READ_ALLOW_UNQUOTED_KEY */ + + + +/** Result code for JSON reader. */ +typedef uint32_t yyjson_read_code; + +/** Success, no error. */ +static const yyjson_read_code YYJSON_READ_SUCCESS = 0; + +/** Invalid parameter, such as NULL input string or 0 input length. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_PARAMETER = 1; + +/** Memory allocation failed. */ +static const yyjson_read_code YYJSON_READ_ERROR_MEMORY_ALLOCATION = 2; + +/** Input JSON string is empty. */ +static const yyjson_read_code YYJSON_READ_ERROR_EMPTY_CONTENT = 3; + +/** Unexpected content after document, such as `[123]abc`. */ +static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CONTENT = 4; + +/** Unexpected end of input, the parsed part is valid, such as `[123`. */ +static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_END = 5; + +/** Unexpected character inside the document, such as `[abc]`. */ +static const yyjson_read_code YYJSON_READ_ERROR_UNEXPECTED_CHARACTER = 6; + +/** Invalid JSON structure, such as `[1,]`. */ +static const yyjson_read_code YYJSON_READ_ERROR_JSON_STRUCTURE = 7; + +/** Invalid comment, deprecated, use `UNEXPECTED_END` for unclosed comment. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_COMMENT = 8; + +/** Invalid number, such as `123.e12`, `000`. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_NUMBER = 9; + +/** Invalid string, such as invalid escaped character inside a string. */ +static const yyjson_read_code YYJSON_READ_ERROR_INVALID_STRING = 10; + +/** Invalid JSON literal, such as `truu`. */ +static const yyjson_read_code YYJSON_READ_ERROR_LITERAL = 11; + +/** Failed to open a file. */ +static const yyjson_read_code YYJSON_READ_ERROR_FILE_OPEN = 12; + +/** Failed to read a file. */ +static const yyjson_read_code YYJSON_READ_ERROR_FILE_READ = 13; + +/** Incomplete input during incremental parsing; parsing state is preserved. */ +static const yyjson_read_code YYJSON_READ_ERROR_MORE = 14; + +/** Error information for JSON reader. */ +typedef struct yyjson_read_err { + /** Error code, see `yyjson_read_code` for all possible values. */ + yyjson_read_code code; + /** Error message, constant, no need to free (NULL if success). */ + const char *msg; + /** Error byte position for input data (0 if success). */ + size_t pos; +} yyjson_read_err; + + + +#if !defined(YYJSON_DISABLE_READER) || !YYJSON_DISABLE_READER + +/** + Read JSON with options. + + This function is thread-safe when: + 1. The `dat` is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is not required. + If this parameter is NULL, the function will fail and return NULL. + The `dat` will not be modified without the flag `YYJSON_READ_INSITU`, so you + can pass a `const char *` string and case it to `char *` if you don't use + the `YYJSON_READ_INSITU` flag. + @param len The length of JSON data in bytes. + If this parameter is 0, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + */ +yyjson_api yyjson_doc *yyjson_read_opts(char *dat, + size_t len, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read a JSON file. + + This function is thread-safe when: + 1. The file is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + This should be a null-terminated string using the system's native encoding. + If this path is NULL or invalid, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + + @warning On 32-bit operating system, files larger than 2GB may fail to read. + */ +yyjson_api yyjson_doc *yyjson_read_file(const char *path, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read JSON from a file pointer. + + @param fp The file pointer. + The data will be read from the current position of the FILE to the end. + If this fp is NULL or invalid, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + + @warning On 32-bit operating system, files larger than 2GB may fail to read. + */ +yyjson_api yyjson_doc *yyjson_read_fp(FILE *fp, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** + Read a JSON string. + + This function is thread-safe. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is not required. + If this parameter is NULL, the function will fail and return NULL. + @param len The length of JSON data in bytes. + If this parameter is 0, the function will fail and return NULL. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + @return A new JSON document, or NULL if an error occurs. + When it's no longer needed, it should be freed with `yyjson_doc_free()`. + */ +yyjson_api_inline yyjson_doc *yyjson_read(const char *dat, + size_t len, + yyjson_read_flag flg) { + flg &= ~YYJSON_READ_INSITU; /* const string cannot be modified */ + return yyjson_read_opts((char *)(void *)(size_t)(const void *)dat, + len, flg, NULL, NULL); +} + + + +#if !defined(YYJSON_DISABLE_INCR_READER) || !YYJSON_DISABLE_INCR_READER + +/** Opaque state for incremental JSON reader. */ +typedef struct yyjson_incr_state yyjson_incr_state; + +/** + Initialize state for incremental read. + + To read a large JSON document incrementally: + 1. Call `yyjson_incr_new()` to create the state for incremental reading. + 2. Call `yyjson_incr_read()` repeatedly. + 3. Call `yyjson_incr_free()` to free the state. + + Note: The incremental JSON reader only supports standard JSON. + Flags for non-standard features (e.g. comments, trailing commas) are ignored. + + @param buf The JSON data, null-terminator is not required. + If this parameter is NULL, the function will fail and return NULL. + @param buf_len The length of the JSON data in `buf`. + If use `YYJSON_READ_INSITU`, `buf_len` should not include the padding size. + @param flg The JSON read options. + Multiple options can be combined with `|` operator. + @param alc The memory allocator used by JSON reader. + Pass NULL to use the libc's default allocator. + @return A state for incremental reading. + It should be freed with `yyjson_incr_free()`. + NULL is returned if memory allocation fails. +*/ +yyjson_api yyjson_incr_state *yyjson_incr_new(char *buf, size_t buf_len, + yyjson_read_flag flg, + const yyjson_alc *alc); + +/** + Performs incremental read of up to `len` bytes. + + If NULL is returned and `err->code` is set to `YYJSON_READ_ERROR_MORE`, it + indicates that more data is required to continue parsing. Then, call this + function again with incremented `len`. Continue until a document is returned or + an error other than `YYJSON_READ_ERROR_MORE` is returned. + + Note: Parsing in very small increments is not efficient. An increment of + several kilobytes or megabytes is recommended. + + @param state The state for incremental reading, created using + `yyjson_incr_new()`. + @param len The number of bytes of JSON data available to parse. + If this parameter is 0, the function will fail and return NULL. + @param err A pointer to receive error information. + @return A new JSON document, or NULL if an error occurs. + When the document is no longer needed, it should be freed with + `yyjson_doc_free()`. +*/ +yyjson_api yyjson_doc *yyjson_incr_read(yyjson_incr_state *state, size_t len, + yyjson_read_err *err); + +/** Release the incremental read state and free the memory. */ +yyjson_api void yyjson_incr_free(yyjson_incr_state *state); + +#endif /* YYJSON_DISABLE_INCR_READER */ + +/** + Returns the size of maximum memory usage to read a JSON data. + + You may use this value to avoid malloc() or calloc() call inside the reader + to get better performance, or read multiple JSON with one piece of memory. + + @param len The length of JSON data in bytes. + @param flg The JSON read options. + @return The maximum memory size to read this JSON, or 0 if overflow. + + @b Example + @code + // read multiple JSON with same pre-allocated memory + + char *dat1, *dat2, *dat3; // JSON data + size_t len1, len2, len3; // JSON length + size_t max_len = MAX(len1, MAX(len2, len3)); + yyjson_doc *doc; + + // use one allocator for multiple JSON + size_t size = yyjson_read_max_memory_usage(max_len, 0); + void *buf = malloc(size); + yyjson_alc alc; + yyjson_alc_pool_init(&alc, buf, size); + + // no more alloc() or realloc() call during reading + doc = yyjson_read_opts(dat1, len1, 0, &alc, NULL); + yyjson_doc_free(doc); + doc = yyjson_read_opts(dat2, len2, 0, &alc, NULL); + yyjson_doc_free(doc); + doc = yyjson_read_opts(dat3, len3, 0, &alc, NULL); + yyjson_doc_free(doc); + + free(buf); + @endcode + @see yyjson_alc_pool_init() + */ +yyjson_api_inline size_t yyjson_read_max_memory_usage(size_t len, + yyjson_read_flag flg) { + /* + 1. The max value count is (json_size / 2 + 1), + for example: "[1,2,3,4]" size is 9, value count is 5. + 2. Some broken JSON may cost more memory during reading, but fail at end, + for example: "[[[[[[[[". + 3. yyjson use 16 bytes per value, see struct yyjson_val. + 4. yyjson use dynamic memory with a growth factor of 1.5. + + The max memory size is (json_size / 2 * 16 * 1.5 + padding). + */ + size_t mul = (size_t)12 + !(flg & YYJSON_READ_INSITU); + size_t pad = 256; + size_t max = (size_t)(~(size_t)0); + if (flg & YYJSON_READ_STOP_WHEN_DONE) len = len < 256 ? 256 : len; + if (len >= (max - pad - mul) / mul) return 0; + return len * mul + pad; +} + +/** + Read a JSON number. + + This function is thread-safe when data is not modified by other threads. + + @param dat The JSON data (UTF-8 without BOM), null-terminator is required. + If this parameter is NULL, the function will fail and return NULL. + @param val The output value where result is stored. + If this parameter is NULL, the function will fail and return NULL. + The value will hold either UINT or SINT or REAL number; + @param flg The JSON read options. + Multiple options can be combined with `|` operator. 0 means no options. + Supports `YYJSON_READ_NUMBER_AS_RAW` and `YYJSON_READ_ALLOW_INF_AND_NAN`. + @param alc The memory allocator used for long number. + It is only used when the built-in floating point reader is disabled. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return If successful, a pointer to the character after the last character + used in the conversion, NULL if an error occurs. + */ +yyjson_api const char *yyjson_read_number(const char *dat, + yyjson_val *val, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err); + +/** Same as `yyjson_read_number()`. */ +yyjson_api_inline const char *yyjson_mut_read_number(const char *dat, + yyjson_mut_val *val, + yyjson_read_flag flg, + const yyjson_alc *alc, + yyjson_read_err *err) { + return yyjson_read_number(dat, (yyjson_val *)val, flg, alc, err); +} + +#endif /* YYJSON_DISABLE_READER) */ + + + +/*============================================================================== + * MARK: - JSON Writer API + *============================================================================*/ + +/** Run-time options for JSON writer. */ +typedef uint32_t yyjson_write_flag; + +/** Default option: + - Write JSON minify. + - Report error on inf or nan number. + - Report error on invalid UTF-8 string. + - Do not escape unicode or slash. */ +static const yyjson_write_flag YYJSON_WRITE_NOFLAG = 0; + +/** Write JSON pretty with 4 space indent. */ +static const yyjson_write_flag YYJSON_WRITE_PRETTY = 1 << 0; + +/** Escape unicode as `uXXXX`, make the output ASCII only. */ +static const yyjson_write_flag YYJSON_WRITE_ESCAPE_UNICODE = 1 << 1; + +/** Escape '/' as '\/'. */ +static const yyjson_write_flag YYJSON_WRITE_ESCAPE_SLASHES = 1 << 2; + +/** Write inf and nan number as 'Infinity' and 'NaN' literal (non-standard). */ +static const yyjson_write_flag YYJSON_WRITE_ALLOW_INF_AND_NAN = 1 << 3; + +/** Write inf and nan number as null literal. + This flag will override `YYJSON_WRITE_ALLOW_INF_AND_NAN` flag. */ +static const yyjson_write_flag YYJSON_WRITE_INF_AND_NAN_AS_NULL = 1 << 4; + +/** Allow invalid unicode when encoding string values (non-standard). + Invalid characters in string value will be copied byte by byte. + If `YYJSON_WRITE_ESCAPE_UNICODE` flag is also set, invalid character will be + escaped as `U+FFFD` (replacement character). + This flag does not affect the performance of correctly encoded strings. */ +static const yyjson_write_flag YYJSON_WRITE_ALLOW_INVALID_UNICODE = 1 << 5; + +/** Write JSON pretty with 2 space indent. + This flag will override `YYJSON_WRITE_PRETTY` flag. */ +static const yyjson_write_flag YYJSON_WRITE_PRETTY_TWO_SPACES = 1 << 6; + +/** Adds a newline character `\n` at the end of the JSON. + This can be helpful for text editors or NDJSON. */ +static const yyjson_write_flag YYJSON_WRITE_NEWLINE_AT_END = 1 << 7; + + + +/** The highest 8 bits of `yyjson_write_flag` and real number value's `tag` + are reserved for controlling the output format of floating-point numbers. */ +#define YYJSON_WRITE_FP_FLAG_BITS 8 + +/** The highest 4 bits of flag are reserved for precision value. */ +#define YYJSON_WRITE_FP_PREC_BITS 4 + +/** Write floating-point number using fixed-point notation. + - This is similar to ECMAScript `Number.prototype.toFixed(prec)`, + but with trailing zeros removed. The `prec` ranges from 1 to 15. + - This will produce shorter output but may lose some precision. */ +#define YYJSON_WRITE_FP_TO_FIXED(prec) ((yyjson_write_flag)( \ + (uint32_t)((uint32_t)(prec)) << (32 - 4) )) + +/** Write floating-point numbers using single-precision (float). + - This casts `double` to `float` before serialization. + - This will produce shorter output, but may lose some precision. + - This flag is ignored if `YYJSON_WRITE_FP_TO_FIXED(prec)` is also used. */ +#define YYJSON_WRITE_FP_TO_FLOAT ((yyjson_write_flag)(1 << (32 - 5))) + + + +/** Result code for JSON writer */ +typedef uint32_t yyjson_write_code; + +/** Success, no error. */ +static const yyjson_write_code YYJSON_WRITE_SUCCESS = 0; + +/** Invalid parameter, such as NULL document. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_PARAMETER = 1; + +/** Memory allocation failure occurs. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_MEMORY_ALLOCATION = 2; + +/** Invalid value type in JSON document. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_VALUE_TYPE = 3; + +/** NaN or Infinity number occurs. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_NAN_OR_INF = 4; + +/** Failed to open a file. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_OPEN = 5; + +/** Failed to write a file. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_FILE_WRITE = 6; + +/** Invalid unicode in string. */ +static const yyjson_write_code YYJSON_WRITE_ERROR_INVALID_STRING = 7; + +/** Error information for JSON writer. */ +typedef struct yyjson_write_err { + /** Error code, see `yyjson_write_code` for all possible values. */ + yyjson_write_code code; + /** Error message, constant, no need to free (NULL if success). */ + const char *msg; +} yyjson_write_err; + + + +#if !defined(YYJSON_DISABLE_WRITER) || !YYJSON_DISABLE_WRITER + +/*============================================================================== + * MARK: - JSON Document Writer API + *============================================================================*/ + +/** + Write a document to JSON string with options. + + This function is thread-safe when: + The `alc` is thread-safe or NULL. + + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_write_opts(const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a document to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + This should be a null-terminated string using the system's native encoding. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_write_file(const char *path, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to file pointer with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this fp is NULL or invalid, the function will fail and return false. + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_write_fp(FILE *fp, + const yyjson_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to JSON string. + + This function is thread-safe. + + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_write(const yyjson_doc *doc, + yyjson_write_flag flg, + size_t *len) { + return yyjson_write_opts(doc, flg, NULL, len, NULL); +} + + + +/** + Write a document to JSON string with options. + + This function is thread-safe when: + 1. The `doc` is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param doc The mutable JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_mut_write_opts(const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a document to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `doc` is not modified by other threads. + 3. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + This should be a null-terminated string using the system's native encoding. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param doc The mutable JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_write_file(const char *path, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to file pointer with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this fp is NULL or invalid, the function will fail and return false. + @param doc The mutable JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_write_fp(FILE *fp, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a document to JSON string. + + This function is thread-safe when: + The `doc` is not modified by other threads. + + @param doc The JSON document. + If this doc is NULL or has no root, the function will fail and return false. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_mut_write(const yyjson_mut_doc *doc, + yyjson_write_flag flg, + size_t *len) { + return yyjson_mut_write_opts(doc, flg, NULL, len, NULL); +} + + + +/*============================================================================== + * MARK: - JSON Value Writer API + *============================================================================*/ + +/** + Write a value to JSON string with options. + + This function is thread-safe when: + The `alc` is thread-safe or NULL. + + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_val_write_opts(const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a value to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + This should be a null-terminated string using the system's native encoding. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_val_write_file(const char *path, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to file pointer with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this path is NULL or invalid, the function will fail and return false. + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_val_write_fp(FILE *fp, + const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to JSON string. + + This function is thread-safe. + + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_val_write(const yyjson_val *val, + yyjson_write_flag flg, + size_t *len) { + return yyjson_val_write_opts(val, flg, NULL, len, NULL); +} + +/** + Write a value to JSON string with options. + + This function is thread-safe when: + 1. The `val` is not modified by other threads. + 2. The `alc` is thread-safe or NULL. + + @param val The mutable JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free() or alc->free(). + */ +yyjson_api char *yyjson_mut_val_write_opts(const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + size_t *len, + yyjson_write_err *err); + +/** + Write a value to JSON file with options. + + This function is thread-safe when: + 1. The file is not accessed by other threads. + 2. The `val` is not modified by other threads. + 3. The `alc` is thread-safe or NULL. + + @param path The JSON file's path. + This should be a null-terminated string using the system's native encoding. + If this path is NULL or invalid, the function will fail and return false. + If this file is not empty, the content will be discarded. + @param val The mutable JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_val_write_file(const char *path, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to JSON file with options. + + @param fp The file pointer. + The data will be written to the current position of the file. + If this path is NULL or invalid, the function will fail and return false. + @param val The mutable JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param alc The memory allocator used by JSON writer. + Pass NULL to use the libc's default allocator. + @param err A pointer to receive error information. + Pass NULL if you don't need error information. + @return true if successful, false if an error occurs. + + @warning On 32-bit operating system, files larger than 2GB may fail to write. + */ +yyjson_api bool yyjson_mut_val_write_fp(FILE *fp, + const yyjson_mut_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc, + yyjson_write_err *err); + +/** + Write a value to JSON string. + + This function is thread-safe when: + The `val` is not modified by other threads. + + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param len A pointer to receive output length in bytes (not including the + null-terminator). Pass NULL if you don't need length information. + @return A new JSON string, or NULL if an error occurs. + This string is encoded as UTF-8 with a null-terminator. + When it's no longer needed, it should be freed with free(). + */ +yyjson_api_inline char *yyjson_mut_val_write(const yyjson_mut_val *val, + yyjson_write_flag flg, + size_t *len) { + return yyjson_mut_val_write_opts(val, flg, NULL, len, NULL); +} + +/** + Write a JSON number. + + @param val A JSON number value to be converted to a string. + If this parameter is invalid, the function will fail and return NULL. + @param buf A buffer to store the resulting null-terminated string. + If this parameter is NULL, the function will fail and return NULL. + For integer values, the buffer must be at least 21 bytes. + For floating-point values, the buffer must be at least 40 bytes. + @return On success, returns a pointer to the character after the last + written character. On failure, returns NULL. + @note + - This function is thread-safe and does not allocate memory + (when `YYJSON_DISABLE_FAST_FP_CONV` is not defined). + - This function will fail and return NULL only in the following cases: + 1) `val` or `buf` is NULL; + 2) `val` is not a number type; + 3) `val` is `inf` or `nan`, and non-standard JSON is explicitly disabled + via the `YYJSON_DISABLE_NON_STANDARD` flag. + */ +yyjson_api char *yyjson_write_number(const yyjson_val *val, char *buf); + +/** Same as `yyjson_write_number()`. */ +yyjson_api_inline char *yyjson_mut_write_number(const yyjson_mut_val *val, + char *buf) { + return yyjson_write_number((const yyjson_val *)val, buf); +} + +#endif /* YYJSON_DISABLE_WRITER */ + + + +/*============================================================================== + * MARK: - JSON Document API + *============================================================================*/ + +/** Returns the root value of this JSON document. + Returns NULL if `doc` is NULL. */ +yyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc); + +/** Returns read size of input JSON data. + Returns 0 if `doc` is NULL. + For example: the read size of `[1,2,3]` is 7 bytes. */ +yyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc); + +/** Returns total value count in this JSON document. + Returns 0 if `doc` is NULL. + For example: the value count of `[1,2,3]` is 4. */ +yyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc); + +/** Release the JSON document and free the memory. + After calling this function, the `doc` and all values from the `doc` are no + longer available. This function will do nothing if the `doc` is NULL. */ +yyjson_api_inline void yyjson_doc_free(yyjson_doc *doc); + + + +/*============================================================================== + * MARK: - JSON Value Type API + *============================================================================*/ + +/** Returns whether the JSON value is raw. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_raw(yyjson_val *val); + +/** Returns whether the JSON value is `null`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_null(yyjson_val *val); + +/** Returns whether the JSON value is `true`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_true(yyjson_val *val); + +/** Returns whether the JSON value is `false`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_false(yyjson_val *val); + +/** Returns whether the JSON value is bool (true/false). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_bool(yyjson_val *val); + +/** Returns whether the JSON value is unsigned integer (uint64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_uint(yyjson_val *val); + +/** Returns whether the JSON value is signed integer (int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_sint(yyjson_val *val); + +/** Returns whether the JSON value is integer (uint64_t/int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_int(yyjson_val *val); + +/** Returns whether the JSON value is real number (double). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_real(yyjson_val *val); + +/** Returns whether the JSON value is number (uint64_t/int64_t/double). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_num(yyjson_val *val); + +/** Returns whether the JSON value is string. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_str(yyjson_val *val); + +/** Returns whether the JSON value is array. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_arr(yyjson_val *val); + +/** Returns whether the JSON value is object. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_obj(yyjson_val *val); + +/** Returns whether the JSON value is container (array/object). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_is_ctn(yyjson_val *val); + + + +/*============================================================================== + * MARK: - JSON Value Content API + *============================================================================*/ + +/** Returns the JSON value's type. + Returns YYJSON_TYPE_NONE if `val` is NULL. */ +yyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val); + +/** Returns the JSON value's subtype. + Returns YYJSON_SUBTYPE_NONE if `val` is NULL. */ +yyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val); + +/** Returns the JSON value's tag. + Returns 0 if `val` is NULL. */ +yyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val); + +/** Returns the JSON value's type description. + The return value should be one of these strings: "raw", "null", "string", + "array", "object", "true", "false", "uint", "sint", "real", "unknown". */ +yyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val); + +/** Returns the content if the value is raw. + Returns NULL if `val` is NULL or type is not raw. */ +yyjson_api_inline const char *yyjson_get_raw(yyjson_val *val); + +/** Returns the content if the value is bool. + Returns false if `val` is NULL or type is not bool. */ +yyjson_api_inline bool yyjson_get_bool(yyjson_val *val); + +/** Returns the content and cast to uint64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val); + +/** Returns the content and cast to int64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val); + +/** Returns the content and cast to int. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int yyjson_get_int(yyjson_val *val); + +/** Returns the content if the value is real number, or 0.0 on error. + Returns 0.0 if `val` is NULL or type is not real(double). */ +yyjson_api_inline double yyjson_get_real(yyjson_val *val); + +/** Returns the content and typecast to `double` if the value is number. + Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */ +yyjson_api_inline double yyjson_get_num(yyjson_val *val); + +/** Returns the content if the value is string. + Returns NULL if `val` is NULL or type is not string. */ +yyjson_api_inline const char *yyjson_get_str(yyjson_val *val); + +/** Returns the content length (string length, array size, object size. + Returns 0 if `val` is NULL or type is not string/array/object. */ +yyjson_api_inline size_t yyjson_get_len(yyjson_val *val); + +/** Returns whether the JSON value is equals to a string. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str); + +/** Returns whether the JSON value is equals to a string. + The `str` should be a UTF-8 string, null-terminator is not required. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str, + size_t len); + +/** Returns whether two JSON values are equal (deep compare). + Returns false if input is NULL. + @note the result may be inaccurate if object has duplicate keys. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs); + +/** Set the value to raw. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_raw(yyjson_val *val, + const char *raw, size_t len); + +/** Set the value to null. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_null(yyjson_val *val); + +/** Set the value to bool. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num); + +/** Set the value to uint. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num); + +/** Set the value to sint. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num); + +/** Set the value to int. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num); + +/** Set the value to float. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_float(yyjson_val *val, float num); + +/** Set the value to double. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_double(yyjson_val *val, double num); + +/** Set the value to real. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num); + +/** Set the floating-point number's output format to fixed-point notation. + Returns false if input is NULL or `val` is not real type. + @see YYJSON_WRITE_FP_TO_FIXED flag. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_fp_to_fixed(yyjson_val *val, int prec); + +/** Set the floating-point number's output format to single-precision. + Returns false if input is NULL or `val` is not real type. + @see YYJSON_WRITE_FP_TO_FLOAT flag. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_fp_to_float(yyjson_val *val, bool flt); + +/** Set the value to string (null-terminated). + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str); + +/** Set the value to string (with length). + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, + const char *str, size_t len); + +/** Marks this string as not needing to be escaped during JSON writing. + This can be used to avoid the overhead of escaping if the string contains + only characters that do not require escaping. + Returns false if input is NULL or `val` is not string. + @see YYJSON_SUBTYPE_NOESC subtype. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_str_noesc(yyjson_val *val, bool noesc); + + + +/*============================================================================== + * MARK: - JSON Array API + *============================================================================*/ + +/** Returns the number of elements in this array. + Returns 0 if `arr` is NULL or type is not array. */ +yyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr); + +/** Returns the element at the specified position in this array. + Returns NULL if array is NULL/empty or the index is out of bounds. + @warning This function takes a linear search time if array is not flat. + For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat. */ +yyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx); + +/** Returns the first element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. */ +yyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr); + +/** Returns the last element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. + @warning This function takes a linear search time if array is not flat. + For example: `[1,{},3]` is flat, `[1,[2],3]` is not flat.*/ +yyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr); + + + +/*============================================================================== + * MARK: - JSON Array Iterator API + *============================================================================*/ + +/** + A JSON array iterator. + + @b Example + @code + yyjson_val *val; + yyjson_arr_iter iter = yyjson_arr_iter_with(arr); + while ((val = yyjson_arr_iter_next(&iter))) { + your_func(val); + } + @endcode + */ +typedef struct yyjson_arr_iter { + size_t idx; /**< next value's index */ + size_t max; /**< maximum index (arr.size) */ + yyjson_val *cur; /**< next value */ +} yyjson_arr_iter; + +/** + Initialize an iterator for this array. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr, + yyjson_arr_iter *iter); + +/** + Create an iterator with an array , same as `yyjson_arr_iter_init()`. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, an empty iterator will returned. + @return A new iterator for the array. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter); + +/** + Returns the next element in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter); + +/** + Macro for iterating over an array. + It works like iterator, but with a more intuitive API. + + @b Example + @code + size_t idx, max; + yyjson_val *val; + yyjson_arr_foreach(arr, idx, max, val) { + your_func(idx, val); + } + @endcode + */ +#define yyjson_arr_foreach(arr, idx, max, val) \ + for ((idx) = 0, \ + (max) = yyjson_arr_size(arr), \ + (val) = yyjson_arr_get_first(arr); \ + (idx) < (max); \ + (idx)++, \ + (val) = unsafe_yyjson_get_next(val)) + + + +/*============================================================================== + * MARK: - JSON Object API + *============================================================================*/ + +/** Returns the number of key-value pairs in this object. + Returns 0 if `obj` is NULL or type is not object. */ +yyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a null-terminated UTF-8 string. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj, const char *key); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a UTF-8 string, null-terminator is not required. + The `key_len` should be the length of the key, in bytes. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj, const char *key, + size_t key_len); + + + +/*============================================================================== + * MARK: - JSON Object Iterator API + *============================================================================*/ + +/** + A JSON object iterator. + + @b Example + @code + yyjson_val *key, *val; + yyjson_obj_iter iter = yyjson_obj_iter_with(obj); + while ((key = yyjson_obj_iter_next(&iter))) { + val = yyjson_obj_iter_get_val(key); + your_func(key, val); + } + @endcode + + If the ordering of the keys is known at compile-time, you can use this method + to speed up value lookups: + @code + // {"k1":1, "k2": 3, "k3": 3} + yyjson_val *key, *val; + yyjson_obj_iter iter = yyjson_obj_iter_with(obj); + yyjson_val *v1 = yyjson_obj_iter_get(&iter, "k1"); + yyjson_val *v3 = yyjson_obj_iter_get(&iter, "k3"); + @endcode + @see yyjson_obj_iter_get() and yyjson_obj_iter_getn() + */ +typedef struct yyjson_obj_iter { + size_t idx; /**< next key's index */ + size_t max; /**< maximum key index (obj.size) */ + yyjson_val *cur; /**< next key */ + yyjson_val *obj; /**< the object being iterated */ +} yyjson_obj_iter; + +/** + Initialize an iterator for this object. + + @param obj The object to be iterated over. + If this parameter is NULL or not an object, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj, + yyjson_obj_iter *iter); + +/** + Create an iterator with an object, same as `yyjson_obj_iter_init()`. + + @param obj The object to be iterated over. + If this parameter is NULL or not an object, an empty iterator will returned. + @return A new iterator for the object. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter); + +/** + Returns the next key in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter); + +/** + Returns the value for key inside the iteration. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_obj_get()`, but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string with null-terminator. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter, + const char *key); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_obj_getn()`, but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string, null-terminator is not required. + @param key_len The the length of `key`, in bytes. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter, + const char *key, + size_t key_len); + +/** + Macro for iterating over an object. + It works like iterator, but with a more intuitive API. + + @b Example + @code + size_t idx, max; + yyjson_val *key, *val; + yyjson_obj_foreach(obj, idx, max, key, val) { + your_func(key, val); + } + @endcode + */ +#define yyjson_obj_foreach(obj, idx, max, key, val) \ + for ((idx) = 0, \ + (max) = yyjson_obj_size(obj), \ + (key) = (obj) ? unsafe_yyjson_get_first(obj) : NULL, \ + (val) = (key) + 1; \ + (idx) < (max); \ + (idx)++, \ + (key) = unsafe_yyjson_get_next(val), \ + (val) = (key) + 1) + + + +/*============================================================================== + * MARK: - Mutable JSON Document API + *============================================================================*/ + +/** Returns the root value of this JSON document. + Returns NULL if `doc` is NULL. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc); + +/** Sets the root value of this JSON document. + Pass NULL to clear root value of the document. */ +yyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc, + yyjson_mut_val *root); + +/** + Set the string pool size for a mutable document. + This function does not allocate memory immediately, but uses the size when + the next memory allocation is needed. + + If the caller knows the approximate bytes of strings that the document needs to + store (e.g. copy string with `yyjson_mut_strcpy` function), setting a larger + size can avoid multiple memory allocations and improve performance. + + @param doc The mutable document. + @param len The desired string pool size in bytes (total string length). + @return true if successful, false if size is 0 or overflow. + */ +yyjson_api bool yyjson_mut_doc_set_str_pool_size(yyjson_mut_doc *doc, + size_t len); + +/** + Set the value pool size for a mutable document. + This function does not allocate memory immediately, but uses the size when + the next memory allocation is needed. + + If the caller knows the approximate number of values that the document needs to + store (e.g. create new value with `yyjson_mut_xxx` functions), setting a larger + size can avoid multiple memory allocations and improve performance. + + @param doc The mutable document. + @param count The desired value pool size (number of `yyjson_mut_val`). + @return true if successful, false if size is 0 or overflow. + */ +yyjson_api bool yyjson_mut_doc_set_val_pool_size(yyjson_mut_doc *doc, + size_t count); + +/** Release the JSON document and free the memory. + After calling this function, the `doc` and all values from the `doc` are no + longer available. This function will do nothing if the `doc` is NULL. */ +yyjson_api void yyjson_mut_doc_free(yyjson_mut_doc *doc); + +/** Creates and returns a new mutable JSON document, returns NULL on error. + If allocator is NULL, the default allocator will be used. */ +yyjson_api yyjson_mut_doc *yyjson_mut_doc_new(const yyjson_alc *alc); + +/** Copies and returns a new mutable document from input, returns NULL on error. + This makes a `deep-copy` on the immutable document. + If allocator is NULL, the default allocator will be used. + @note `imut_doc` -> `mut_doc`. */ +yyjson_api yyjson_mut_doc *yyjson_doc_mut_copy(yyjson_doc *doc, + const yyjson_alc *alc); + +/** Copies and returns a new mutable document from input, returns NULL on error. + This makes a `deep-copy` on the mutable document. + If allocator is NULL, the default allocator will be used. + @note `mut_doc` -> `mut_doc`. */ +yyjson_api yyjson_mut_doc *yyjson_mut_doc_mut_copy(yyjson_mut_doc *doc, + const yyjson_alc *alc); + +/** Copies and returns a new mutable value from input, returns NULL on error. + This makes a `deep-copy` on the immutable value. + The memory was managed by mutable document. + @note `imut_val` -> `mut_val`. */ +yyjson_api yyjson_mut_val *yyjson_val_mut_copy(yyjson_mut_doc *doc, + yyjson_val *val); + +/** Copies and returns a new mutable value from input, returns NULL on error. + This makes a `deep-copy` on the mutable value. + The memory was managed by mutable document. + @note `mut_val` -> `mut_val`. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api yyjson_mut_val *yyjson_mut_val_mut_copy(yyjson_mut_doc *doc, + yyjson_mut_val *val); + +/** Copies and returns a new immutable document from input, + returns NULL on error. This makes a `deep-copy` on the mutable document. + The returned document should be freed with `yyjson_doc_free()`. + @note `mut_doc` -> `imut_doc`. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api yyjson_doc *yyjson_mut_doc_imut_copy(yyjson_mut_doc *doc, + const yyjson_alc *alc); + +/** Copies and returns a new immutable document from input, + returns NULL on error. This makes a `deep-copy` on the mutable value. + The returned document should be freed with `yyjson_doc_free()`. + @note `mut_val` -> `imut_doc`. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api yyjson_doc *yyjson_mut_val_imut_copy(yyjson_mut_val *val, + const yyjson_alc *alc); + + + +/*============================================================================== + * MARK: - Mutable JSON Value Type API + *============================================================================*/ + +/** Returns whether the JSON value is raw. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val); + +/** Returns whether the JSON value is `null`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val); + +/** Returns whether the JSON value is `true`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val); + +/** Returns whether the JSON value is `false`. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val); + +/** Returns whether the JSON value is bool (true/false). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val); + +/** Returns whether the JSON value is unsigned integer (uint64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val); + +/** Returns whether the JSON value is signed integer (int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val); + +/** Returns whether the JSON value is integer (uint64_t/int64_t). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val); + +/** Returns whether the JSON value is real number (double). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val); + +/** Returns whether the JSON value is number (uint/sint/real). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val); + +/** Returns whether the JSON value is string. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val); + +/** Returns whether the JSON value is array. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val); + +/** Returns whether the JSON value is object. + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val); + +/** Returns whether the JSON value is container (array/object). + Returns false if `val` is NULL. */ +yyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val); + + + +/*============================================================================== + * MARK: - Mutable JSON Value Content API + *============================================================================*/ + +/** Returns the JSON value's type. + Returns `YYJSON_TYPE_NONE` if `val` is NULL. */ +yyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val); + +/** Returns the JSON value's subtype. + Returns `YYJSON_SUBTYPE_NONE` if `val` is NULL. */ +yyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val); + +/** Returns the JSON value's tag. + Returns 0 if `val` is NULL. */ +yyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val); + +/** Returns the JSON value's type description. + The return value should be one of these strings: "raw", "null", "string", + "array", "object", "true", "false", "uint", "sint", "real", "unknown". */ +yyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val); + +/** Returns the content if the value is raw. + Returns NULL if `val` is NULL or type is not raw. */ +yyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val); + +/** Returns the content if the value is bool. + Returns NULL if `val` is NULL or type is not bool. */ +yyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val); + +/** Returns the content and cast to uint64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val); + +/** Returns the content and cast to int64_t. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val); + +/** Returns the content and cast to int. + Returns 0 if `val` is NULL or type is not integer(sint/uint). */ +yyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val); + +/** Returns the content if the value is real number. + Returns 0.0 if `val` is NULL or type is not real(double). */ +yyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val); + +/** Returns the content and typecast to `double` if the value is number. + Returns 0.0 if `val` is NULL or type is not number(uint/sint/real). */ +yyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val); + +/** Returns the content if the value is string. + Returns NULL if `val` is NULL or type is not string. */ +yyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val); + +/** Returns the content length (string length, array size, object size. + Returns 0 if `val` is NULL or type is not string/array/object. */ +yyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val); + +/** Returns whether the JSON value is equals to a string. + The `str` should be a null-terminated UTF-8 string. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val, + const char *str); + +/** Returns whether the JSON value is equals to a string. + The `str` should be a UTF-8 string, null-terminator is not required. + Returns false if input is NULL or type is not string. */ +yyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val, + const char *str, size_t len); + +/** Returns whether two JSON values are equal (deep compare). + Returns false if input is NULL. + @note the result may be inaccurate if object has duplicate keys. + @warning This function is recursive and may cause a stack overflow + if the object level is too deep. */ +yyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs, + yyjson_mut_val *rhs); + +/** Set the value to raw. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val, + const char *raw, size_t len); + +/** Set the value to null. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val); + +/** Set the value to bool. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num); + +/** Set the value to uint. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num); + +/** Set the value to sint. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num); + +/** Set the value to int. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num); + +/** Set the value to float. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_float(yyjson_mut_val *val, float num); + +/** Set the value to double. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_double(yyjson_mut_val *val, double num); + +/** Set the value to real. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num); + +/** Set the floating-point number's output format to fixed-point notation. + Returns false if input is NULL or `val` is not real type. + @see YYJSON_WRITE_FP_TO_FIXED flag. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_mut_set_fp_to_fixed(yyjson_mut_val *val, + int prec); + +/** Set the floating-point number's output format to single-precision. + Returns false if input is NULL or `val` is not real type. + @see YYJSON_WRITE_FP_TO_FLOAT flag. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_mut_set_fp_to_float(yyjson_mut_val *val, + bool flt); + +/** Set the value to string (null-terminated). + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, const char *str); + +/** Set the value to string (with length). + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, + const char *str, size_t len); + +/** Marks this string as not needing to be escaped during JSON writing. + This can be used to avoid the overhead of escaping if the string contains + only characters that do not require escaping. + Returns false if input is NULL or `val` is not string. + @see YYJSON_SUBTYPE_NOESC subtype. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_mut_set_str_noesc(yyjson_mut_val *val, + bool noesc); + +/** Set the value to array. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val); + +/** Set the value to array. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val); + + + +/*============================================================================== + * MARK: - Mutable JSON Value Creation API + *============================================================================*/ + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc, + const char *str, + size_t len); + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a raw value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc, + const char *str, + size_t len); + +/** Creates and returns a null value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc); + +/** Creates and returns a true value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc); + +/** Creates and returns a false value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc); + +/** Creates and returns a bool value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, + bool val); + +/** Creates and returns an unsigned integer value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, + uint64_t num); + +/** Creates and returns a signed integer value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, + int64_t num); + +/** Creates and returns a signed integer value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, + int64_t num); + +/** Creates and returns a float number value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_float(yyjson_mut_doc *doc, + float num); + +/** Creates and returns a double number value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_double(yyjson_mut_doc *doc, + double num); + +/** Creates and returns a real number value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, + double num); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc, + const char *str, + size_t len); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a null-terminated UTF-8 string. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc, + const char *str); + +/** Creates and returns a string value, returns NULL on error. + The `str` should be a UTF-8 string, null-terminator is not required. + The input string is copied and held by the document. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc, + const char *str, + size_t len); + + + +/*============================================================================== + * MARK: - Mutable JSON Array API + *============================================================================*/ + +/** Returns the number of elements in this array. + Returns 0 if `arr` is NULL or type is not array. */ +yyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr); + +/** Returns the element at the specified position in this array. + Returns NULL if array is NULL/empty or the index is out of bounds. + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr, + size_t idx); + +/** Returns the first element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first(yyjson_mut_val *arr); + +/** Returns the last element of this array. + Returns NULL if `arr` is NULL/empty or type is not array. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last(yyjson_mut_val *arr); + + + +/*============================================================================== + * MARK: - Mutable JSON Array Iterator API + *============================================================================*/ + +/** + A mutable JSON array iterator. + + @warning You should not modify the array while iterating over it, but you can + use `yyjson_mut_arr_iter_remove()` to remove current value. + + @b Example + @code + yyjson_mut_val *val; + yyjson_mut_arr_iter iter = yyjson_mut_arr_iter_with(arr); + while ((val = yyjson_mut_arr_iter_next(&iter))) { + your_func(val); + if (your_val_is_unused(val)) { + yyjson_mut_arr_iter_remove(&iter); + } + } + @endcode + */ +typedef struct yyjson_mut_arr_iter { + size_t idx; /**< next value's index */ + size_t max; /**< maximum index (arr.size) */ + yyjson_mut_val *cur; /**< current value */ + yyjson_mut_val *pre; /**< previous value */ + yyjson_mut_val *arr; /**< the array being iterated */ +} yyjson_mut_arr_iter; + +/** + Initialize an iterator for this array. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr, + yyjson_mut_arr_iter *iter); + +/** + Create an iterator with an array , same as `yyjson_mut_arr_iter_init()`. + + @param arr The array to be iterated over. + If this parameter is NULL or not an array, an empty iterator will returned. + @return A new iterator for the array. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with( + yyjson_mut_val *arr); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_mut_arr_iter_has_next( + yyjson_mut_arr_iter *iter); + +/** + Returns the next element in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next( + yyjson_mut_arr_iter *iter); + +/** + Removes and returns current element in the iteration. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove( + yyjson_mut_arr_iter *iter); + +/** + Macro for iterating over an array. + It works like iterator, but with a more intuitive API. + + @warning You should not modify the array while iterating over it. + + @b Example + @code + size_t idx, max; + yyjson_mut_val *val; + yyjson_mut_arr_foreach(arr, idx, max, val) { + your_func(idx, val); + } + @endcode + */ +#define yyjson_mut_arr_foreach(arr, idx, max, val) \ + for ((idx) = 0, \ + (max) = yyjson_mut_arr_size(arr), \ + (val) = yyjson_mut_arr_get_first(arr); \ + (idx) < (max); \ + (idx)++, \ + (val) = (val)->next) + + + +/*============================================================================== + * MARK: - Mutable JSON Array Creation API + *============================================================================*/ + +/** + Creates and returns an empty mutable array. + @param doc A mutable document, used for memory allocation only. + @return The new array. NULL if input is NULL or memory allocation failed. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc); + +/** + Creates and returns a new mutable array with the given boolean values. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of boolean values. + @param count The value count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const bool vals[3] = { true, false, true }; + yyjson_mut_val *arr = yyjson_mut_arr_with_bool(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool( + yyjson_mut_doc *doc, const bool *vals, size_t count); + +/** + Creates and returns a new mutable array with the given sint numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of sint numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const int64_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint( + yyjson_mut_doc *doc, const int64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const uint64_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given real numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of real numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const double vals[3] = { 0.1, 0.2, 0.3 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_real(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real( + yyjson_mut_doc *doc, const double *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int8 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int8 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const int8_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint8(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8( + yyjson_mut_doc *doc, const int8_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int16 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int16 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const int16_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint16(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16( + yyjson_mut_doc *doc, const int16_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int32 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int32 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const int32_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint32(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32( + yyjson_mut_doc *doc, const int32_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given int64 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of int64 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const int64_t vals[3] = { -1, 0, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_sint64(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64( + yyjson_mut_doc *doc, const int64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint8 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint8 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const uint8_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint8(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8( + yyjson_mut_doc *doc, const uint8_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint16 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint16 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const uint16_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint16(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16( + yyjson_mut_doc *doc, const uint16_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint32 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint32 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const uint32_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint32(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32( + yyjson_mut_doc *doc, const uint32_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given uint64 numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of uint64 numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const uint64_t vals[3] = { 0, 1, 0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_uint64(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count); + +/** + Creates and returns a new mutable array with the given float numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of float numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const float vals[3] = { -1.0f, 0.0f, 1.0f }; + yyjson_mut_val *arr = yyjson_mut_arr_with_float(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float( + yyjson_mut_doc *doc, const float *vals, size_t count); + +/** + Creates and returns a new mutable array with the given double numbers. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of double numbers. + @param count The number count. If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const double vals[3] = { -1.0, 0.0, 1.0 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_double(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double( + yyjson_mut_doc *doc, const double *vals, size_t count); + +/** + Creates and returns a new mutable array with the given strings, these strings + will not be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 null-terminator strings. + If this array contains NULL, the function will fail and return NULL. + @param count The number of values in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @warning The input strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. If these strings will be + modified, you should use `yyjson_mut_arr_with_strcpy()` instead. + + @b Example + @code + const char *vals[3] = { "a", "b", "c" }; + yyjson_mut_val *arr = yyjson_mut_arr_with_str(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str( + yyjson_mut_doc *doc, const char **vals, size_t count); + +/** + Creates and returns a new mutable array with the given strings and string + lengths, these strings will not be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 strings, null-terminator is not required. + If this array contains NULL, the function will fail and return NULL. + @param lens A C array of string lengths, in bytes. + @param count The number of strings in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @warning The input strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. If these strings will be + modified, you should use `yyjson_mut_arr_with_strncpy()` instead. + + @b Example + @code + const char *vals[3] = { "a", "bb", "c" }; + const size_t lens[3] = { 1, 2, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count); + +/** + Creates and returns a new mutable array with the given strings, these strings + will be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 null-terminator strings. + If this array contains NULL, the function will fail and return NULL. + @param count The number of values in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const char *vals[3] = { "a", "b", "c" }; + yyjson_mut_val *arr = yyjson_mut_arr_with_strcpy(doc, vals, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy( + yyjson_mut_doc *doc, const char **vals, size_t count); + +/** + Creates and returns a new mutable array with the given strings and string + lengths, these strings will be copied. + + @param doc A mutable document, used for memory allocation only. + If this parameter is NULL, the function will fail and return NULL. + @param vals A C array of UTF-8 strings, null-terminator is not required. + If this array contains NULL, the function will fail and return NULL. + @param lens A C array of string lengths, in bytes. + @param count The number of strings in `vals`. + If this value is 0, an empty array will return. + @return The new array. NULL if input is invalid or memory allocation failed. + + @b Example + @code + const char *vals[3] = { "a", "bb", "c" }; + const size_t lens[3] = { 1, 2, 1 }; + yyjson_mut_val *arr = yyjson_mut_arr_with_strn(doc, vals, lens, 3); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count); + + + +/*============================================================================== + * MARK: - Mutable JSON Array Modification API + *============================================================================*/ + +/** + Inserts a value into an array at a given index. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @param idx The index to which to insert the new value. + Returns false if the index is out of range. + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr, + yyjson_mut_val *val, size_t idx); + +/** + Inserts a value at the end of the array. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr, + yyjson_mut_val *val); + +/** + Inserts a value at the head of the array. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr, + yyjson_mut_val *val); + +/** + Replaces a value at index and returns old value. + @param arr The array to which the value is to be replaced. + Returns false if it is NULL or not an array. + @param idx The index to which to replace the value. + Returns false if the index is out of range. + @param val The new value to replace. Returns false if it is NULL. + @return Old value, or NULL on error. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr, + size_t idx, + yyjson_mut_val *val); + +/** + Removes and returns a value at index. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @param idx The index from which to remove the value. + Returns false if the index is out of range. + @return Old value, or NULL on error. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr, + size_t idx); + +/** + Removes and returns the first value in this array. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @return The first value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first( + yyjson_mut_val *arr); + +/** + Removes and returns the last value in this array. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @return The last value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last( + yyjson_mut_val *arr); + +/** + Removes all values within a specified range in the array. + @param arr The array from which the value is to be removed. + Returns false if it is NULL or not an array. + @param idx The start index of the range (0 is the first). + @param len The number of items in the range (can be 0). + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr, + size_t idx, size_t len); + +/** + Removes all values in this array. + @param arr The array from which all of the values are to be removed. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr); + +/** + Rotates values in this array for the given number of times. + For example: `[1,2,3,4,5]` rotate 2 is `[3,4,5,1,2]`. + @param arr The array to be rotated. + @param idx Index (or times) to rotate. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr, + size_t idx); + + + +/*============================================================================== + * MARK: - Mutable JSON Array Modification Convenience API + *============================================================================*/ + +/** + Adds a value at the end of the array. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The value to be inserted. Returns false if it is NULL. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr, + yyjson_mut_val *val); + +/** + Adds a `null` value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Adds a `true` value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Adds a `false` value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Adds a bool value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param val The bool value to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + bool val); + +/** + Adds an unsigned integer value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + uint64_t num); + +/** + Adds a signed integer value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num); + +/** + Adds an integer value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num); + +/** + Adds a float value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_float(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + float num); + +/** + Adds a double value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_double(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num); + +/** + Adds a double value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num); + +/** + Adds a string value at the end of the array (no copy). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A null-terminated UTF-8 string. + @return Whether successful. + @warning The input string is not copied, you should keep this string unmodified + for the lifetime of this JSON document. + */ +yyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str); + +/** + Adds a string value at the end of the array (no copy). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A UTF-8 string, null-terminator is not required. + @param len The length of the string, in bytes. + @return Whether successful. + @warning The input string is not copied, you should keep this string unmodified + for the lifetime of this JSON document. + */ +yyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, + size_t len); + +/** + Adds a string value at the end of the array (copied). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A null-terminated UTF-8 string. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str); + +/** + Adds a string value at the end of the array (copied). + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param str A UTF-8 string, null-terminator is not required. + @param len The length of the string, in bytes. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, + size_t len); + +/** + Creates and adds a new array at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return The new array, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + +/** + Creates and adds a new object at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @return The new object, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *arr); + + + +/*============================================================================== + * MARK: - Mutable JSON Object API + *============================================================================*/ + +/** Returns the number of key-value pairs in this object. + Returns 0 if `obj` is NULL or type is not object. */ +yyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a null-terminated UTF-8 string. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj, + const char *key); + +/** Returns the value to which the specified key is mapped. + Returns NULL if this object contains no mapping for the key. + Returns NULL if `obj/key` is NULL, or type is not object. + + The `key` should be a UTF-8 string, null-terminator is not required. + The `key_len` should be the length of the key, in bytes. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj, + const char *key, + size_t key_len); + + + +/*============================================================================== + * MARK: - Mutable JSON Object Iterator API + *============================================================================*/ + +/** + A mutable JSON object iterator. + + @warning You should not modify the object while iterating over it, but you can + use `yyjson_mut_obj_iter_remove()` to remove current value. + + @b Example + @code + yyjson_mut_val *key, *val; + yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj); + while ((key = yyjson_mut_obj_iter_next(&iter))) { + val = yyjson_mut_obj_iter_get_val(key); + your_func(key, val); + if (your_val_is_unused(key, val)) { + yyjson_mut_obj_iter_remove(&iter); + } + } + @endcode + + If the ordering of the keys is known at compile-time, you can use this method + to speed up value lookups: + @code + // {"k1":1, "k2": 3, "k3": 3} + yyjson_mut_val *key, *val; + yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(obj); + yyjson_mut_val *v1 = yyjson_mut_obj_iter_get(&iter, "k1"); + yyjson_mut_val *v3 = yyjson_mut_obj_iter_get(&iter, "k3"); + @endcode + @see `yyjson_mut_obj_iter_get()` and `yyjson_mut_obj_iter_getn()` + */ +typedef struct yyjson_mut_obj_iter { + size_t idx; /**< next key's index */ + size_t max; /**< maximum key index (obj.size) */ + yyjson_mut_val *cur; /**< current key */ + yyjson_mut_val *pre; /**< previous key */ + yyjson_mut_val *obj; /**< the object being iterated */ +} yyjson_mut_obj_iter; + +/** + Initialize an iterator for this object. + + @param obj The object to be iterated over. + If this parameter is NULL or not an array, `iter` will be set to empty. + @param iter The iterator to be initialized. + If this parameter is NULL, the function will fail and return false. + @return true if the `iter` has been successfully initialized. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj, + yyjson_mut_obj_iter *iter); + +/** + Create an iterator with an object, same as `yyjson_obj_iter_init()`. + + @param obj The object to be iterated over. + If this parameter is NULL or not an object, an empty iterator will returned. + @return A new iterator for the object. + + @note The iterator does not need to be destroyed. + */ +yyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with( + yyjson_mut_val *obj); + +/** + Returns whether the iteration has more elements. + If `iter` is NULL, this function will return false. + */ +yyjson_api_inline bool yyjson_mut_obj_iter_has_next( + yyjson_mut_obj_iter *iter); + +/** + Returns the next key in the iteration, or NULL on end. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next( + yyjson_mut_obj_iter *iter); + +/** + Returns the value for key inside the iteration. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val( + yyjson_mut_val *key); + +/** + Removes current key-value pair in the iteration, returns the removed value. + If `iter` is NULL, this function will return NULL. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove( + yyjson_mut_obj_iter *iter); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_mut_obj_get()`, but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string with null-terminator. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get( + yyjson_mut_obj_iter *iter, const char *key); + +/** + Iterates to a specified key and returns the value. + + This function does the same thing as `yyjson_mut_obj_getn()` but is much faster + if the ordering of the keys is known at compile-time and you are using the same + order to look up the values. If the key exists in this object, then the + iterator will stop at the next key, otherwise the iterator will not change and + NULL is returned. + + @param iter The object iterator, should not be NULL. + @param key The key, should be a UTF-8 string, null-terminator is not required. + @param key_len The the length of `key`, in bytes. + @return The value to which the specified key is mapped. + NULL if this object contains no mapping for the key or input is invalid. + + @warning This function takes a linear search time if the key is not nearby. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn( + yyjson_mut_obj_iter *iter, const char *key, size_t key_len); + +/** + Macro for iterating over an object. + It works like iterator, but with a more intuitive API. + + @warning You should not modify the object while iterating over it. + + @b Example + @code + size_t idx, max; + yyjson_mut_val *key, *val; + yyjson_mut_obj_foreach(obj, idx, max, key, val) { + your_func(key, val); + } + @endcode + */ +#define yyjson_mut_obj_foreach(obj, idx, max, key, val) \ + for ((idx) = 0, \ + (max) = yyjson_mut_obj_size(obj), \ + (key) = (max) ? ((yyjson_mut_val *)(obj)->uni.ptr)->next->next : NULL, \ + (val) = (key) ? (key)->next : NULL; \ + (idx) < (max); \ + (idx)++, \ + (key) = (val)->next, \ + (val) = (key)->next) + + + +/*============================================================================== + * MARK: - Mutable JSON Object Creation API + *============================================================================*/ + +/** Creates and returns a mutable object, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc); + +/** + Creates and returns a mutable object with keys and values, returns NULL on + error. The keys and values are not copied. The strings should be a + null-terminated UTF-8 string. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. + + @b Example + @code + const char *keys[2] = { "id", "name" }; + const char *vals[2] = { "01", "Harry" }; + yyjson_mut_val *obj = yyjson_mut_obj_with_str(doc, keys, vals, 2); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc, + const char **keys, + const char **vals, + size_t count); + +/** + Creates and returns a mutable object with key-value pairs and pair count, + returns NULL on error. The keys and values are not copied. The strings should + be a null-terminated UTF-8 string. + + @warning The input string is not copied, you should keep this string + unmodified for the lifetime of this JSON document. + + @b Example + @code + const char *kv_pairs[4] = { "id", "01", "name", "Harry" }; + yyjson_mut_val *obj = yyjson_mut_obj_with_kv(doc, kv_pairs, 2); + @endcode + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc, + const char **kv_pairs, + size_t pair_count); + + + +/*============================================================================== + * MARK: - Mutable JSON Object Modification API + *============================================================================*/ + +/** + Adds a key-value pair at the end of the object. + This function allows duplicated key in one object. + @param obj The object to which the new key-value pair is to be added. + @param key The key, should be a string which is created by `yyjson_mut_str()`, + `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. + @param val The value to add to the object. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val); +/** + Sets a key-value pair at the end of the object. + This function may remove all key-value pairs for the given key before add. + @param obj The object to which the new key-value pair is to be added. + @param key The key, should be a string which is created by `yyjson_mut_str()`, + `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. + @param val The value to add to the object. If this value is null, the behavior + is same as `yyjson_mut_obj_remove()`. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val); + +/** + Inserts a key-value pair to the object at the given position. + This function allows duplicated key in one object. + @param obj The object to which the new key-value pair is to be added. + @param key The key, should be a string which is created by `yyjson_mut_str()`, + `yyjson_mut_strn()`, `yyjson_mut_strcpy()` or `yyjson_mut_strncpy()`. + @param val The value to add to the object. + @param idx The index to which to insert the new pair. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val, + size_t idx); + +/** + Removes all key-value pair from the object with given key. + @param obj The object from which the key-value pair is to be removed. + @param key The key, should be a string value. + @return The first matched value, or NULL if no matched value. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj, + yyjson_mut_val *key); + +/** + Removes all key-value pair from the object with given key. + @param obj The object from which the key-value pair is to be removed. + @param key The key, should be a UTF-8 string with null-terminator. + @return The first matched value, or NULL if no matched value. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key( + yyjson_mut_val *obj, const char *key); + +/** + Removes all key-value pair from the object with given key. + @param obj The object from which the key-value pair is to be removed. + @param key The key, should be a UTF-8 string, null-terminator is not required. + @param key_len The length of the key. + @return The first matched value, or NULL if no matched value. + @warning This function takes a linear search time. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn( + yyjson_mut_val *obj, const char *key, size_t key_len); + +/** + Removes all key-value pairs in this object. + @param obj The object from which all of the values are to be removed. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj); + +/** + Replaces value from the object with given key. + If the key is not exist, or the value is NULL, it will fail. + @param obj The object to which the value is to be replaced. + @param key The key, should be a string value. + @param val The value to replace into the object. + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val); + +/** + Rotates key-value pairs in the object for the given number of times. + For example: `{"a":1,"b":2,"c":3,"d":4}` rotate 1 is + `{"b":2,"c":3,"d":4,"a":1}`. + @param obj The object to be rotated. + @param idx Index (or times) to rotate. + @return Whether successful. + @warning This function takes a linear search time. + */ +yyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj, + size_t idx); + + + +/*============================================================================== + * MARK: - Mutable JSON Object Modification Convenience API + *============================================================================*/ + +/** Adds a `null` value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a `true` value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a `false` value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a bool value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, bool val); + +/** Adds an unsigned integer value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, uint64_t val); + +/** Adds a signed integer value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, int64_t val); + +/** Adds an int value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, int64_t val); + +/** Adds a float value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_float(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, float val); + +/** Adds a double value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_double(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, double val); + +/** Adds a real value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, double val); + +/** Adds a string value at the end of the object. + The `key` and `val` should be null-terminated UTF-8 strings. + This function allows duplicated key in one object. + + @warning The key/value strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, const char *val); + +/** Adds a string value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + The `val` should be a UTF-8 string, null-terminator is not required. + The `len` should be the length of the `val`, in bytes. + This function allows duplicated key in one object. + + @warning The key/value strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *val, size_t len); + +/** Adds a string value at the end of the object. + The `key` and `val` should be null-terminated UTF-8 strings. + The value string is copied. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *val); + +/** Adds a string value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + The `val` should be a UTF-8 string, null-terminator is not required. + The `len` should be the length of the `val`, in bytes. + This function allows duplicated key in one object. + + @warning The key strings are not copied, you should keep these strings + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *val, size_t len); + +/** + Creates and adds a new array to the target object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep these strings + unmodified for the lifetime of this JSON document. + @return The new array, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** + Creates and adds a new object to the target object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep these strings + unmodified for the lifetime of this JSON document. + @return The new object, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key); + +/** Adds a JSON value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + yyjson_mut_val *val); + +/** Removes all key-value pairs for the given key. + Returns the first value to which the specified key is mapped or NULL if this + object contains no mapping for the key. + The `key` should be a null-terminated UTF-8 string. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str( + yyjson_mut_val *obj, const char *key); + +/** Removes all key-value pairs for the given key. + Returns the first value to which the specified key is mapped or NULL if this + object contains no mapping for the key. + The `key` should be a UTF-8 string, null-terminator is not required. + The `len` should be the length of the key, in bytes. + + @warning This function takes a linear search time. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn( + yyjson_mut_val *obj, const char *key, size_t len); + +/** Replaces all matching keys with the new key. + Returns true if at least one key was renamed. + The `key` and `new_key` should be a null-terminated UTF-8 string. + The `new_key` is copied and held by doc. + + @warning This function takes a linear search time. + If `new_key` already exists, it will cause duplicate keys. + */ +yyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *new_key); + +/** Replaces all matching keys with the new key. + Returns true if at least one key was renamed. + The `key` and `new_key` should be a UTF-8 string, + null-terminator is not required. The `new_key` is copied and held by doc. + + @warning This function takes a linear search time. + If `new_key` already exists, it will cause duplicate keys. + */ +yyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + size_t len, + const char *new_key, + size_t new_len); + + + +#if !defined(YYJSON_DISABLE_UTILS) || !YYJSON_DISABLE_UTILS + +/*============================================================================== + * MARK: - JSON Pointer API (RFC 6901) + * https://tools.ietf.org/html/rfc6901 + *============================================================================*/ + +/** JSON Pointer error code. */ +typedef uint32_t yyjson_ptr_code; + +/** No JSON pointer error. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_NONE = 0; + +/** Invalid input parameter, such as NULL input. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_PARAMETER = 1; + +/** JSON pointer syntax error, such as invalid escape, token no prefix. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_SYNTAX = 2; + +/** JSON pointer resolve failed, such as index out of range, key not found. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_RESOLVE = 3; + +/** Document's root is NULL, but it is required for the function call. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_NULL_ROOT = 4; + +/** Cannot set root as the target is not a document. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_SET_ROOT = 5; + +/** The memory allocation failed and a new value could not be created. */ +static const yyjson_ptr_code YYJSON_PTR_ERR_MEMORY_ALLOCATION = 6; + +/** Error information for JSON pointer. */ +typedef struct yyjson_ptr_err { + /** Error code, see `yyjson_ptr_code` for all possible values. */ + yyjson_ptr_code code; + /** Error message, constant, no need to free (NULL if no error). */ + const char *msg; + /** Error byte position for input JSON pointer (0 if no error). */ + size_t pos; +} yyjson_ptr_err; + +/** + A context for JSON pointer operation. + + This struct stores the context of JSON Pointer operation result. The struct + can be used with three helper functions: `ctx_append()`, `ctx_replace()`, and + `ctx_remove()`, which perform the corresponding operations on the container + without re-parsing the JSON Pointer. + + For example: + @code + // doc before: {"a":[0,1,null]} + // ptr: "/a/2" + val = yyjson_mut_doc_ptr_getx(doc, ptr, strlen(ptr), &ctx, &err); + if (yyjson_is_null(val)) { + yyjson_ptr_ctx_remove(&ctx); + } + // doc after: {"a":[0,1]} + @endcode + */ +typedef struct yyjson_ptr_ctx { + /** + The container (parent) of the target value. It can be either an array or + an object. If the target location has no value, but all its parent + containers exist, and the target location can be used to insert a new + value, then `ctn` is the parent container of the target location. + Otherwise, `ctn` is NULL. + */ + yyjson_mut_val *ctn; + /** + The previous sibling of the target value. It can be either a value in an + array or a key in an object. As the container is a `circular linked list` + of elements, `pre` is the previous node of the target value. If the + operation is `add` or `set`, then `pre` is the previous node of the new + value, not the original target value. If the target value does not exist, + `pre` is NULL. + */ + yyjson_mut_val *pre; + /** + The removed value if the operation is `set`, `replace` or `remove`. It can + be used to restore the original state of the document if needed. + */ + yyjson_mut_val *old; +} yyjson_ptr_ctx; + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc, + const char *ptr, size_t len); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc, + const char *ptr, size_t len, + yyjson_ptr_err *err); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val, + const char *ptr, size_t len); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t len, + yyjson_ptr_err *err); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc, + const char *ptr, + size_t len); + +/** + Get value by a JSON Pointer. + @param doc The JSON document to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `doc` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val, + const char *ptr); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val, + const char *ptr, + size_t len); + +/** + Get value by a JSON Pointer. + @param val The JSON value to be queried. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The value referenced by the JSON pointer. + NULL if `val` or `ptr` is NULL, or the JSON pointer cannot be resolved. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Add (insert) value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val); + +/** + Add (insert) value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val); + +/** + Add (insert) value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be added. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is added, false otherwise. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Add (insert) value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param doc Only used to create new values when needed. + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Add (insert) value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param doc Only used to create new values when needed. + @param new_val The value to be added. + @return true if JSON pointer is valid and new value is added, false otherwise. + @note The parent nodes will be created if they do not exist. + */ +yyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Add (insert) value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param doc Only used to create new values when needed. + @param new_val The value to be added. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is added, false otherwise. + */ +yyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Set value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The value to be set, pass NULL to remove. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val); + +/** + Set value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val); + +/** + Set value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Set value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The value to be set, pass NULL to remove. + @param doc Only used to create new values when needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Set value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @param doc Only used to create new values when needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note The parent nodes will be created if they do not exist. + If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc); + +/** + Set value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The value to be set, pass NULL to remove. + @param doc Only used to create new values when needed. + @param create_parent Whether to create parent nodes if not exist. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return true if JSON pointer is valid and new value is set, false otherwise. + @note If the target value already exists, it will be replaced by the new value. + */ +yyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Replace value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace( + yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/** + Replace value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace( + yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val); + +/** + Replace value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param new_val The new value to replace the old one. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The old value that was replaced, or NULL if not found. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/** + Remove value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove( + yyjson_mut_doc *doc, const char *ptr); + +/** + Remove value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen( + yyjson_mut_doc *doc, const char *ptr, size_t len); + +/** + Remove value by a JSON pointer. + @param doc The target JSON document. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex( + yyjson_mut_doc *doc, const char *ptr, size_t len, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/** + Remove value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8 with null-terminator). + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val, + const char *ptr); + +/** + Remove value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val, + const char *ptr, + size_t len); + +/** + Remove value by a JSON pointer. + @param val The target JSON value. + @param ptr The JSON pointer string (UTF-8, null-terminator is not required). + @param len The length of `ptr` in bytes. + @param ctx A pointer to store the result context, or NULL if not needed. + @param err A pointer to store the error information, or NULL if not needed. + @return The removed value, or NULL on error. + */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/** + Append value by JSON pointer context. + @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. + @param key New key if `ctx->ctn` is object, or NULL if `ctx->ctn` is array. + @param val New value to be added. + @return true on success or false on fail. + */ +yyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx, + yyjson_mut_val *key, + yyjson_mut_val *val); + +/** + Replace value by JSON pointer context. + @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. + @param val New value to be replaced. + @return true on success or false on fail. + @note If success, the old value will be returned via `ctx->old`. + */ +yyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx, + yyjson_mut_val *val); + +/** + Remove value by JSON pointer context. + @param ctx The context from the `yyjson_mut_ptr_xxx()` calls. + @return true on success or false on fail. + @note If success, the old value will be returned via `ctx->old`. + */ +yyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx); + + + +/*============================================================================== + * MARK: - JSON Patch API (RFC 6902) + * https://tools.ietf.org/html/rfc6902 + *============================================================================*/ + +/** Result code for JSON patch. */ +typedef uint32_t yyjson_patch_code; + +/** Success, no error. */ +static const yyjson_patch_code YYJSON_PATCH_SUCCESS = 0; + +/** Invalid parameter, such as NULL input or non-array patch. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_PARAMETER = 1; + +/** Memory allocation failure occurs. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_MEMORY_ALLOCATION = 2; + +/** JSON patch operation is not object type. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_OPERATION = 3; + +/** JSON patch operation is missing a required key. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_MISSING_KEY = 4; + +/** JSON patch operation member is invalid. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_INVALID_MEMBER = 5; + +/** JSON patch operation `test` not equal. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_EQUAL = 6; + +/** JSON patch operation failed on JSON pointer. */ +static const yyjson_patch_code YYJSON_PATCH_ERROR_POINTER = 7; + +/** Error information for JSON patch. */ +typedef struct yyjson_patch_err { + /** Error code, see `yyjson_patch_code` for all possible values. */ + yyjson_patch_code code; + /** Index of the error operation (0 if no error). */ + size_t idx; + /** Error message, constant, no need to free (NULL if no error). */ + const char *msg; + /** JSON pointer error if `code == YYJSON_PATCH_ERROR_POINTER`. */ + yyjson_ptr_err ptr; +} yyjson_patch_err; + +/** + Creates and returns a patched JSON value (RFC 6902). + The memory of the returned value is allocated by the `doc`. + The `err` is used to receive error information, pass NULL if not needed. + Returns NULL if the patch could not be applied. + */ +yyjson_api yyjson_mut_val *yyjson_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch, + yyjson_patch_err *err); + +/** + Creates and returns a patched JSON value (RFC 6902). + The memory of the returned value is allocated by the `doc`. + The `err` is used to receive error information, pass NULL if not needed. + Returns NULL if the patch could not be applied. + */ +yyjson_api yyjson_mut_val *yyjson_mut_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch, + yyjson_patch_err *err); + + + +/*============================================================================== + * MARK: - JSON Merge-Patch API (RFC 7386) + * https://tools.ietf.org/html/rfc7386 + *============================================================================*/ + +/** + Creates and returns a merge-patched JSON value (RFC 7386). + The memory of the returned value is allocated by the `doc`. + Returns NULL if the patch could not be applied. + + @warning This function is recursive and may cause a stack overflow if the + object level is too deep. + */ +yyjson_api yyjson_mut_val *yyjson_merge_patch(yyjson_mut_doc *doc, + yyjson_val *orig, + yyjson_val *patch); + +/** + Creates and returns a merge-patched JSON value (RFC 7386). + The memory of the returned value is allocated by the `doc`. + Returns NULL if the patch could not be applied. + + @warning This function is recursive and may cause a stack overflow if the + object level is too deep. + */ +yyjson_api yyjson_mut_val *yyjson_mut_merge_patch(yyjson_mut_doc *doc, + yyjson_mut_val *orig, + yyjson_mut_val *patch); + +#endif /* YYJSON_DISABLE_UTILS */ + + + +/*============================================================================== + * MARK: - JSON Structure (Implementation) + *============================================================================*/ + +/** Payload of a JSON value (8 bytes). */ +typedef union yyjson_val_uni { + uint64_t u64; + int64_t i64; + double f64; + const char *str; + void *ptr; + size_t ofs; +} yyjson_val_uni; + +/** + Immutable JSON value, 16 bytes. + */ +struct yyjson_val { + uint64_t tag; /**< type, subtype and length */ + yyjson_val_uni uni; /**< payload */ +}; + +struct yyjson_doc { + /** Root value of the document (nonnull). */ + yyjson_val *root; + /** Allocator used by document (nonnull). */ + yyjson_alc alc; + /** The total number of bytes read when parsing JSON (nonzero). */ + size_t dat_read; + /** The total number of value read when parsing JSON (nonzero). */ + size_t val_read; + /** The string pool used by JSON values (nullable). */ + char *str_pool; +}; + + + +/*============================================================================== + * MARK: - Unsafe JSON Value API (Implementation) + *============================================================================*/ + +/* + Whether the string does not need to be escaped for serialization. + This function is used to optimize the writing speed of small constant strings. + This function works only if the compiler can evaluate it at compile time. + + Clang supports it since v8.0, + earlier versions do not support constant_p(strlen) and return false. + GCC supports it since at least v4.4, + earlier versions may compile it as run-time instructions. + ICC supports it since at least v16, + earlier versions are uncertain. + + @param str The C string. + @param len The returnd value from strlen(str). + */ +yyjson_api_inline bool unsafe_yyjson_is_str_noesc(const char *str, size_t len) { +#if YYJSON_HAS_CONSTANT_P && \ + (!YYJSON_IS_REAL_GCC || yyjson_gcc_available(4, 4, 0)) + if (yyjson_constant_p(len) && len <= 32) { + /* + Same as the following loop: + + for (size_t i = 0; i < len; i++) { + char c = str[i]; + if (c < ' ' || c > '~' || c == '"' || c == '\\') return false; + } + + GCC evaluates it at compile time only if the string length is within 17 + and -O3 (which turns on the -fpeel-loops flag) is used. + So the loop is unrolled for GCC. + */ +# define yyjson_repeat32_incr(x) \ + x(0) x(1) x(2) x(3) x(4) x(5) x(6) x(7) \ + x(8) x(9) x(10) x(11) x(12) x(13) x(14) x(15) \ + x(16) x(17) x(18) x(19) x(20) x(21) x(22) x(23) \ + x(24) x(25) x(26) x(27) x(28) x(29) x(30) x(31) +# define yyjson_check_char_noesc(i) \ + if (i < len) { \ + char c = str[i]; \ + if (c < ' ' || c > '~' || c == '"' || c == '\\') return false; } + yyjson_repeat32_incr(yyjson_check_char_noesc) +# undef yyjson_repeat32_incr +# undef yyjson_check_char_noesc + return true; + } +#else + (void)str; + (void)len; +#endif + return false; +} + +yyjson_api_inline double unsafe_yyjson_u64_to_f64(uint64_t num) { +#if YYJSON_U64_TO_F64_NO_IMPL + uint64_t msb = ((uint64_t)1) << 63; + if ((num & msb) == 0) { + return (double)(int64_t)num; + } else { + return ((double)(int64_t)((num >> 1) | (num & 1))) * (double)2.0; + } +#else + return (double)num; +#endif +} + +yyjson_api_inline yyjson_type unsafe_yyjson_get_type(void *val) { + uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; + return (yyjson_type)(tag & YYJSON_TYPE_MASK); +} + +yyjson_api_inline yyjson_subtype unsafe_yyjson_get_subtype(void *val) { + uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; + return (yyjson_subtype)(tag & YYJSON_SUBTYPE_MASK); +} + +yyjson_api_inline uint8_t unsafe_yyjson_get_tag(void *val) { + uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; + return (uint8_t)(tag & YYJSON_TAG_MASK); +} + +yyjson_api_inline bool unsafe_yyjson_is_raw(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_RAW; +} + +yyjson_api_inline bool unsafe_yyjson_is_null(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NULL; +} + +yyjson_api_inline bool unsafe_yyjson_is_bool(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_BOOL; +} + +yyjson_api_inline bool unsafe_yyjson_is_num(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_NUM; +} + +yyjson_api_inline bool unsafe_yyjson_is_str(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_STR; +} + +yyjson_api_inline bool unsafe_yyjson_is_arr(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_ARR; +} + +yyjson_api_inline bool unsafe_yyjson_is_obj(void *val) { + return unsafe_yyjson_get_type(val) == YYJSON_TYPE_OBJ; +} + +yyjson_api_inline bool unsafe_yyjson_is_ctn(void *val) { + uint8_t mask = YYJSON_TYPE_ARR & YYJSON_TYPE_OBJ; + return (unsafe_yyjson_get_tag(val) & mask) == mask; +} + +yyjson_api_inline bool unsafe_yyjson_is_uint(void *val) { + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_sint(void *val) { + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_int(void *val) { + const uint8_t mask = YYJSON_TAG_MASK & (~YYJSON_SUBTYPE_SINT); + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; + return (unsafe_yyjson_get_tag(val) & mask) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_real(void *val) { + const uint8_t patt = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_true(void *val) { + const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_is_false(void *val) { + const uint8_t patt = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; + return unsafe_yyjson_get_tag(val) == patt; +} + +yyjson_api_inline bool unsafe_yyjson_arr_is_flat(yyjson_val *val) { + size_t ofs = val->uni.ofs; + size_t len = (size_t)(val->tag >> YYJSON_TAG_BIT); + return len * sizeof(yyjson_val) + sizeof(yyjson_val) == ofs; +} + +yyjson_api_inline const char *unsafe_yyjson_get_raw(void *val) { + return ((yyjson_val *)val)->uni.str; +} + +yyjson_api_inline bool unsafe_yyjson_get_bool(void *val) { + uint8_t tag = unsafe_yyjson_get_tag(val); + return (bool)((tag & YYJSON_SUBTYPE_MASK) >> YYJSON_TYPE_BIT); +} + +yyjson_api_inline uint64_t unsafe_yyjson_get_uint(void *val) { + return ((yyjson_val *)val)->uni.u64; +} + +yyjson_api_inline int64_t unsafe_yyjson_get_sint(void *val) { + return ((yyjson_val *)val)->uni.i64; +} + +yyjson_api_inline int unsafe_yyjson_get_int(void *val) { + return (int)((yyjson_val *)val)->uni.i64; +} + +yyjson_api_inline double unsafe_yyjson_get_real(void *val) { + return ((yyjson_val *)val)->uni.f64; +} + +yyjson_api_inline double unsafe_yyjson_get_num(void *val) { + uint8_t tag = unsafe_yyjson_get_tag(val); + if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL)) { + return ((yyjson_val *)val)->uni.f64; + } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT)) { + return (double)((yyjson_val *)val)->uni.i64; + } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT)) { + return unsafe_yyjson_u64_to_f64(((yyjson_val *)val)->uni.u64); + } + return 0.0; +} + +yyjson_api_inline const char *unsafe_yyjson_get_str(void *val) { + return ((yyjson_val *)val)->uni.str; +} + +yyjson_api_inline size_t unsafe_yyjson_get_len(void *val) { + return (size_t)(((yyjson_val *)val)->tag >> YYJSON_TAG_BIT); +} + +yyjson_api_inline yyjson_val *unsafe_yyjson_get_first(yyjson_val *ctn) { + return ctn + 1; +} + +yyjson_api_inline yyjson_val *unsafe_yyjson_get_next(yyjson_val *val) { + bool is_ctn = unsafe_yyjson_is_ctn(val); + size_t ctn_ofs = val->uni.ofs; + size_t ofs = (is_ctn ? ctn_ofs : sizeof(yyjson_val)); + return (yyjson_val *)(void *)((uint8_t *)val + ofs); +} + +yyjson_api_inline bool unsafe_yyjson_equals_strn(void *val, const char *str, + size_t len) { + return unsafe_yyjson_get_len(val) == len && + memcmp(((yyjson_val *)val)->uni.str, str, len) == 0; +} + +yyjson_api_inline bool unsafe_yyjson_equals_str(void *val, const char *str) { + return unsafe_yyjson_equals_strn(val, str, strlen(str)); +} + +yyjson_api_inline void unsafe_yyjson_set_type(void *val, yyjson_type type, + yyjson_subtype subtype) { + uint8_t tag = (type | subtype); + uint64_t new_tag = ((yyjson_val *)val)->tag; + new_tag = (new_tag & (~(uint64_t)YYJSON_TAG_MASK)) | (uint64_t)tag; + ((yyjson_val *)val)->tag = new_tag; +} + +yyjson_api_inline void unsafe_yyjson_set_len(void *val, size_t len) { + uint64_t tag = ((yyjson_val *)val)->tag & YYJSON_TAG_MASK; + tag |= (uint64_t)len << YYJSON_TAG_BIT; + ((yyjson_val *)val)->tag = tag; +} + +yyjson_api_inline void unsafe_yyjson_set_tag(void *val, yyjson_type type, + yyjson_subtype subtype, + size_t len) { + uint64_t tag = (uint64_t)len << YYJSON_TAG_BIT; + tag |= (type | subtype); + ((yyjson_val *)val)->tag = tag; +} + +yyjson_api_inline void unsafe_yyjson_inc_len(void *val) { + uint64_t tag = ((yyjson_val *)val)->tag; + tag += (uint64_t)(1 << YYJSON_TAG_BIT); + ((yyjson_val *)val)->tag = tag; +} + +yyjson_api_inline void unsafe_yyjson_set_raw(void *val, const char *raw, + size_t len) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_RAW, YYJSON_SUBTYPE_NONE, len); + ((yyjson_val *)val)->uni.str = raw; +} + +yyjson_api_inline void unsafe_yyjson_set_null(void *val) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NULL, YYJSON_SUBTYPE_NONE, 0); +} + +yyjson_api_inline void unsafe_yyjson_set_bool(void *val, bool num) { + yyjson_subtype subtype = num ? YYJSON_SUBTYPE_TRUE : YYJSON_SUBTYPE_FALSE; + unsafe_yyjson_set_tag(val, YYJSON_TYPE_BOOL, subtype, 0); +} + +yyjson_api_inline void unsafe_yyjson_set_uint(void *val, uint64_t num) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_UINT, 0); + ((yyjson_val *)val)->uni.u64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_sint(void *val, int64_t num) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_SINT, 0); + ((yyjson_val *)val)->uni.i64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_fp_to_fixed(void *val, int prec) { + ((yyjson_val *)val)->tag &= ~((uint64_t)YYJSON_WRITE_FP_TO_FIXED(15) << 32); + ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_WRITE_FP_TO_FIXED(prec) << 32; +} + +yyjson_api_inline void unsafe_yyjson_set_fp_to_float(void *val, bool flt) { + uint64_t flag = (uint64_t)YYJSON_WRITE_FP_TO_FLOAT << 32; + if (flt) ((yyjson_val *)val)->tag |= flag; + else ((yyjson_val *)val)->tag &= ~flag; +} + +yyjson_api_inline void unsafe_yyjson_set_float(void *val, float num) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0); + ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_WRITE_FP_TO_FLOAT << 32; + ((yyjson_val *)val)->uni.f64 = (double)num; +} + +yyjson_api_inline void unsafe_yyjson_set_double(void *val, double num) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0); + ((yyjson_val *)val)->uni.f64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_real(void *val, double num) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0); + ((yyjson_val *)val)->uni.f64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_str_noesc(void *val, bool noesc) { + ((yyjson_val *)val)->tag &= ~(uint64_t)YYJSON_SUBTYPE_MASK; + if (noesc) ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_SUBTYPE_NOESC; +} + +yyjson_api_inline void unsafe_yyjson_set_strn(void *val, const char *str, + size_t len) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, YYJSON_SUBTYPE_NONE, len); + ((yyjson_val *)val)->uni.str = str; +} + +yyjson_api_inline void unsafe_yyjson_set_str(void *val, const char *str) { + size_t len = strlen(str); + bool noesc = unsafe_yyjson_is_str_noesc(str, len); + yyjson_subtype subtype = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; + unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, subtype, len); + ((yyjson_val *)val)->uni.str = str; +} + +yyjson_api_inline void unsafe_yyjson_set_arr(void *val, size_t size) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_ARR, YYJSON_SUBTYPE_NONE, size); +} + +yyjson_api_inline void unsafe_yyjson_set_obj(void *val, size_t size) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_OBJ, YYJSON_SUBTYPE_NONE, size); +} + + + +/*============================================================================== + * MARK: - JSON Document API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_val *yyjson_doc_get_root(yyjson_doc *doc) { + return doc ? doc->root : NULL; +} + +yyjson_api_inline size_t yyjson_doc_get_read_size(yyjson_doc *doc) { + return doc ? doc->dat_read : 0; +} + +yyjson_api_inline size_t yyjson_doc_get_val_count(yyjson_doc *doc) { + return doc ? doc->val_read : 0; +} + +yyjson_api_inline void yyjson_doc_free(yyjson_doc *doc) { + if (doc) { + yyjson_alc alc = doc->alc; + memset(&doc->alc, 0, sizeof(alc)); + if (doc->str_pool) alc.free(alc.ctx, doc->str_pool); + alc.free(alc.ctx, doc); + } +} + + + +/*============================================================================== + * MARK: - JSON Value Type API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_is_raw(yyjson_val *val) { + return val ? unsafe_yyjson_is_raw(val) : false; +} + +yyjson_api_inline bool yyjson_is_null(yyjson_val *val) { + return val ? unsafe_yyjson_is_null(val) : false; +} + +yyjson_api_inline bool yyjson_is_true(yyjson_val *val) { + return val ? unsafe_yyjson_is_true(val) : false; +} + +yyjson_api_inline bool yyjson_is_false(yyjson_val *val) { + return val ? unsafe_yyjson_is_false(val) : false; +} + +yyjson_api_inline bool yyjson_is_bool(yyjson_val *val) { + return val ? unsafe_yyjson_is_bool(val) : false; +} + +yyjson_api_inline bool yyjson_is_uint(yyjson_val *val) { + return val ? unsafe_yyjson_is_uint(val) : false; +} + +yyjson_api_inline bool yyjson_is_sint(yyjson_val *val) { + return val ? unsafe_yyjson_is_sint(val) : false; +} + +yyjson_api_inline bool yyjson_is_int(yyjson_val *val) { + return val ? unsafe_yyjson_is_int(val) : false; +} + +yyjson_api_inline bool yyjson_is_real(yyjson_val *val) { + return val ? unsafe_yyjson_is_real(val) : false; +} + +yyjson_api_inline bool yyjson_is_num(yyjson_val *val) { + return val ? unsafe_yyjson_is_num(val) : false; +} + +yyjson_api_inline bool yyjson_is_str(yyjson_val *val) { + return val ? unsafe_yyjson_is_str(val) : false; +} + +yyjson_api_inline bool yyjson_is_arr(yyjson_val *val) { + return val ? unsafe_yyjson_is_arr(val) : false; +} + +yyjson_api_inline bool yyjson_is_obj(yyjson_val *val) { + return val ? unsafe_yyjson_is_obj(val) : false; +} + +yyjson_api_inline bool yyjson_is_ctn(yyjson_val *val) { + return val ? unsafe_yyjson_is_ctn(val) : false; +} + + + +/*============================================================================== + * MARK: - JSON Value Content API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_type yyjson_get_type(yyjson_val *val) { + return val ? unsafe_yyjson_get_type(val) : YYJSON_TYPE_NONE; +} + +yyjson_api_inline yyjson_subtype yyjson_get_subtype(yyjson_val *val) { + return val ? unsafe_yyjson_get_subtype(val) : YYJSON_SUBTYPE_NONE; +} + +yyjson_api_inline uint8_t yyjson_get_tag(yyjson_val *val) { + return val ? unsafe_yyjson_get_tag(val) : 0; +} + +yyjson_api_inline const char *yyjson_get_type_desc(yyjson_val *val) { + switch (yyjson_get_tag(val)) { + case YYJSON_TYPE_RAW | YYJSON_SUBTYPE_NONE: return "raw"; + case YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE: return "null"; + case YYJSON_TYPE_STR | YYJSON_SUBTYPE_NONE: return "string"; + case YYJSON_TYPE_STR | YYJSON_SUBTYPE_NOESC: return "string"; + case YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE: return "array"; + case YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE: return "object"; + case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE: return "true"; + case YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE: return "false"; + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT: return "uint"; + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT: return "sint"; + case YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL: return "real"; + default: return "unknown"; + } +} + +yyjson_api_inline const char *yyjson_get_raw(yyjson_val *val) { + return yyjson_is_raw(val) ? unsafe_yyjson_get_raw(val) : NULL; +} + +yyjson_api_inline bool yyjson_get_bool(yyjson_val *val) { + return yyjson_is_bool(val) ? unsafe_yyjson_get_bool(val) : false; +} + +yyjson_api_inline uint64_t yyjson_get_uint(yyjson_val *val) { + return yyjson_is_int(val) ? unsafe_yyjson_get_uint(val) : 0; +} + +yyjson_api_inline int64_t yyjson_get_sint(yyjson_val *val) { + return yyjson_is_int(val) ? unsafe_yyjson_get_sint(val) : 0; +} + +yyjson_api_inline int yyjson_get_int(yyjson_val *val) { + return yyjson_is_int(val) ? unsafe_yyjson_get_int(val) : 0; +} + +yyjson_api_inline double yyjson_get_real(yyjson_val *val) { + return yyjson_is_real(val) ? unsafe_yyjson_get_real(val) : 0.0; +} + +yyjson_api_inline double yyjson_get_num(yyjson_val *val) { + return val ? unsafe_yyjson_get_num(val) : 0.0; +} + +yyjson_api_inline const char *yyjson_get_str(yyjson_val *val) { + return yyjson_is_str(val) ? unsafe_yyjson_get_str(val) : NULL; +} + +yyjson_api_inline size_t yyjson_get_len(yyjson_val *val) { + return val ? unsafe_yyjson_get_len(val) : 0; +} + +yyjson_api_inline bool yyjson_equals_str(yyjson_val *val, const char *str) { + if (yyjson_likely(val && str)) { + return unsafe_yyjson_is_str(val) && + unsafe_yyjson_equals_str(val, str); + } + return false; +} + +yyjson_api_inline bool yyjson_equals_strn(yyjson_val *val, const char *str, + size_t len) { + if (yyjson_likely(val && str)) { + return unsafe_yyjson_is_str(val) && + unsafe_yyjson_equals_strn(val, str, len); + } + return false; +} + +yyjson_api bool unsafe_yyjson_equals(yyjson_val *lhs, yyjson_val *rhs); + +yyjson_api_inline bool yyjson_equals(yyjson_val *lhs, yyjson_val *rhs) { + if (yyjson_unlikely(!lhs || !rhs)) return false; + return unsafe_yyjson_equals(lhs, rhs); +} + +yyjson_api_inline bool yyjson_set_raw(yyjson_val *val, + const char *raw, size_t len) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_raw(val, raw, len); + return true; +} + +yyjson_api_inline bool yyjson_set_null(yyjson_val *val) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_null(val); + return true; +} + +yyjson_api_inline bool yyjson_set_bool(yyjson_val *val, bool num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_bool(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_uint(yyjson_val *val, uint64_t num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_uint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_sint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_sint(val, (int64_t)num); + return true; +} + +yyjson_api_inline bool yyjson_set_float(yyjson_val *val, float num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_float(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_double(yyjson_val *val, double num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_double(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_real(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_fp_to_fixed(yyjson_val *val, int prec) { + if (yyjson_unlikely(!yyjson_is_real(val))) return false; + unsafe_yyjson_set_fp_to_fixed(val, prec); + return true; +} + +yyjson_api_inline bool yyjson_set_fp_to_float(yyjson_val *val, bool flt) { + if (yyjson_unlikely(!yyjson_is_real(val))) return false; + unsafe_yyjson_set_fp_to_float(val, flt); + return true; +} + +yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + if (yyjson_unlikely(!str)) return false; + unsafe_yyjson_set_str(val, str); + return true; +} + +yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, + const char *str, size_t len) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + if (yyjson_unlikely(!str)) return false; + unsafe_yyjson_set_strn(val, str, len); + return true; +} + +yyjson_api_inline bool yyjson_set_str_noesc(yyjson_val *val, bool noesc) { + if (yyjson_unlikely(!yyjson_is_str(val))) return false; + unsafe_yyjson_set_str_noesc(val, noesc); + return true; +} + + + +/*============================================================================== + * MARK: - JSON Array API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_arr_size(yyjson_val *arr) { + return yyjson_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0; +} + +yyjson_api_inline yyjson_val *yyjson_arr_get(yyjson_val *arr, size_t idx) { + if (yyjson_likely(yyjson_is_arr(arr))) { + if (yyjson_likely(unsafe_yyjson_get_len(arr) > idx)) { + yyjson_val *val = unsafe_yyjson_get_first(arr); + if (unsafe_yyjson_arr_is_flat(arr)) { + return val + idx; + } else { + while (idx-- > 0) val = unsafe_yyjson_get_next(val); + return val; + } + } + } + return NULL; +} + +yyjson_api_inline yyjson_val *yyjson_arr_get_first(yyjson_val *arr) { + if (yyjson_likely(yyjson_is_arr(arr))) { + if (yyjson_likely(unsafe_yyjson_get_len(arr) > 0)) { + return unsafe_yyjson_get_first(arr); + } + } + return NULL; +} + +yyjson_api_inline yyjson_val *yyjson_arr_get_last(yyjson_val *arr) { + if (yyjson_likely(yyjson_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(len > 0)) { + yyjson_val *val = unsafe_yyjson_get_first(arr); + if (unsafe_yyjson_arr_is_flat(arr)) { + return val + (len - 1); + } else { + while (len-- > 1) val = unsafe_yyjson_get_next(val); + return val; + } + } + } + return NULL; +} + + + +/*============================================================================== + * MARK: - JSON Array Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_arr_iter_init(yyjson_val *arr, + yyjson_arr_iter *iter) { + if (yyjson_likely(yyjson_is_arr(arr) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(arr); + iter->cur = unsafe_yyjson_get_first(arr); + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_arr_iter)); + return false; +} + +yyjson_api_inline yyjson_arr_iter yyjson_arr_iter_with(yyjson_val *arr) { + yyjson_arr_iter iter; + yyjson_arr_iter_init(arr, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_arr_iter_has_next(yyjson_arr_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_val *yyjson_arr_iter_next(yyjson_arr_iter *iter) { + yyjson_val *val; + if (iter && iter->idx < iter->max) { + val = iter->cur; + iter->cur = unsafe_yyjson_get_next(val); + iter->idx++; + return val; + } + return NULL; +} + + + +/*============================================================================== + * MARK: - JSON Object API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_obj_size(yyjson_val *obj) { + return yyjson_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0; +} + +yyjson_api_inline yyjson_val *yyjson_obj_get(yyjson_val *obj, + const char *key) { + return yyjson_obj_getn(obj, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_val *yyjson_obj_getn(yyjson_val *obj, + const char *_key, + size_t key_len) { + if (yyjson_likely(yyjson_is_obj(obj) && _key)) { + size_t len = unsafe_yyjson_get_len(obj); + yyjson_val *key = unsafe_yyjson_get_first(obj); + while (len-- > 0) { + if (unsafe_yyjson_equals_strn(key, _key, key_len)) return key + 1; + key = unsafe_yyjson_get_next(key + 1); + } + } + return NULL; +} + + + +/*============================================================================== + * MARK: - JSON Object Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_obj_iter_init(yyjson_val *obj, + yyjson_obj_iter *iter) { + if (yyjson_likely(yyjson_is_obj(obj) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(obj); + iter->cur = unsafe_yyjson_get_first(obj); + iter->obj = obj; + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_obj_iter)); + return false; +} + +yyjson_api_inline yyjson_obj_iter yyjson_obj_iter_with(yyjson_val *obj) { + yyjson_obj_iter iter; + yyjson_obj_iter_init(obj, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_obj_iter_has_next(yyjson_obj_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_next(yyjson_obj_iter *iter) { + if (iter && iter->idx < iter->max) { + yyjson_val *key = iter->cur; + iter->idx++; + iter->cur = unsafe_yyjson_get_next(key + 1); + return key; + } + return NULL; +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_get_val(yyjson_val *key) { + return key ? key + 1 : NULL; +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_get(yyjson_obj_iter *iter, + const char *key) { + return yyjson_obj_iter_getn(iter, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_val *yyjson_obj_iter_getn(yyjson_obj_iter *iter, + const char *key, + size_t key_len) { + if (iter && key) { + size_t idx = iter->idx; + size_t max = iter->max; + yyjson_val *cur = iter->cur; + if (yyjson_unlikely(idx == max)) { + idx = 0; + cur = unsafe_yyjson_get_first(iter->obj); + } + while (idx++ < max) { + yyjson_val *next = unsafe_yyjson_get_next(cur + 1); + if (unsafe_yyjson_equals_strn(cur, key, key_len)) { + iter->idx = idx; + iter->cur = next; + return cur + 1; + } + cur = next; + if (idx == iter->max && iter->idx < iter->max) { + idx = 0; + max = iter->idx; + cur = unsafe_yyjson_get_first(iter->obj); + } + } + } + return NULL; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Structure (Implementation) + *============================================================================*/ + +/** + Mutable JSON value, 24 bytes. + The 'tag' and 'uni' field is same as immutable value. + The 'next' field links all elements inside the container to be a cycle. + */ +struct yyjson_mut_val { + uint64_t tag; /**< type, subtype and length */ + yyjson_val_uni uni; /**< payload */ + yyjson_mut_val *next; /**< the next value in circular linked list */ +}; + +/** + A memory chunk in string memory pool. + */ +typedef struct yyjson_str_chunk { + struct yyjson_str_chunk *next; /* next chunk linked list */ + size_t chunk_size; /* chunk size in bytes */ + /* char str[]; flexible array member */ +} yyjson_str_chunk; + +/** + A memory pool to hold all strings in a mutable document. + */ +typedef struct yyjson_str_pool { + char *cur; /* cursor inside current chunk */ + char *end; /* the end of current chunk */ + size_t chunk_size; /* chunk size in bytes while creating new chunk */ + size_t chunk_size_max; /* maximum chunk size in bytes */ + yyjson_str_chunk *chunks; /* a linked list of chunks, nullable */ +} yyjson_str_pool; + +/** + A memory chunk in value memory pool. + `sizeof(yyjson_val_chunk)` should not larger than `sizeof(yyjson_mut_val)`. + */ +typedef struct yyjson_val_chunk { + struct yyjson_val_chunk *next; /* next chunk linked list */ + size_t chunk_size; /* chunk size in bytes */ + /* char pad[sizeof(yyjson_mut_val) - sizeof(yyjson_val_chunk)]; padding */ + /* yyjson_mut_val vals[]; flexible array member */ +} yyjson_val_chunk; + +/** + A memory pool to hold all values in a mutable document. + */ +typedef struct yyjson_val_pool { + yyjson_mut_val *cur; /* cursor inside current chunk */ + yyjson_mut_val *end; /* the end of current chunk */ + size_t chunk_size; /* chunk size in bytes while creating new chunk */ + size_t chunk_size_max; /* maximum chunk size in bytes */ + yyjson_val_chunk *chunks; /* a linked list of chunks, nullable */ +} yyjson_val_pool; + +struct yyjson_mut_doc { + yyjson_mut_val *root; /**< root value of the JSON document, nullable */ + yyjson_alc alc; /**< a valid allocator, nonnull */ + yyjson_str_pool str_pool; /**< string memory pool */ + yyjson_val_pool val_pool; /**< value memory pool */ +}; + +/* Ensures the capacity to at least equal to the specified byte length. */ +yyjson_api bool unsafe_yyjson_str_pool_grow(yyjson_str_pool *pool, + const yyjson_alc *alc, + size_t len); + +/* Ensures the capacity to at least equal to the specified value count. */ +yyjson_api bool unsafe_yyjson_val_pool_grow(yyjson_val_pool *pool, + const yyjson_alc *alc, + size_t count); + +/* Allocate memory for string. */ +yyjson_api_inline char *unsafe_yyjson_mut_str_alc(yyjson_mut_doc *doc, + size_t len) { + char *mem; + const yyjson_alc *alc = &doc->alc; + yyjson_str_pool *pool = &doc->str_pool; + if (yyjson_unlikely((size_t)(pool->end - pool->cur) <= len)) { + if (yyjson_unlikely(!unsafe_yyjson_str_pool_grow(pool, alc, len + 1))) { + return NULL; + } + } + mem = pool->cur; + pool->cur = mem + len + 1; + return mem; +} + +yyjson_api_inline char *unsafe_yyjson_mut_strncpy(yyjson_mut_doc *doc, + const char *str, size_t len) { + char *mem = unsafe_yyjson_mut_str_alc(doc, len); + if (yyjson_unlikely(!mem)) return NULL; + memcpy((void *)mem, (const void *)str, len); + mem[len] = '\0'; + return mem; +} + +yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_val(yyjson_mut_doc *doc, + size_t count) { + yyjson_mut_val *val; + yyjson_alc *alc = &doc->alc; + yyjson_val_pool *pool = &doc->val_pool; + if (yyjson_unlikely((size_t)(pool->end - pool->cur) < count)) { + if (yyjson_unlikely(!unsafe_yyjson_val_pool_grow(pool, alc, count))) { + return NULL; + } + } + val = pool->cur; + pool->cur += count; + return val; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Document API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_root(yyjson_mut_doc *doc) { + return doc ? doc->root : NULL; +} + +yyjson_api_inline void yyjson_mut_doc_set_root(yyjson_mut_doc *doc, + yyjson_mut_val *root) { + if (doc) doc->root = root; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Value Type API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_is_raw(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_raw(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_null(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_null(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_true(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_true(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_false(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_false(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_bool(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_bool(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_uint(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_uint(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_sint(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_sint(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_int(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_int(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_real(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_real(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_num(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_num(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_str(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_str(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_arr(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_arr(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_obj(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_obj(val) : false; +} + +yyjson_api_inline bool yyjson_mut_is_ctn(yyjson_mut_val *val) { + return val ? unsafe_yyjson_is_ctn(val) : false; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Value Content API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_type yyjson_mut_get_type(yyjson_mut_val *val) { + return yyjson_get_type((yyjson_val *)val); +} + +yyjson_api_inline yyjson_subtype yyjson_mut_get_subtype(yyjson_mut_val *val) { + return yyjson_get_subtype((yyjson_val *)val); +} + +yyjson_api_inline uint8_t yyjson_mut_get_tag(yyjson_mut_val *val) { + return yyjson_get_tag((yyjson_val *)val); +} + +yyjson_api_inline const char *yyjson_mut_get_type_desc(yyjson_mut_val *val) { + return yyjson_get_type_desc((yyjson_val *)val); +} + +yyjson_api_inline const char *yyjson_mut_get_raw(yyjson_mut_val *val) { + return yyjson_get_raw((yyjson_val *)val); +} + +yyjson_api_inline bool yyjson_mut_get_bool(yyjson_mut_val *val) { + return yyjson_get_bool((yyjson_val *)val); +} + +yyjson_api_inline uint64_t yyjson_mut_get_uint(yyjson_mut_val *val) { + return yyjson_get_uint((yyjson_val *)val); +} + +yyjson_api_inline int64_t yyjson_mut_get_sint(yyjson_mut_val *val) { + return yyjson_get_sint((yyjson_val *)val); +} + +yyjson_api_inline int yyjson_mut_get_int(yyjson_mut_val *val) { + return yyjson_get_int((yyjson_val *)val); +} + +yyjson_api_inline double yyjson_mut_get_real(yyjson_mut_val *val) { + return yyjson_get_real((yyjson_val *)val); +} + +yyjson_api_inline double yyjson_mut_get_num(yyjson_mut_val *val) { + return yyjson_get_num((yyjson_val *)val); +} + +yyjson_api_inline const char *yyjson_mut_get_str(yyjson_mut_val *val) { + return yyjson_get_str((yyjson_val *)val); +} + +yyjson_api_inline size_t yyjson_mut_get_len(yyjson_mut_val *val) { + return yyjson_get_len((yyjson_val *)val); +} + +yyjson_api_inline bool yyjson_mut_equals_str(yyjson_mut_val *val, + const char *str) { + return yyjson_equals_str((yyjson_val *)val, str); +} + +yyjson_api_inline bool yyjson_mut_equals_strn(yyjson_mut_val *val, + const char *str, size_t len) { + return yyjson_equals_strn((yyjson_val *)val, str, len); +} + +yyjson_api bool unsafe_yyjson_mut_equals(yyjson_mut_val *lhs, + yyjson_mut_val *rhs); + +yyjson_api_inline bool yyjson_mut_equals(yyjson_mut_val *lhs, + yyjson_mut_val *rhs) { + if (yyjson_unlikely(!lhs || !rhs)) return false; + return unsafe_yyjson_mut_equals(lhs, rhs); +} + +yyjson_api_inline bool yyjson_mut_set_raw(yyjson_mut_val *val, + const char *raw, size_t len) { + if (yyjson_unlikely(!val || !raw)) return false; + unsafe_yyjson_set_raw(val, raw, len); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_null(yyjson_mut_val *val) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_null(val); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_bool(yyjson_mut_val *val, bool num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_bool(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_uint(yyjson_mut_val *val, uint64_t num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_uint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_sint(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_sint(val, (int64_t)num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_float(yyjson_mut_val *val, float num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_float(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_double(yyjson_mut_val *val, double num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_double(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_real(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_fp_to_fixed(yyjson_mut_val *val, + int prec) { + if (yyjson_unlikely(!yyjson_mut_is_real(val))) return false; + unsafe_yyjson_set_fp_to_fixed(val, prec); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_fp_to_float(yyjson_mut_val *val, + bool flt) { + if (yyjson_unlikely(!yyjson_mut_is_real(val))) return false; + unsafe_yyjson_set_fp_to_float(val, flt); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, + const char *str) { + if (yyjson_unlikely(!val || !str)) return false; + unsafe_yyjson_set_str(val, str); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, + const char *str, size_t len) { + if (yyjson_unlikely(!val || !str)) return false; + unsafe_yyjson_set_strn(val, str, len); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_str_noesc(yyjson_mut_val *val, + bool noesc) { + if (yyjson_unlikely(!yyjson_mut_is_str(val))) return false; + unsafe_yyjson_set_str_noesc(val, noesc); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_arr(val, 0); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_obj(val, 0); + return true; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Value Creation API (Implementation) + *============================================================================*/ + +#define yyjson_mut_val_one(func) \ + if (yyjson_likely(doc)) { \ + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); \ + if (yyjson_likely(val)) { \ + func \ + return val; \ + } \ + } \ + return NULL + +#define yyjson_mut_val_one_str(func) \ + if (yyjson_likely(doc && str)) { \ + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); \ + if (yyjson_likely(val)) { \ + func \ + return val; \ + } \ + } \ + return NULL + +yyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc, + const char *str) { + yyjson_mut_val_one_str({ unsafe_yyjson_set_raw(val, str, strlen(str)); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc, + const char *str, + size_t len) { + yyjson_mut_val_one_str({ unsafe_yyjson_set_raw(val, str, len); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc, + const char *str) { + yyjson_mut_val_one_str({ + size_t len = strlen(str); + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_raw(val, new_str, len); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc, + const char *str, + size_t len) { + yyjson_mut_val_one_str({ + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_raw(val, new_str, len); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc) { + yyjson_mut_val_one({ unsafe_yyjson_set_null(val); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc) { + yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, true); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc) { + yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, false); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, + bool _val) { + yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, _val); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, + uint64_t num) { + yyjson_mut_val_one({ unsafe_yyjson_set_uint(val, num); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, + int64_t num) { + yyjson_mut_val_one({ unsafe_yyjson_set_sint(val, num); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, + int64_t num) { + yyjson_mut_val_one({ unsafe_yyjson_set_sint(val, num); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_float(yyjson_mut_doc *doc, + float num) { + yyjson_mut_val_one({ unsafe_yyjson_set_float(val, num); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_double(yyjson_mut_doc *doc, + double num) { + yyjson_mut_val_one({ unsafe_yyjson_set_double(val, num); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, + double num) { + yyjson_mut_val_one({ unsafe_yyjson_set_real(val, num); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc, + const char *str) { + yyjson_mut_val_one_str({ unsafe_yyjson_set_str(val, str); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc, + const char *str, + size_t len) { + yyjson_mut_val_one_str({ unsafe_yyjson_set_strn(val, str, len); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc, + const char *str) { + yyjson_mut_val_one_str({ + size_t len = strlen(str); + bool noesc = unsafe_yyjson_is_str_noesc(str, len); + yyjson_subtype sub = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, sub, len); + val->uni.str = new_str; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc, + const char *str, + size_t len) { + yyjson_mut_val_one_str({ + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_strn(val, new_str, len); + }); +} + +#undef yyjson_mut_val_one +#undef yyjson_mut_val_one_str + + + +/*============================================================================== + * MARK: - Mutable JSON Array API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_mut_arr_size(yyjson_mut_val *arr) { + return yyjson_mut_is_arr(arr) ? unsafe_yyjson_get_len(arr) : 0; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get(yyjson_mut_val *arr, + size_t idx) { + if (yyjson_likely(idx < yyjson_mut_arr_size(arr))) { + yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; + while (idx-- > 0) val = val->next; + return val->next; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_first( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) { + return ((yyjson_mut_val *)arr->uni.ptr)->next; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_get_last( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_arr_size(arr) > 0)) { + return ((yyjson_mut_val *)arr->uni.ptr); + } + return NULL; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Array Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_arr_iter_init(yyjson_mut_val *arr, + yyjson_mut_arr_iter *iter) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(arr); + iter->cur = iter->max ? (yyjson_mut_val *)arr->uni.ptr : NULL; + iter->pre = NULL; + iter->arr = arr; + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_mut_arr_iter)); + return false; +} + +yyjson_api_inline yyjson_mut_arr_iter yyjson_mut_arr_iter_with( + yyjson_mut_val *arr) { + yyjson_mut_arr_iter iter; + yyjson_mut_arr_iter_init(arr, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_mut_arr_iter_has_next(yyjson_mut_arr_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_next( + yyjson_mut_arr_iter *iter) { + if (iter && iter->idx < iter->max) { + yyjson_mut_val *val = iter->cur; + iter->pre = val; + iter->cur = val->next; + iter->idx++; + return iter->cur; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_iter_remove( + yyjson_mut_arr_iter *iter) { + if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) { + yyjson_mut_val *prev = iter->pre; + yyjson_mut_val *cur = iter->cur; + yyjson_mut_val *next = cur->next; + if (yyjson_unlikely(iter->idx == iter->max)) iter->arr->uni.ptr = prev; + iter->idx--; + iter->max--; + unsafe_yyjson_set_len(iter->arr, iter->max); + prev->next = next; + iter->cur = prev; + return cur; + } + return NULL; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Array Creation API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE; + return val; + } + } + return NULL; +} + +#define yyjson_mut_arr_with_func(func) \ + if (yyjson_likely(doc && ((0 < count && count < \ + (~(size_t)0) / sizeof(yyjson_mut_val) && vals) || count == 0))) { \ + yyjson_mut_val *arr = unsafe_yyjson_mut_val(doc, 1 + count); \ + if (yyjson_likely(arr)) { \ + arr->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_ARR; \ + if (count > 0) { \ + size_t i; \ + for (i = 0; i < count; i++) { \ + yyjson_mut_val *val = arr + i + 1; \ + func \ + val->next = val + 1; \ + } \ + arr[count].next = arr + 1; \ + arr->uni.ptr = arr + count; \ + } \ + return arr; \ + } \ + } \ + return NULL + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool( + yyjson_mut_doc *doc, const bool *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_bool(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint( + yyjson_mut_doc *doc, const int64_t *vals, size_t count) { + return yyjson_mut_arr_with_sint64(doc, vals, count); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count) { + return yyjson_mut_arr_with_uint64(doc, vals, count); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real( + yyjson_mut_doc *doc, const double *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_real(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8( + yyjson_mut_doc *doc, const int8_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_sint(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16( + yyjson_mut_doc *doc, const int16_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_sint(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32( + yyjson_mut_doc *doc, const int32_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_sint(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64( + yyjson_mut_doc *doc, const int64_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_sint(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8( + yyjson_mut_doc *doc, const uint8_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_uint(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16( + yyjson_mut_doc *doc, const uint16_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_uint(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32( + yyjson_mut_doc *doc, const uint32_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_uint(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64( + yyjson_mut_doc *doc, const uint64_t *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_uint(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float( + yyjson_mut_doc *doc, const float *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_float(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double( + yyjson_mut_doc *doc, const double *vals, size_t count) { + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_double(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str( + yyjson_mut_doc *doc, const char **vals, size_t count) { + yyjson_mut_arr_with_func({ + if (yyjson_unlikely(!vals[i])) return NULL; + unsafe_yyjson_set_str(val, vals[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { + if (yyjson_unlikely(count > 0 && !lens)) return NULL; + yyjson_mut_arr_with_func({ + if (yyjson_unlikely(!vals[i])) return NULL; + unsafe_yyjson_set_strn(val, vals[i], lens[i]); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy( + yyjson_mut_doc *doc, const char **vals, size_t count) { + size_t len; + const char *str, *new_str; + yyjson_mut_arr_with_func({ + str = vals[i]; + if (yyjson_unlikely(!str)) return NULL; + len = strlen(str); + new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_strn(val, new_str, len); + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy( + yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { + size_t len; + const char *str, *new_str; + if (yyjson_unlikely(count > 0 && !lens)) return NULL; + yyjson_mut_arr_with_func({ + str = vals[i]; + if (yyjson_unlikely(!str)) return NULL; + len = lens[i]; + new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_strn(val, new_str, len); + }); +} + +#undef yyjson_mut_arr_with_func + + + +/*============================================================================== + * MARK: - Mutable JSON Array Modification API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_arr_insert(yyjson_mut_val *arr, + yyjson_mut_val *val, size_t idx) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(idx <= len)) { + unsafe_yyjson_set_len(arr, len + 1); + if (len == 0) { + val->next = val; + arr->uni.ptr = val; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + if (idx == len) { + prev->next = val; + val->next = next; + arr->uni.ptr = val; + } else { + while (idx-- > 0) { + prev = next; + next = next->next; + } + prev->next = val; + val->next = next; + } + } + return true; + } + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_append(yyjson_mut_val *arr, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + unsafe_yyjson_set_len(arr, len + 1); + if (len == 0) { + val->next = val; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + prev->next = val; + val->next = next; + } + arr->uni.ptr = val; + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_prepend(yyjson_mut_val *arr, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + unsafe_yyjson_set_len(arr, len + 1); + if (len == 0) { + val->next = val; + arr->uni.ptr = val; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + prev->next = val; + val->next = next; + } + return true; + } + return false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_replace(yyjson_mut_val *arr, + size_t idx, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && val)) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(idx < len)) { + if (yyjson_likely(len > 1)) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + while (idx-- > 0) { + prev = next; + next = next->next; + } + prev->next = val; + val->next = next->next; + if ((void *)next == arr->uni.ptr) arr->uni.ptr = val; + return next; + } else { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + val->next = val; + arr->uni.ptr = val; + return prev; + } + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove(yyjson_mut_val *arr, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(idx < len)) { + unsafe_yyjson_set_len(arr, len - 1); + if (yyjson_likely(len > 1)) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + while (idx-- > 0) { + prev = next; + next = next->next; + } + prev->next = next->next; + if ((void *)next == arr->uni.ptr) arr->uni.ptr = prev; + return next; + } else { + return ((yyjson_mut_val *)arr->uni.ptr); + } + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_first( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (len > 1) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + prev->next = next->next; + unsafe_yyjson_set_len(arr, len - 1); + return next; + } else if (len == 1) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + unsafe_yyjson_set_len(arr, 0); + return prev; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_remove_last( + yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_likely(len > 1)) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + yyjson_mut_val *next = prev->next; + unsafe_yyjson_set_len(arr, len - 1); + while (--len > 0) prev = prev->next; + prev->next = next; + next = (yyjson_mut_val *)arr->uni.ptr; + arr->uni.ptr = prev; + return next; + } else if (len == 1) { + yyjson_mut_val *prev = ((yyjson_mut_val *)arr->uni.ptr); + unsafe_yyjson_set_len(arr, 0); + return prev; + } + } + return NULL; +} + +yyjson_api_inline bool yyjson_mut_arr_remove_range(yyjson_mut_val *arr, + size_t _idx, size_t _len) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + yyjson_mut_val *prev, *next; + bool tail_removed; + size_t len = unsafe_yyjson_get_len(arr); + if (yyjson_unlikely(_idx + _len > len)) return false; + if (yyjson_unlikely(_len == 0)) return true; + unsafe_yyjson_set_len(arr, len - _len); + if (yyjson_unlikely(len == _len)) return true; + tail_removed = (_idx + _len == len); + prev = ((yyjson_mut_val *)arr->uni.ptr); + while (_idx-- > 0) prev = prev->next; + next = prev->next; + while (_len-- > 0) next = next->next; + prev->next = next; + if (yyjson_unlikely(tail_removed)) arr->uni.ptr = prev; + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_clear(yyjson_mut_val *arr) { + if (yyjson_likely(yyjson_mut_is_arr(arr))) { + unsafe_yyjson_set_len(arr, 0); + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_rotate(yyjson_mut_val *arr, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_arr(arr) && + unsafe_yyjson_get_len(arr) > idx)) { + yyjson_mut_val *val = (yyjson_mut_val *)arr->uni.ptr; + while (idx-- > 0) val = val->next; + arr->uni.ptr = (void *)val; + return true; + } + return false; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Array Modification Convenience API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_arr_add_val(yyjson_mut_val *arr, + yyjson_mut_val *val) { + return yyjson_mut_arr_append(arr, val); +} + +yyjson_api_inline bool yyjson_mut_arr_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_null(doc); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_true(doc); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_false(doc); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + bool _val) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_bool(doc, _val); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + uint64_t num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_uint(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_sint(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + int64_t num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_sint(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_float(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + float num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_float(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_double(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_double(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_real(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_str(doc, str); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, size_t len) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_strn(doc, str, len); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_strcpy(doc, str); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + const char *str, size_t len) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_strncpy(doc, str, len); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_arr(doc); + return yyjson_mut_arr_append(arr, val) ? val : NULL; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *arr) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_obj(doc); + return yyjson_mut_arr_append(arr, val) ? val : NULL; + } + return NULL; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Object API (Implementation) + *============================================================================*/ + +yyjson_api_inline size_t yyjson_mut_obj_size(yyjson_mut_val *obj) { + return yyjson_mut_is_obj(obj) ? unsafe_yyjson_get_len(obj) : 0; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_get(yyjson_mut_val *obj, + const char *key) { + return yyjson_mut_obj_getn(obj, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_getn(yyjson_mut_val *obj, + const char *_key, + size_t key_len) { + size_t len = yyjson_mut_obj_size(obj); + if (yyjson_likely(len && _key)) { + yyjson_mut_val *key = ((yyjson_mut_val *)obj->uni.ptr)->next->next; + while (len-- > 0) { + if (unsafe_yyjson_equals_strn(key, _key, key_len)) return key->next; + key = key->next->next; + } + } + return NULL; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Object Iterator API (Implementation) + *============================================================================*/ + +yyjson_api_inline bool yyjson_mut_obj_iter_init(yyjson_mut_val *obj, + yyjson_mut_obj_iter *iter) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && iter)) { + iter->idx = 0; + iter->max = unsafe_yyjson_get_len(obj); + iter->cur = iter->max ? (yyjson_mut_val *)obj->uni.ptr : NULL; + iter->pre = NULL; + iter->obj = obj; + return true; + } + if (iter) memset(iter, 0, sizeof(yyjson_mut_obj_iter)); + return false; +} + +yyjson_api_inline yyjson_mut_obj_iter yyjson_mut_obj_iter_with( + yyjson_mut_val *obj) { + yyjson_mut_obj_iter iter; + yyjson_mut_obj_iter_init(obj, &iter); + return iter; +} + +yyjson_api_inline bool yyjson_mut_obj_iter_has_next(yyjson_mut_obj_iter *iter) { + return iter ? iter->idx < iter->max : false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_next( + yyjson_mut_obj_iter *iter) { + if (iter && iter->idx < iter->max) { + yyjson_mut_val *key = iter->cur; + iter->pre = key; + iter->cur = key->next->next; + iter->idx++; + return iter->cur; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get_val( + yyjson_mut_val *key) { + return key ? key->next : NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_remove( + yyjson_mut_obj_iter *iter) { + if (yyjson_likely(iter && 0 < iter->idx && iter->idx <= iter->max)) { + yyjson_mut_val *prev = iter->pre; + yyjson_mut_val *cur = iter->cur; + yyjson_mut_val *next = cur->next->next; + if (yyjson_unlikely(iter->idx == iter->max)) iter->obj->uni.ptr = prev; + iter->idx--; + iter->max--; + unsafe_yyjson_set_len(iter->obj, iter->max); + prev->next->next = next; + iter->cur = prev; + return cur->next; + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_get( + yyjson_mut_obj_iter *iter, const char *key) { + return yyjson_mut_obj_iter_getn(iter, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_iter_getn( + yyjson_mut_obj_iter *iter, const char *key, size_t key_len) { + if (iter && key) { + size_t idx = 0; + size_t max = iter->max; + yyjson_mut_val *pre, *cur = iter->cur; + while (idx++ < max) { + pre = cur; + cur = cur->next->next; + if (unsafe_yyjson_equals_strn(cur, key, key_len)) { + iter->idx += idx; + if (iter->idx > max) iter->idx -= max + 1; + iter->pre = pre; + iter->cur = cur; + return cur->next; + } + } + } + return NULL; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Object Creation API (Implementation) + *============================================================================*/ + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj(yyjson_mut_doc *doc) { + if (yyjson_likely(doc)) { + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + if (yyjson_likely(val)) { + val->tag = YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE; + return val; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_str(yyjson_mut_doc *doc, + const char **keys, + const char **vals, + size_t count) { + if (yyjson_likely(doc && ((count > 0 && keys && vals) || (count == 0)))) { + yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2); + if (yyjson_likely(obj)) { + obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ; + if (count > 0) { + size_t i; + for (i = 0; i < count; i++) { + yyjson_mut_val *key = obj + (i * 2 + 1); + yyjson_mut_val *val = obj + (i * 2 + 2); + uint64_t key_len = (uint64_t)strlen(keys[i]); + uint64_t val_len = (uint64_t)strlen(vals[i]); + key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + key->uni.str = keys[i]; + val->uni.str = vals[i]; + key->next = val; + val->next = val + 1; + } + obj[count * 2].next = obj + 1; + obj->uni.ptr = obj + (count * 2 - 1); + } + return obj; + } + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_with_kv(yyjson_mut_doc *doc, + const char **pairs, + size_t count) { + if (yyjson_likely(doc && ((count > 0 && pairs) || (count == 0)))) { + yyjson_mut_val *obj = unsafe_yyjson_mut_val(doc, 1 + count * 2); + if (yyjson_likely(obj)) { + obj->tag = ((uint64_t)count << YYJSON_TAG_BIT) | YYJSON_TYPE_OBJ; + if (count > 0) { + size_t i; + for (i = 0; i < count; i++) { + yyjson_mut_val *key = obj + (i * 2 + 1); + yyjson_mut_val *val = obj + (i * 2 + 2); + const char *key_str = pairs[i * 2 + 0]; + const char *val_str = pairs[i * 2 + 1]; + uint64_t key_len = (uint64_t)strlen(key_str); + uint64_t val_len = (uint64_t)strlen(val_str); + key->tag = (key_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->tag = (val_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + key->uni.str = key_str; + val->uni.str = val_str; + key->next = val; + val->next = val + 1; + } + obj[count * 2].next = obj + 1; + obj->uni.ptr = obj + (count * 2 - 1); + } + return obj; + } + } + return NULL; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Object Modification API (Implementation) + *============================================================================*/ + +yyjson_api_inline void unsafe_yyjson_mut_obj_add(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val, + size_t len) { + if (yyjson_likely(len)) { + yyjson_mut_val *prev_val = ((yyjson_mut_val *)obj->uni.ptr)->next; + yyjson_mut_val *next_key = prev_val->next; + prev_val->next = key; + val->next = next_key; + } else { + val->next = key; + } + key->next = val; + obj->uni.ptr = (void *)key; + unsafe_yyjson_set_len(obj, len + 1); +} + +yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_obj_remove( + yyjson_mut_val *obj, const char *key, size_t key_len) { + size_t obj_len = unsafe_yyjson_get_len(obj); + if (obj_len) { + yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr; + yyjson_mut_val *cur_key = pre_key->next->next; + yyjson_mut_val *removed_item = NULL; + size_t i; + for (i = 0; i < obj_len; i++) { + if (unsafe_yyjson_equals_strn(cur_key, key, key_len)) { + if (!removed_item) removed_item = cur_key->next; + cur_key = cur_key->next->next; + pre_key->next->next = cur_key; + if (i + 1 == obj_len) obj->uni.ptr = pre_key; + i--; + obj_len--; + } else { + pre_key = cur_key; + cur_key = cur_key->next->next; + } + } + unsafe_yyjson_set_len(obj, obj_len); + return removed_item; + } else { + return NULL; + } +} + +yyjson_api_inline bool unsafe_yyjson_mut_obj_replace(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + size_t key_len = unsafe_yyjson_get_len(key); + size_t obj_len = unsafe_yyjson_get_len(obj); + if (obj_len) { + yyjson_mut_val *pre_key = (yyjson_mut_val *)obj->uni.ptr; + yyjson_mut_val *cur_key = pre_key->next->next; + size_t i; + for (i = 0; i < obj_len; i++) { + if (unsafe_yyjson_equals_strn(cur_key, key->uni.str, key_len)) { + cur_key->next->tag = val->tag; + cur_key->next->uni.u64 = val->uni.u64; + return true; + } else { + cur_key = cur_key->next->next; + } + } + } + return false; +} + +yyjson_api_inline void unsafe_yyjson_mut_obj_rotate(yyjson_mut_val *obj, + size_t idx) { + yyjson_mut_val *key = (yyjson_mut_val *)obj->uni.ptr; + while (idx-- > 0) key = key->next->next; + obj->uni.ptr = (void *)key; +} + +yyjson_api_inline bool yyjson_mut_obj_add(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + yyjson_mut_is_str(key) && val)) { + unsafe_yyjson_mut_obj_add(obj, key, val, unsafe_yyjson_get_len(obj)); + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_obj_put(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + bool replaced = false; + size_t key_len; + yyjson_mut_obj_iter iter; + yyjson_mut_val *cur_key; + if (yyjson_unlikely(!yyjson_mut_is_obj(obj) || + !yyjson_mut_is_str(key))) return false; + key_len = unsafe_yyjson_get_len(key); + yyjson_mut_obj_iter_init(obj, &iter); + while ((cur_key = yyjson_mut_obj_iter_next(&iter)) != 0) { + if (unsafe_yyjson_equals_strn(cur_key, key->uni.str, key_len)) { + if (!replaced && val) { + replaced = true; + val->next = cur_key->next->next; + cur_key->next = val; + } else { + yyjson_mut_obj_iter_remove(&iter); + } + } + } + if (!replaced && val) unsafe_yyjson_mut_obj_add(obj, key, val, iter.max); + return true; +} + +yyjson_api_inline bool yyjson_mut_obj_insert(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + yyjson_mut_is_str(key) && val)) { + size_t len = unsafe_yyjson_get_len(obj); + if (yyjson_likely(len >= idx)) { + if (len > idx) { + void *ptr = obj->uni.ptr; + unsafe_yyjson_mut_obj_rotate(obj, idx); + unsafe_yyjson_mut_obj_add(obj, key, val, len); + obj->uni.ptr = ptr; + } else { + unsafe_yyjson_mut_obj_add(obj, key, val, len); + } + return true; + } + } + return false; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove(yyjson_mut_val *obj, + yyjson_mut_val *key) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && yyjson_mut_is_str(key))) { + return unsafe_yyjson_mut_obj_remove(obj, key->uni.str, + unsafe_yyjson_get_len(key)); + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_key( + yyjson_mut_val *obj, const char *key) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) { + size_t key_len = strlen(key); + return unsafe_yyjson_mut_obj_remove(obj, key, key_len); + } + return NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_keyn( + yyjson_mut_val *obj, const char *key, size_t key_len) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && key)) { + return unsafe_yyjson_mut_obj_remove(obj, key, key_len); + } + return NULL; +} + +yyjson_api_inline bool yyjson_mut_obj_clear(yyjson_mut_val *obj) { + if (yyjson_likely(yyjson_mut_is_obj(obj))) { + unsafe_yyjson_set_len(obj, 0); + return true; + } + return false; +} + +yyjson_api_inline bool yyjson_mut_obj_replace(yyjson_mut_val *obj, + yyjson_mut_val *key, + yyjson_mut_val *val) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + yyjson_mut_is_str(key) && val)) { + return unsafe_yyjson_mut_obj_replace(obj, key, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj, + size_t idx) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && + unsafe_yyjson_get_len(obj) > idx)) { + unsafe_yyjson_mut_obj_rotate(obj, idx); + return true; + } + return false; +} + + + +/*============================================================================== + * MARK: - Mutable JSON Object Modification Convenience API (Implementation) + *============================================================================*/ + +#define yyjson_mut_obj_add_func(func) \ + if (yyjson_likely(doc && yyjson_mut_is_obj(obj) && _key)) { \ + yyjson_mut_val *key = unsafe_yyjson_mut_val(doc, 2); \ + if (yyjson_likely(key)) { \ + size_t len = unsafe_yyjson_get_len(obj); \ + yyjson_mut_val *val = key + 1; \ + size_t key_len = strlen(_key); \ + bool noesc = unsafe_yyjson_is_str_noesc(_key, key_len); \ + key->tag = YYJSON_TYPE_STR; \ + key->tag |= noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; \ + key->tag |= (uint64_t)strlen(_key) << YYJSON_TAG_BIT; \ + key->uni.str = _key; \ + func \ + unsafe_yyjson_mut_obj_add(obj, key, val, len); \ + return true; \ + } \ + } \ + return false + +yyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_null(val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, true); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, false); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + bool _val) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, _val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + uint64_t _val) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_uint(val, _val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + int64_t _val) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_sint(val, _val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + int64_t _val) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_sint(val, _val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_float(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + float _val) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_float(val, _val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_double(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + double _val) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_double(val, _val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + double _val) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_real(val, _val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + size_t val_len = strlen(_val); + bool val_noesc = unsafe_yyjson_is_str_noesc(_val, val_len); + val->tag = ((uint64_t)strlen(_val) << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->tag |= val_noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; + val->uni.str = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val, + size_t _len) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + val->uni.str = _val; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_strcpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + size_t _len = strlen(_val); + val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len); + if (yyjson_unlikely(!val->uni.str)) return false; + val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_strncpy(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + const char *_val, + size_t _len) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val->uni.str = unsafe_yyjson_mut_strncpy(doc, _val, _len); + if (yyjson_unlikely(!val->uni.str)) return false; + val->tag = ((uint64_t)_len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_arr(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_val *key = yyjson_mut_str(doc, _key); + yyjson_mut_val *val = yyjson_mut_arr(doc); + return yyjson_mut_obj_add(obj, key, val) ? val : NULL; +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_add_obj(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key) { + yyjson_mut_val *key = yyjson_mut_str(doc, _key); + yyjson_mut_val *val = yyjson_mut_obj(doc); + return yyjson_mut_obj_add(obj, key, val) ? val : NULL; +} + +yyjson_api_inline bool yyjson_mut_obj_add_val(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + yyjson_mut_val *_val) { + if (yyjson_unlikely(!_val)) return false; + yyjson_mut_obj_add_func({ + val = _val; + }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_str(yyjson_mut_val *obj, + const char *key) { + return yyjson_mut_obj_remove_strn(obj, key, key ? strlen(key) : 0); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_obj_remove_strn( + yyjson_mut_val *obj, const char *_key, size_t _len) { + if (yyjson_likely(yyjson_mut_is_obj(obj) && _key)) { + yyjson_mut_val *key; + yyjson_mut_obj_iter iter; + yyjson_mut_val *val_removed = NULL; + yyjson_mut_obj_iter_init(obj, &iter); + while ((key = yyjson_mut_obj_iter_next(&iter)) != NULL) { + if (unsafe_yyjson_equals_strn(key, _key, _len)) { + if (!val_removed) val_removed = key->next; + yyjson_mut_obj_iter_remove(&iter); + } + } + return val_removed; + } + return NULL; +} + +yyjson_api_inline bool yyjson_mut_obj_rename_key(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + const char *new_key) { + if (!key || !new_key) return false; + return yyjson_mut_obj_rename_keyn(doc, obj, key, strlen(key), + new_key, strlen(new_key)); +} + +yyjson_api_inline bool yyjson_mut_obj_rename_keyn(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, + size_t len, + const char *new_key, + size_t new_len) { + char *cpy_key = NULL; + yyjson_mut_val *old_key; + yyjson_mut_obj_iter iter; + if (!doc || !obj || !key || !new_key) return false; + yyjson_mut_obj_iter_init(obj, &iter); + while ((old_key = yyjson_mut_obj_iter_next(&iter))) { + if (unsafe_yyjson_equals_strn((void *)old_key, key, len)) { + if (!cpy_key) { + cpy_key = unsafe_yyjson_mut_strncpy(doc, new_key, new_len); + if (!cpy_key) return false; + } + yyjson_mut_set_strn(old_key, cpy_key, new_len); + } + } + return cpy_key != NULL; +} + + + +#if !defined(YYJSON_DISABLE_UTILS) || !YYJSON_DISABLE_UTILS + +/*============================================================================== + * MARK: - JSON Pointer API (Implementation) + *============================================================================*/ + +#define yyjson_ptr_set_err(_code, _msg) do { \ + if (err) { \ + err->code = YYJSON_PTR_ERR_##_code; \ + err->msg = _msg; \ + err->pos = 0; \ + } \ +} while(false) + +/* require: val != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_val *unsafe_yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t len, + yyjson_ptr_err *err); + +/* require: val != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/* require: val/new_val/doc != NULL, *ptr == '/', len > 0 */ +yyjson_api bool unsafe_yyjson_mut_ptr_putx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, bool insert_new, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +/* require: val/err != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err); + +/* require: val/err != NULL, *ptr == '/', len > 0 */ +yyjson_api yyjson_mut_val *unsafe_yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err); + +yyjson_api_inline yyjson_val *yyjson_doc_ptr_get(yyjson_doc *doc, + const char *ptr) { + if (yyjson_unlikely(!ptr)) return NULL; + return yyjson_doc_ptr_getn(doc, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getn(yyjson_doc *doc, + const char *ptr, size_t len) { + return yyjson_doc_ptr_getx(doc, ptr, len, NULL); +} + +yyjson_api_inline yyjson_val *yyjson_doc_ptr_getx(yyjson_doc *doc, + const char *ptr, size_t len, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return doc->root; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_ptr_getx(doc->root, ptr, len, err); +} + +yyjson_api_inline yyjson_val *yyjson_ptr_get(yyjson_val *val, + const char *ptr) { + if (yyjson_unlikely(!ptr)) return NULL; + return yyjson_ptr_getn(val, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_val *yyjson_ptr_getn(yyjson_val *val, + const char *ptr, size_t len) { + return yyjson_ptr_getx(val, ptr, len, NULL); +} + +yyjson_api_inline yyjson_val *yyjson_ptr_getx(yyjson_val *val, + const char *ptr, size_t len, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (yyjson_unlikely(!val || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return val; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_ptr_getx(val, ptr, len, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_get(yyjson_mut_doc *doc, + const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_doc_ptr_getn(doc, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getn(yyjson_mut_doc *doc, + const char *ptr, + size_t len) { + return yyjson_mut_doc_ptr_getx(doc, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_getx(yyjson_mut_doc *doc, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return doc->root; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_getx(doc->root, ptr, len, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_get(yyjson_mut_val *val, + const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_ptr_getn(val, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getn(yyjson_mut_val *val, + const char *ptr, + size_t len) { + return yyjson_mut_ptr_getx(val, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_getx(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + return val; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_getx(val, ptr, len, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_add(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_doc_ptr_addn(doc, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_addn(yyjson_mut_doc *doc, + const char *ptr, + size_t len, + yyjson_mut_val *new_val) { + return yyjson_mut_doc_ptr_addx(doc, ptr, len, new_val, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_addx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr || !new_val)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + if (doc->root) { + yyjson_ptr_set_err(SET_ROOT, "cannot set document's root"); + return false; + } else { + doc->root = new_val; + return true; + } + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + if (yyjson_unlikely(!doc->root && !create_parent)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return false; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_mut_val *root = yyjson_mut_obj(doc); + if (yyjson_unlikely(!root)) { + yyjson_ptr_set_err(MEMORY_ALLOCATION, "failed to create value"); + return false; + } + if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc, + create_parent, true, ctx, err)) { + doc->root = root; + return true; + } + return false; + } + return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc, + create_parent, true, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_ptr_add(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_ptr_addn(val, ptr, strlen(ptr), new_val, doc); +} + +yyjson_api_inline bool yyjson_mut_ptr_addn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + return yyjson_mut_ptr_addx(val, ptr, len, new_val, doc, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_ptr_addx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr || !new_val || !doc)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return false; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val, + doc, create_parent, true, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_set(yyjson_mut_doc *doc, + const char *ptr, + yyjson_mut_val *new_val) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_doc_ptr_setn(doc, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_setn(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val) { + return yyjson_mut_doc_ptr_setx(doc, ptr, len, new_val, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_doc_ptr_setx(yyjson_mut_doc *doc, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + if (ctx) ctx->old = doc->root; + doc->root = new_val; + return true; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + if (!new_val) { + if (!doc->root) { + yyjson_ptr_set_err(RESOLVE, "JSON pointer cannot be resolved"); + return false; + } + return !!unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err); + } + if (yyjson_unlikely(!doc->root && !create_parent)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return false; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_mut_val *root = yyjson_mut_obj(doc); + if (yyjson_unlikely(!root)) { + yyjson_ptr_set_err(MEMORY_ALLOCATION, "failed to create value"); + return false; + } + if (unsafe_yyjson_mut_ptr_putx(root, ptr, len, new_val, doc, + create_parent, false, ctx, err)) { + doc->root = root; + return true; + } + return false; + } + return unsafe_yyjson_mut_ptr_putx(doc->root, ptr, len, new_val, doc, + create_parent, false, ctx, err); +} + +yyjson_api_inline bool yyjson_mut_ptr_set(yyjson_mut_val *val, + const char *ptr, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + if (yyjson_unlikely(!ptr)) return false; + return yyjson_mut_ptr_setn(val, ptr, strlen(ptr), new_val, doc); +} + +yyjson_api_inline bool yyjson_mut_ptr_setn(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc) { + return yyjson_mut_ptr_setx(val, ptr, len, new_val, doc, true, NULL, NULL); +} + +yyjson_api_inline bool yyjson_mut_ptr_setx(yyjson_mut_val *val, + const char *ptr, size_t len, + yyjson_mut_val *new_val, + yyjson_mut_doc *doc, + bool create_parent, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr || !doc)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return false; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return false; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return false; + } + if (!new_val) { + return !!unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err); + } + return unsafe_yyjson_mut_ptr_putx(val, ptr, len, new_val, doc, + create_parent, false, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replace( + yyjson_mut_doc *doc, const char *ptr, yyjson_mut_val *new_val) { + if (!ptr) return NULL; + return yyjson_mut_doc_ptr_replacen(doc, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacen( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val) { + return yyjson_mut_doc_ptr_replacex(doc, ptr, len, new_val, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_replacex( + yyjson_mut_doc *doc, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr || !new_val)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_mut_val *root = doc->root; + if (yyjson_unlikely(!root)) { + yyjson_ptr_set_err(RESOLVE, "JSON pointer cannot be resolved"); + return NULL; + } + if (ctx) ctx->old = root; + doc->root = new_val; + return root; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_replacex(doc->root, ptr, len, new_val, + ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replace( + yyjson_mut_val *val, const char *ptr, yyjson_mut_val *new_val) { + if (!ptr) return NULL; + return yyjson_mut_ptr_replacen(val, ptr, strlen(ptr), new_val); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacen( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val) { + return yyjson_mut_ptr_replacex(val, ptr, len, new_val, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_replacex( + yyjson_mut_val *val, const char *ptr, size_t len, yyjson_mut_val *new_val, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr || !new_val)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return NULL; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_replacex(val, ptr, len, new_val, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_remove( + yyjson_mut_doc *doc, const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_doc_ptr_removen(doc, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removen( + yyjson_mut_doc *doc, const char *ptr, size_t len) { + return yyjson_mut_doc_ptr_removex(doc, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_ptr_removex( + yyjson_mut_doc *doc, const char *ptr, size_t len, + yyjson_ptr_ctx *ctx, yyjson_ptr_err *err) { + + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!doc || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(!doc->root)) { + yyjson_ptr_set_err(NULL_ROOT, "document's root is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_mut_val *root = doc->root; + if (ctx) ctx->old = root; + doc->root = NULL; + return root; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_removex(doc->root, ptr, len, ctx, err); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_remove(yyjson_mut_val *val, + const char *ptr) { + if (!ptr) return NULL; + return yyjson_mut_ptr_removen(val, ptr, strlen(ptr)); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removen(yyjson_mut_val *val, + const char *ptr, + size_t len) { + return yyjson_mut_ptr_removex(val, ptr, len, NULL, NULL); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_ptr_removex(yyjson_mut_val *val, + const char *ptr, + size_t len, + yyjson_ptr_ctx *ctx, + yyjson_ptr_err *err) { + yyjson_ptr_set_err(NONE, NULL); + if (ctx) memset(ctx, 0, sizeof(*ctx)); + + if (yyjson_unlikely(!val || !ptr)) { + yyjson_ptr_set_err(PARAMETER, "input parameter is NULL"); + return NULL; + } + if (yyjson_unlikely(len == 0)) { + yyjson_ptr_set_err(SET_ROOT, "cannot set root"); + return NULL; + } + if (yyjson_unlikely(*ptr != '/')) { + yyjson_ptr_set_err(SYNTAX, "no prefix '/'"); + return NULL; + } + return unsafe_yyjson_mut_ptr_removex(val, ptr, len, ctx, err); +} + +yyjson_api_inline bool yyjson_ptr_ctx_append(yyjson_ptr_ctx *ctx, + yyjson_mut_val *key, + yyjson_mut_val *val) { + yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val; + if (!ctx || !ctx->ctn || !val) return false; + ctn = ctx->ctn; + + if (yyjson_mut_is_obj(ctn)) { + if (!key) return false; + key->next = val; + pre_key = ctx->pre; + if (unsafe_yyjson_get_len(ctn) == 0) { + val->next = key; + ctn->uni.ptr = key; + ctx->pre = key; + } else if (!pre_key) { + pre_key = (yyjson_mut_val *)ctn->uni.ptr; + pre_val = pre_key->next; + val->next = pre_val->next; + pre_val->next = key; + ctn->uni.ptr = key; + ctx->pre = pre_key; + } else { + cur_key = pre_key->next->next; + cur_val = cur_key->next; + val->next = cur_val->next; + cur_val->next = key; + if (ctn->uni.ptr == cur_key) ctn->uni.ptr = key; + ctx->pre = cur_key; + } + } else { + pre_val = ctx->pre; + if (unsafe_yyjson_get_len(ctn) == 0) { + val->next = val; + ctn->uni.ptr = val; + ctx->pre = val; + } else if (!pre_val) { + pre_val = (yyjson_mut_val *)ctn->uni.ptr; + val->next = pre_val->next; + pre_val->next = val; + ctn->uni.ptr = val; + ctx->pre = pre_val; + } else { + cur_val = pre_val->next; + val->next = cur_val->next; + cur_val->next = val; + if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val; + ctx->pre = cur_val; + } + } + unsafe_yyjson_inc_len(ctn); + return true; +} + +yyjson_api_inline bool yyjson_ptr_ctx_replace(yyjson_ptr_ctx *ctx, + yyjson_mut_val *val) { + yyjson_mut_val *ctn, *pre_key, *cur_key, *pre_val, *cur_val; + if (!ctx || !ctx->ctn || !ctx->pre || !val) return false; + ctn = ctx->ctn; + if (yyjson_mut_is_obj(ctn)) { + pre_key = ctx->pre; + pre_val = pre_key->next; + cur_key = pre_val->next; + cur_val = cur_key->next; + /* replace current value */ + cur_key->next = val; + val->next = cur_val->next; + ctx->old = cur_val; + } else { + pre_val = ctx->pre; + cur_val = pre_val->next; + /* replace current value */ + if (pre_val != cur_val) { + val->next = cur_val->next; + pre_val->next = val; + if (ctn->uni.ptr == cur_val) ctn->uni.ptr = val; + } else { + val->next = val; + ctn->uni.ptr = val; + ctx->pre = val; + } + ctx->old = cur_val; + } + return true; +} + +yyjson_api_inline bool yyjson_ptr_ctx_remove(yyjson_ptr_ctx *ctx) { + yyjson_mut_val *ctn, *pre_key, *pre_val, *cur_key, *cur_val; + size_t len; + if (!ctx || !ctx->ctn || !ctx->pre) return false; + ctn = ctx->ctn; + if (yyjson_mut_is_obj(ctn)) { + pre_key = ctx->pre; + pre_val = pre_key->next; + cur_key = pre_val->next; + cur_val = cur_key->next; + /* remove current key-value */ + pre_val->next = cur_val->next; + if (ctn->uni.ptr == cur_key) ctn->uni.ptr = pre_key; + ctx->pre = NULL; + ctx->old = cur_val; + } else { + pre_val = ctx->pre; + cur_val = pre_val->next; + /* remove current key-value */ + pre_val->next = cur_val->next; + if (ctn->uni.ptr == cur_val) ctn->uni.ptr = pre_val; + ctx->pre = NULL; + ctx->old = cur_val; + } + len = unsafe_yyjson_get_len(ctn) - 1; + if (len == 0) ctn->uni.ptr = NULL; + unsafe_yyjson_set_len(ctn, len); + return true; +} + +#undef yyjson_ptr_set_err + + + +/*============================================================================== + * MARK: - JSON Value at Pointer API (Implementation) + *============================================================================*/ + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type bool. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_bool( + yyjson_val *root, const char *ptr, bool *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_bool(val)) { + *value = unsafe_yyjson_get_bool(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is an integer + that fits in `uint64_t`. Returns true if successful, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_uint( + yyjson_val *root, const char *ptr, uint64_t *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && val) { + uint64_t ret = val->uni.u64; + if (unsafe_yyjson_is_uint(val) || + (unsafe_yyjson_is_sint(val) && !(ret >> 63))) { + *value = ret; + return true; + } + } + return false; +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is an integer + that fits in `int64_t`. Returns true if successful, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_sint( + yyjson_val *root, const char *ptr, int64_t *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && val) { + int64_t ret = val->uni.i64; + if (unsafe_yyjson_is_sint(val) || + (unsafe_yyjson_is_uint(val) && ret >= 0)) { + *value = ret; + return true; + } + } + return false; +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type real. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_real( + yyjson_val *root, const char *ptr, double *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_real(val)) { + *value = unsafe_yyjson_get_real(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type sint, + uint or real. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_num( + yyjson_val *root, const char *ptr, double *value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_num(val)) { + *value = unsafe_yyjson_get_num(val); + return true; + } else { + return false; + } +} + +/** + Set provided `value` if the JSON Pointer (RFC 6901) exists and is type string. + Returns true if value at `ptr` exists and is the correct type, otherwise false. + */ +yyjson_api_inline bool yyjson_ptr_get_str( + yyjson_val *root, const char *ptr, const char **value) { + yyjson_val *val = yyjson_ptr_get(root, ptr); + if (value && yyjson_is_str(val)) { + *value = unsafe_yyjson_get_str(val); + return true; + } else { + return false; + } +} + + + +/*============================================================================== + * MARK: - Deprecated + *============================================================================*/ + +/** @deprecated renamed to `yyjson_doc_ptr_get` */ +yyjson_deprecated("renamed to yyjson_doc_ptr_get") +yyjson_api_inline yyjson_val *yyjson_doc_get_pointer(yyjson_doc *doc, + const char *ptr) { + return yyjson_doc_ptr_get(doc, ptr); +} + +/** @deprecated renamed to `yyjson_doc_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_doc_ptr_getn") +yyjson_api_inline yyjson_val *yyjson_doc_get_pointern(yyjson_doc *doc, + const char *ptr, + size_t len) { + return yyjson_doc_ptr_getn(doc, ptr, len); +} + +/** @deprecated renamed to `yyjson_mut_doc_ptr_get` */ +yyjson_deprecated("renamed to yyjson_mut_doc_ptr_get") +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointer( + yyjson_mut_doc *doc, const char *ptr) { + return yyjson_mut_doc_ptr_get(doc, ptr); +} + +/** @deprecated renamed to `yyjson_mut_doc_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_mut_doc_ptr_getn") +yyjson_api_inline yyjson_mut_val *yyjson_mut_doc_get_pointern( + yyjson_mut_doc *doc, const char *ptr, size_t len) { + return yyjson_mut_doc_ptr_getn(doc, ptr, len); +} + +/** @deprecated renamed to `yyjson_ptr_get` */ +yyjson_deprecated("renamed to yyjson_ptr_get") +yyjson_api_inline yyjson_val *yyjson_get_pointer(yyjson_val *val, + const char *ptr) { + return yyjson_ptr_get(val, ptr); +} + +/** @deprecated renamed to `yyjson_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_ptr_getn") +yyjson_api_inline yyjson_val *yyjson_get_pointern(yyjson_val *val, + const char *ptr, + size_t len) { + return yyjson_ptr_getn(val, ptr, len); +} + +/** @deprecated renamed to `yyjson_mut_ptr_get` */ +yyjson_deprecated("renamed to yyjson_mut_ptr_get") +yyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointer(yyjson_mut_val *val, + const char *ptr) { + return yyjson_mut_ptr_get(val, ptr); +} + +/** @deprecated renamed to `yyjson_mut_ptr_getn` */ +yyjson_deprecated("renamed to yyjson_mut_ptr_getn") +yyjson_api_inline yyjson_mut_val *yyjson_mut_get_pointern(yyjson_mut_val *val, + const char *ptr, + size_t len) { + return yyjson_mut_ptr_getn(val, ptr, len); +} + +/** @deprecated renamed to `yyjson_mut_ptr_getn` */ +yyjson_deprecated("renamed to unsafe_yyjson_ptr_getn") +yyjson_api_inline yyjson_val *unsafe_yyjson_get_pointer(yyjson_val *val, + const char *ptr, + size_t len) { + yyjson_ptr_err err; + return unsafe_yyjson_ptr_getx(val, ptr, len, &err); +} + +/** @deprecated renamed to `unsafe_yyjson_mut_ptr_getx` */ +yyjson_deprecated("renamed to unsafe_yyjson_mut_ptr_getx") +yyjson_api_inline yyjson_mut_val *unsafe_yyjson_mut_get_pointer( + yyjson_mut_val *val, const char *ptr, size_t len) { + yyjson_ptr_err err; + return unsafe_yyjson_mut_ptr_getx(val, ptr, len, NULL, &err); +} + +#endif /* YYJSON_DISABLE_UTILS */ + + + +/*============================================================================== + * MARK: - Compiler Hint End + *============================================================================*/ + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) +# pragma GCC diagnostic pop +# endif +#elif defined(_MSC_VER) +# pragma warning(pop) +#endif /* warning suppress end */ + +#ifdef __cplusplus +} +#endif /* extern "C" end */ + +#endif /* YYJSON_H */ From ed63ffa29378ccf829024aa8d480ceab36cce5a3 Mon Sep 17 00:00:00 2001 From: nook24 Date: Sat, 29 Aug 2026 19:19:10 +0200 Subject: [PATCH 28/28] parse incoming messages with yyjson and drop json-c The previous commit moved message building to yyjson but left json-c in place for the worker command path, which meant carrying two JSON libraries. Measuring the parse side says there is no reason to. Parse plus extraction of every field ParseCheckResult() reads, both libraries doing the same work and returning the same values: single command 371 B json-c 2.24us yyjson 0.18us -92% bulk of 50 18631 B json-c 121.12us yyjson 7.51us -94% bulk of 100 37231 B json-c 239.24us yyjson 15.02us -94% The sizes are the ones that actually arrive: single messages, and bulk messages of 50 to 100. So the whole path moves over. json_tokener_parse() becomes yyjson_read(), the tree walk uses yyjson_obj_get() and yyjson_obj_foreach(), and IMessageHandler::ProcessMessage takes a yyjson_val. json-c is gone from the sources, from meson.build and from the test build; the module no longer links against it. The tests move with it. tests/bench.cpp loses its SPACED vs PLAIN comparison, which measured a json-c output option that no longer exists in the code path - yyjson always writes minified. One test deserves a note: the perf data case asserted that invalid utf8 had been converted, and used to lean on json-c refusing to build a string from invalid utf8. It now proves the same thing by round tripping the message through the parser and comparing the string, which does not depend on a particular library's behaviour. It passes. Verified against naemon with 5000 hosts and 100000 services: messages keep their shape (6 fields in the envelope, 45 in servicestatus), no errors logged, RSS flat at 198MB over 12 minutes, 100% of services checked within their interval at 1667 checks/s, mean check latency 0.05s. Unit tests: 75 cases, 432 assertions, all passing. Signed-off-by: nook24 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WY8mbGLNkt5eQfc57cTnZ5 --- meson.build | 1 - src/MessageHandler/IMessageHandler.h | 4 +- src/MessageHandler/MessageHandler.h | 136 ++++++++++++++------------- src/NagiosObject.h | 1 - src/meson.build | 2 +- tests/bench.cpp | 83 +++++----------- tests/meson.build | 2 +- tests/test_messageformat.cpp | 112 ++++++++++++---------- tests/test_support.h | 4 +- 9 files changed, 161 insertions(+), 184 deletions(-) diff --git a/meson.build b/meson.build index 155f152..359faf5 100644 --- a/meson.build +++ b/meson.build @@ -9,7 +9,6 @@ project('statusengine-broker', ['cpp', 'c'], uchardet_dep = dependency('uchardet') gearmand_dep = dependency('gearmand', required: get_option('gearman')) librabbitmq_dep = dependency('librabbitmq', required: get_option('rabbitmq')) -jsonc_dep = dependency('json-c') if get_option('nagios') nagios_dep = declare_dependency(include_directories: include_directories(get_option('nagios_include_dir'))).as_system('system') diff --git a/src/MessageHandler/IMessageHandler.h b/src/MessageHandler/IMessageHandler.h index 9e84119..af76b11 100644 --- a/src/MessageHandler/IMessageHandler.h +++ b/src/MessageHandler/IMessageHandler.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "yyjson.h" #include #include "Queue.h" @@ -13,7 +13,7 @@ namespace statusengine { virtual ~IMessageHandler() = default; virtual void ProcessMessage(WorkerQueue workerQueue, const std::string &message) = 0; - virtual void ProcessMessage(WorkerQueue workerQueue, json_object *obj) = 0; + virtual void ProcessMessage(WorkerQueue workerQueue, yyjson_val *obj) = 0; virtual bool Connect() = 0; virtual bool Worker(unsigned long &counter) = 0; diff --git a/src/MessageHandler/MessageHandler.h b/src/MessageHandler/MessageHandler.h index b31d791..a0d599a 100644 --- a/src/MessageHandler/MessageHandler.h +++ b/src/MessageHandler/MessageHandler.h @@ -22,12 +22,12 @@ namespace statusengine { /** * Copy a json string into a buffer owned by the C++ side. Release it with delete[]. */ - inline static char *get_json_string(json_object *obj) { - auto jsonChars = json_object_get_string(obj); + inline static char *get_json_string(yyjson_val *obj) { + auto jsonChars = yyjson_get_str(obj); if (jsonChars == nullptr) { return nullptr; } - auto jsonCharsLen = json_object_get_string_len(obj); + auto jsonCharsLen = yyjson_get_len(obj); char *chars = new char[jsonCharsLen + 1]; std::memcpy(chars, jsonChars, jsonCharsLen); chars[jsonCharsLen] = 0; // set last byte to zero @@ -39,44 +39,44 @@ namespace statusengine { * is stored in a check_result has to be allocated this way: free_check_result() * releases those strings with free(), which must not be paired with new[]. */ - inline static char *get_json_string_c(json_object *obj) { - auto jsonChars = json_object_get_string(obj); + inline static char *get_json_string_c(yyjson_val *obj) { + auto jsonChars = yyjson_get_str(obj); if (jsonChars == nullptr) { return nullptr; } - return strndup(jsonChars, json_object_get_string_len(obj)); + return strndup(jsonChars, yyjson_get_len(obj)); } void ProcessMessage(WorkerQueue workerQueue, const std::string &message) override { - json_object *obj = json_tokener_parse(message.c_str()); - if (obj == nullptr) { + yyjson_doc *doc = yyjson_read(message.c_str(), message.length(), 0); + if (doc == nullptr) { se->Log() << "Received non-json string '" << message << "'. Ignoring..." << LogLevel::Warning; } else { - ProcessMessage(workerQueue, obj); - json_object_put(obj); + ProcessMessage(workerQueue, yyjson_doc_get_root(doc)); + yyjson_doc_free(doc); } } - void ProcessMessage(WorkerQueue workerQueue, json_object *obj) override { + void ProcessMessage(WorkerQueue workerQueue, yyjson_val *obj) override { if (workerQueue == WorkerQueue::OCHP) { - json_object *messages; - if(json_object_object_get_ex(obj, "messages", &messages)) { - if (!json_object_is_type(messages, json_type_array)) { + yyjson_val *messages; + if((messages = yyjson_obj_get(obj, "messages")) != nullptr) { + if (!yyjson_is_arr(messages)) { se->Log() << "OCHP::messages is not an array. Ignoring..." << LogLevel::Warning; } else { - long unsigned int arrLen = json_object_array_length(messages); + long unsigned int arrLen = yyjson_arr_size(messages); for (long unsigned int i = 0; i < arrLen; i++) { - json_object *arrObj = json_object_array_get_idx(messages, i); + yyjson_val *arrObj = yyjson_arr_get(messages, i); ProcessMessage(WorkerQueue::OCHP, arrObj); } } } else { - json_object *hostcheck; - if(json_object_object_get_ex(obj, "hostcheck", &hostcheck)) { + yyjson_val *hostcheck; + if((hostcheck = yyjson_obj_get(obj, "hostcheck")) != nullptr) { ParseCheckResult(hostcheck); } else { @@ -86,22 +86,22 @@ namespace statusengine { } } else if (workerQueue == WorkerQueue::OCSP) { - json_object *messages; - if(json_object_object_get_ex(obj, "messages", &messages)) { - if (!json_object_is_type(messages, json_type_array)) { + yyjson_val *messages; + if((messages = yyjson_obj_get(obj, "messages")) != nullptr) { + if (!yyjson_is_arr(messages)) { se->Log() << "OCSP::messages is not an array. Ignoring..." << LogLevel::Warning; } else { - long unsigned int arrLen = json_object_array_length(messages); + long unsigned int arrLen = yyjson_arr_size(messages); for (long unsigned int i = 0; i < arrLen; i++) { - json_object *arrObj = json_object_array_get_idx(messages, i); + yyjson_val *arrObj = yyjson_arr_get(messages, i); ProcessMessage(WorkerQueue::OCSP, arrObj); } } } else { - json_object *servicecheck; - if(json_object_object_get_ex(obj, "servicecheck", &servicecheck)) { + yyjson_val *servicecheck; + if((servicecheck = yyjson_obj_get(obj, "servicecheck")) != nullptr) { ParseCheckResult(servicecheck); } else { @@ -112,13 +112,15 @@ namespace statusengine { } else if (workerQueue == WorkerQueue::Command) { std::string command; - json_object *data = nullptr; + yyjson_val *data = nullptr; bool haveCommand = false, haveData = false, haveList = false; - json_object_object_foreach(obj, cKey, jsonValue) { - std::string jsonKey(cKey); + size_t objIdx, objMax; + yyjson_val *jsonKeyVal, *jsonValue; + yyjson_obj_foreach(obj, objIdx, objMax, jsonKeyVal, jsonValue) { + std::string jsonKey(yyjson_get_str(jsonKeyVal), yyjson_get_len(jsonKeyVal)); if (jsonKey.compare("Command") == 0) { - command = std::string(json_object_get_string(jsonValue), json_object_get_string_len(jsonValue)); + command = std::string(yyjson_get_str(jsonValue), yyjson_get_len(jsonValue)); haveCommand = true; } else if (jsonKey.compare("Data") == 0) { @@ -126,13 +128,13 @@ namespace statusengine { haveData = true; } else if (jsonKey.compare("messages") == 0) { - if (!json_object_is_type(jsonValue, json_type_array)) { + if (!yyjson_is_arr(jsonValue)) { se->Log() << "messages doesn't contain an array. Ignoring..." << LogLevel::Warning; } else { - long unsigned int arrLen = json_object_array_length(jsonValue); + long unsigned int arrLen = yyjson_arr_size(jsonValue); for (long unsigned int i = 0; i < arrLen; i++) { - json_object *arrObj = json_object_array_get_idx(jsonValue, i); + yyjson_val *arrObj = yyjson_arr_get(jsonValue, i); ProcessMessage(WorkerQueue::Command, arrObj); } } @@ -209,7 +211,7 @@ namespace statusengine { return fullOutput; } - void ParseCheckResult(json_object *obj) { + void ParseCheckResult(yyjson_val *obj) { check_result cr; init_check_result(&cr); char *output = nullptr; @@ -225,44 +227,44 @@ namespace statusengine { }); // Direct lookups rather than iterating every key and running it down a chain - // of string comparisons: json-c keeps the members in a hash table, so this is - // one lookup per field instead of comparisons proportional to keys times fields. - json_object *value = nullptr; - if (json_object_object_get_ex(obj, "host_name", &value)) { + // of string comparisons: one lookup per field instead of comparisons + // proportional to keys times fields. + yyjson_val *value = nullptr; + if ((value = yyjson_obj_get(obj, "host_name")) != nullptr) { cr.host_name = get_json_string_c(value); } - if (json_object_object_get_ex(obj, "service_description", &value)) { + if ((value = yyjson_obj_get(obj, "service_description")) != nullptr) { cr.service_description = get_json_string_c(value); } - if (json_object_object_get_ex(obj, "output", &value)) { + if ((value = yyjson_obj_get(obj, "output")) != nullptr) { output = get_json_string_c(value); } - if (json_object_object_get_ex(obj, "long_output", &value)) { + if ((value = yyjson_obj_get(obj, "long_output")) != nullptr) { longOutput = get_json_string_c(value); } - if (json_object_object_get_ex(obj, "perf_data", &value)) { + if ((value = yyjson_obj_get(obj, "perf_data")) != nullptr) { perfData = get_json_string_c(value); } - if (json_object_object_get_ex(obj, "check_type", &value)) { - cr.check_type = json_object_get_int64(value); + if ((value = yyjson_obj_get(obj, "check_type")) != nullptr) { + cr.check_type = yyjson_get_sint(value); } - if (json_object_object_get_ex(obj, "return_code", &value)) { - cr.return_code = json_object_get_int64(value); + if ((value = yyjson_obj_get(obj, "return_code")) != nullptr) { + cr.return_code = yyjson_get_sint(value); } - if (json_object_object_get_ex(obj, "start_time", &value)) { - cr.start_time.tv_sec = json_object_get_int64(value); + if ((value = yyjson_obj_get(obj, "start_time")) != nullptr) { + cr.start_time.tv_sec = yyjson_get_sint(value); } - if (json_object_object_get_ex(obj, "end_time", &value)) { - cr.finish_time.tv_sec = json_object_get_int64(value); + if ((value = yyjson_obj_get(obj, "end_time")) != nullptr) { + cr.finish_time.tv_sec = yyjson_get_sint(value); } - if (json_object_object_get_ex(obj, "early_timeout", &value)) { - cr.early_timeout = json_object_get_int64(value); + if ((value = yyjson_obj_get(obj, "early_timeout")) != nullptr) { + cr.early_timeout = yyjson_get_sint(value); } - if (json_object_object_get_ex(obj, "latency", &value)) { - cr.latency = json_object_get_double(value); + if ((value = yyjson_obj_get(obj, "latency")) != nullptr) { + cr.latency = yyjson_get_real(value); } - if (json_object_object_get_ex(obj, "exited_ok", &value)) { - cr.exited_ok = json_object_get_int64(value); + if ((value = yyjson_obj_get(obj, "exited_ok")) != nullptr) { + cr.exited_ok = yyjson_get_sint(value); } cr.output = BuildCheckOutput(output, longOutput, perfData); @@ -297,7 +299,7 @@ namespace statusengine { free_check_result(&cr); } - void ParseScheduleCheck(json_object *obj) { + void ParseScheduleCheck(yyjson_val *obj) { const char *hostname = nullptr; const char *service_description = nullptr; time_t schedule_time = 0; @@ -305,8 +307,10 @@ namespace statusengine { delete[] hostname; delete[] service_description; }); - json_object_object_foreach(obj, cKey, jsonValue) { - std::string jsonKey(cKey); + size_t objIdx, objMax; + yyjson_val *jsonKeyVal, *jsonValue; + yyjson_obj_foreach(obj, objIdx, objMax, jsonKeyVal, jsonValue) { + std::string jsonKey(yyjson_get_str(jsonKeyVal), yyjson_get_len(jsonKeyVal)); if (jsonKey.compare("host_name") == 0) { hostname = get_json_string(jsonValue); } @@ -314,7 +318,7 @@ namespace statusengine { service_description = get_json_string(jsonValue); } else if (jsonKey.compare("schedule_time") == 0) { - schedule_time = json_object_get_int64(jsonValue); + schedule_time = yyjson_get_sint(jsonValue); } } @@ -342,7 +346,7 @@ namespace statusengine { } } - void ParseDeleteDowntime(json_object *obj) { + void ParseDeleteDowntime(yyjson_val *obj) { const char *hostname = nullptr; const char *service_description = nullptr; time_t start_time = 0; @@ -353,8 +357,10 @@ namespace statusengine { delete[] service_description; delete[] comment; }); - json_object_object_foreach(obj, cKey, jsonValue) { - std::string jsonKey(cKey); + size_t objIdx, objMax; + yyjson_val *jsonKeyVal, *jsonValue; + yyjson_obj_foreach(obj, objIdx, objMax, jsonKeyVal, jsonValue) { + std::string jsonKey(yyjson_get_str(jsonKeyVal), yyjson_get_len(jsonKeyVal)); if (jsonKey.compare("host_name") == 0) { hostname = get_json_string(jsonValue); } @@ -362,10 +368,10 @@ namespace statusengine { service_description = get_json_string(jsonValue); } else if (jsonKey.compare("start_time") == 0) { - start_time = json_object_get_int64(jsonValue); + start_time = yyjson_get_sint(jsonValue); } else if (jsonKey.compare("end_time") == 0) { - end_time = json_object_get_int64(jsonValue); + end_time = yyjson_get_sint(jsonValue); } else if (jsonKey.compare("comment") == 0) { comment = get_json_string(jsonValue); @@ -380,7 +386,7 @@ namespace statusengine { Nebmodule::Instance().DeleteDowntime(hostname, service_description, start_time, end_time, comment); } - inline static void ParseRaw(json_object *obj) { + inline static void ParseRaw(yyjson_val *obj) { auto cmd = get_json_string(obj); process_external_command1(cmd); delete[] cmd; diff --git a/src/NagiosObject.h b/src/NagiosObject.h index c49b18c..cd8f591 100644 --- a/src/NagiosObject.h +++ b/src/NagiosObject.h @@ -2,7 +2,6 @@ #include -#include #include "yyjson.h" #include "Nebmodule.h" diff --git a/src/meson.build b/src/meson.build index 192012e..2e9e8f3 100644 --- a/src/meson.build +++ b/src/meson.build @@ -6,7 +6,7 @@ version_h = configure_file(output: 'version.h', configuration: version_conf) statusengine_sources = ['Encoding.cpp', 'Nebmodule.cpp', 'Statusengine.cpp', 'MessageHandler/MessageHandlerList.cpp', '../vendor/yyjson.c'] -statusengine_deps = [uchardet_dep, jsonc_dep, toml_dep] +statusengine_deps = [uchardet_dep, toml_dep] statusengine_cpp_args = [] if get_option('nagios') diff --git a/tests/bench.cpp b/tests/bench.cpp index 8035d9f..48c7fbe 100644 --- a/tests/bench.cpp +++ b/tests/bench.cpp @@ -50,21 +50,25 @@ namespace { /// Builds an OCSP worker payload with `count` service checks, the shape the broker /// receives from another instance: {"messages":[{...,"servicecheck":{...}}, ...]}. std::string BulkServiceChecks(size_t count) { - json_object *fixture = json_tokener_parse(ReadFixture("statusngin_servicechecks.json").c_str()); - json_object *messages = nullptr; - json_object_object_get_ex(fixture, "messages", &messages); - json_object *one = json_object_array_get_idx(messages, 0); - - json_object *arr = json_object_new_array(); + std::string fixtureText = ReadFixture("statusngin_servicechecks.json"); + yyjson_doc *fixture = yyjson_read(fixtureText.c_str(), fixtureText.length(), 0); + yyjson_val *messages = yyjson_obj_get(yyjson_doc_get_root(fixture), "messages"); + yyjson_val *one = yyjson_arr_get(messages, 0); + + yyjson_mut_doc *doc = yyjson_mut_doc_new(nullptr); + yyjson_mut_val *root = yyjson_mut_obj(doc); + yyjson_mut_doc_set_root(doc, root); + yyjson_mut_val *arr = yyjson_mut_arr(doc); for (size_t i = 0; i < count; ++i) { - json_object_array_add(arr, json_object_get(one)); + yyjson_mut_arr_append(arr, yyjson_val_mut_copy(doc, one)); } - json_object *root = json_object_new_object(); - json_object_object_add(root, "messages", arr); + yyjson_mut_obj_add_val(doc, root, "messages", arr); - std::string result(json_object_to_json_string(root)); - json_object_put(root); - json_object_put(fixture); + char *out = yyjson_mut_write(doc, 0, nullptr); + std::string result(out ? out : ""); + free(out); + yyjson_mut_doc_free(doc); + yyjson_doc_free(fixture); return result; } @@ -175,11 +179,11 @@ int main() { const std::string bulk100 = BulkServiceChecks(100); Bench("ProcessMessage ocsp bulk (100)", 2000, [&] { handler.ProcessMessage(WorkerQueue::OCSP, bulk100); }); - // How much of that is json-c parsing, which we cannot avoid? - Bench(" json_tokener_parse only (bulk)", 2000, [&] { - json_object *o = json_tokener_parse(bulk100.c_str()); - sink += json_object_object_length(o); - json_object_put(o); + // How much of that is parsing, which we cannot avoid? + Bench(" yyjson_read only (bulk)", 2000, [&] { + yyjson_doc *d = yyjson_read(bulk100.c_str(), bulk100.size(), 0); + sink += static_cast(yyjson_obj_size(yyjson_doc_get_root(d))); + yyjson_doc_free(d); }); // And how much is the std::string copy of the payload that step 4 targets? Bench(" payload copy only (bulk)", 200000, [&] { @@ -217,49 +221,8 @@ int main() { }); } - // Serialisation: what json-c's default SPACED costs against PLAIN. - { - NagiosServiceCheckData msg(&scd); - json_object *raw = msg.GetDataCopy(); - const char *spaced = json_object_to_json_string_ext(raw, JSON_C_TO_STRING_SPACED); - size_t spacedLen = std::strlen(spaced); - const char *plain = json_object_to_json_string_ext(raw, JSON_C_TO_STRING_PLAIN); - size_t plainLen = std::strlen(plain); - - Bench("ToString SPACED (current)", 200000, [&] { - sink += static_cast(std::strlen(json_object_to_json_string_ext(raw, JSON_C_TO_STRING_SPACED))); - }); - Bench("ToString PLAIN", 200000, [&] { - sink += static_cast(std::strlen(json_object_to_json_string_ext(raw, JSON_C_TO_STRING_PLAIN))); - }); - std::cout << " serialised size: SPACED " << spacedLen << " B, PLAIN " << plainLen << " B (" - << std::setprecision(1) << (100.0 - 100.0 * static_cast(plainLen) / - static_cast(spacedLen)) - << "% smaller)" << std::endl; - json_object_put(raw); - } - - // The same comparison on a realistic bulk payload, which is what actually travels. - { - json_object *fixture = json_tokener_parse(ReadFixture("statusngin_servicechecks.json").c_str()); - json_object *messages = nullptr; - json_object_object_get_ex(fixture, "messages", &messages); - json_object *one = json_object_array_get_idx(messages, 0); - json_object *arr = json_object_new_array(); - for (int i = 0; i < 100; ++i) { - json_object_array_add(arr, json_object_get(one)); - } - json_object *root = json_object_new_object(); - json_object_object_add(root, "messages", arr); - size_t sp = std::strlen(json_object_to_json_string_ext(root, JSON_C_TO_STRING_SPACED)); - size_t pl = std::strlen(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PLAIN)); - std::cout << " bulk of 100: SPACED " << sp << " B, PLAIN " << pl << " B (" - << std::setprecision(1) << (100.0 - 100.0 * static_cast(pl) / - static_cast(sp)) - << "% smaller, " << (sp - pl) << " B saved)" << std::endl; - json_object_put(root); - json_object_put(fixture); - } + // The SPACED vs PLAIN comparison that used to live here measured a json-c + // output option. yyjson always writes minified, so there is nothing to compare. std::cout << "\nbulk payload of 100: " << bulk100.size() << " bytes, " << "processed check results: " << processedCheckResults << std::endl; diff --git a/tests/meson.build b/tests/meson.build index b4e209b..80c50d5 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,6 +1,6 @@ doctest_dep = dependency('doctest') -test_deps = [doctest_dep, uchardet_dep, jsonc_dep, toml_dep] +test_deps = [doctest_dep, uchardet_dep, toml_dep] test_cpp_args = statusengine_cpp_args + [ '-DSTATUSENGINE_EXAMPLE_CONFIG="' + meson.project_source_root() / 'statusengine.toml' + '"', '-DSTATUSENGINE_DEVENV_CONFIG="' + meson.project_source_root() / 'devtools/buildenv/statusengine.toml' + '"', diff --git a/tests/test_messageformat.cpp b/tests/test_messageformat.cpp index 51c1ce5..76cfbf7 100644 --- a/tests/test_messageformat.cpp +++ b/tests/test_messageformat.cpp @@ -14,33 +14,37 @@ using namespace statusengine; namespace { - /// Reads a recorded broker message from tests/fixtures. - json_object *LoadFixture(const std::string &name) { + /// Reads a recorded broker message from tests/fixtures. The document owns the + /// values, so it is kept alive by the caller. + yyjson_doc *LoadFixture(const std::string &name) { std::ifstream in(std::string(STATUSENGINE_FIXTURE_DIR) + "/" + name); REQUIRE_MESSAGE(in.good(), "missing fixture: ", name); std::stringstream buffer; buffer << in.rdbuf(); - json_object *obj = json_tokener_parse(buffer.str().c_str()); - REQUIRE_MESSAGE(obj != nullptr, "fixture is not valid json: ", name); - return obj; + std::string text = buffer.str(); + yyjson_doc *doc = yyjson_read(text.c_str(), text.length(), 0); + REQUIRE_MESSAGE(doc != nullptr, "fixture is not valid json: ", name); + return doc; } - std::set KeysOf(json_object *obj) { + std::set KeysOf(yyjson_val *obj) { std::set keys; - REQUIRE(json_object_is_type(obj, json_type_object)); - json_object_object_foreach(obj, key, value) { + REQUIRE(yyjson_is_obj(obj)); + size_t idx, max; + yyjson_val *key, *value; + yyjson_obj_foreach(obj, idx, max, key, value) { (void)value; - keys.insert(key); + keys.insert(std::string(yyjson_get_str(key), yyjson_get_len(key))); } return keys; } /// Unwraps a fixture down to the single recorded message, bulk or not. - json_object *FirstMessage(json_object *fixture) { - json_object *messages; - if (json_object_object_get_ex(fixture, "messages", &messages)) { - REQUIRE(json_object_array_length(messages) > 0); - return json_object_array_get_idx(messages, 0); + yyjson_val *FirstMessage(yyjson_val *fixture) { + yyjson_val *messages = yyjson_obj_get(fixture, "messages"); + if (messages != nullptr) { + REQUIRE(yyjson_arr_size(messages) > 0); + return yyjson_arr_get(messages, 0); } return fixture; } @@ -55,24 +59,26 @@ namespace { * fixtures were recorded from a different host. */ void CheckShape(NagiosObject &produced, const std::string &fixtureName, const char *subObject) { - json_object *fixture = LoadFixture(fixtureName); - json_object *expected = FirstMessage(fixture); + yyjson_doc *fixture = LoadFixture(fixtureName); + yyjson_val *expected = FirstMessage(yyjson_doc_get_root(fixture)); - json_object *actual = json_tokener_parse(Rendered(produced).c_str()); - REQUIRE(actual != nullptr); + std::string renderedText = Rendered(produced); + yyjson_doc *actualDoc = yyjson_read(renderedText.c_str(), renderedText.length(), 0); + REQUIRE(actualDoc != nullptr); + yyjson_val *actual = yyjson_doc_get_root(actualDoc); CHECK(KeysOf(actual) == KeysOf(expected)); if (subObject != nullptr) { - json_object *actualSub = nullptr; - json_object *expectedSub = nullptr; - REQUIRE(json_object_object_get_ex(actual, subObject, &actualSub)); - REQUIRE(json_object_object_get_ex(expected, subObject, &expectedSub)); + yyjson_val *actualSub = yyjson_obj_get(actual, subObject); + yyjson_val *expectedSub = yyjson_obj_get(expected, subObject); + REQUIRE(actualSub != nullptr); + REQUIRE(expectedSub != nullptr); CHECK(KeysOf(actualSub) == KeysOf(expectedSub)); } - json_object_put(actual); - json_object_put(fixture); + yyjson_doc_free(actualDoc); + yyjson_doc_free(fixture); } /// Fills the fields every nebstruct shares. @@ -234,24 +240,25 @@ TEST_CASE("acknowledgement carries the end time") { #endif NagiosAcknowledgementData msg(&data); - json_object *parsed = json_tokener_parse(Rendered(msg).c_str()); - REQUIRE(parsed != nullptr); + std::string renderedText = Rendered(msg); + yyjson_doc *parsedDoc = yyjson_read(renderedText.c_str(), renderedText.length(), 0); + REQUIRE(parsedDoc != nullptr); - json_object *ack = nullptr; - REQUIRE(json_object_object_get_ex(parsed, "acknowledgement", &ack)); - json_object *endTime = nullptr; + yyjson_val *ack = yyjson_obj_get(yyjson_doc_get_root(parsedDoc), "acknowledgement"); + REQUIRE(ack != nullptr); // The key is always present, so consumers can rely on it regardless of the core. - REQUIRE(json_object_object_get_ex(ack, "end_time", &endTime)); + yyjson_val *endTime = yyjson_obj_get(ack, "end_time"); + REQUIRE(endTime != nullptr); #ifndef BUILD_NAGIOS - CHECK(json_object_get_int64(endTime) == 1785470668); + CHECK(yyjson_get_sint(endTime) == 1785470668); #else // Nagios has no expiring acknowledgements, so 0 - "does not expire" - is not a // placeholder here but the truth for every nagios acknowledgement. - CHECK(json_object_get_int64(endTime) == 0); + CHECK(yyjson_get_sint(endTime) == 0); #endif - json_object_put(parsed); + yyjson_doc_free(parsedDoc); } TEST_CASE("downtime message keeps its shape") { @@ -322,18 +329,18 @@ TEST_CASE("non utf8 plugin output is converted in the message") { NagiosServiceCheckPerfData msg(&data); std::string rendered = Rendered(msg); - // json-c refuses to build a string from invalid utf8, so a successful round trip - // through the parser is what proves the conversion happened. - json_object *parsed = json_tokener_parse(rendered.c_str()); - REQUIRE(parsed != nullptr); + // A successful round trip through the parser is what proves the conversion + // happened: invalid utf8 would not survive it intact. + yyjson_doc *parsedDoc = yyjson_read(rendered.c_str(), rendered.length(), 0); + REQUIRE(parsedDoc != nullptr); - json_object *sub = nullptr; - REQUIRE(json_object_object_get_ex(parsed, "servicecheck", &sub)); - json_object *perf = nullptr; - REQUIRE(json_object_object_get_ex(sub, "perf_data", &perf)); - CHECK(std::string(json_object_get_string(perf)) == "Größe des Puffers überschritten, Dienst läuft nicht"); + yyjson_val *sub = yyjson_obj_get(yyjson_doc_get_root(parsedDoc), "servicecheck"); + REQUIRE(sub != nullptr); + yyjson_val *perf = yyjson_obj_get(sub, "perf_data"); + REQUIRE(perf != nullptr); + CHECK(std::string(yyjson_get_str(perf)) == "Größe des Puffers überschritten, Dienst läuft nicht"); - json_object_put(parsed); + yyjson_doc_free(parsedDoc); } TEST_CASE("core restart message keeps its shape") { @@ -352,16 +359,19 @@ TEST_CASE("core restart carries naemon's event time") { data.timestamp.tv_sec = 1785470668; NagiosRestartData msg(&data); - json_object *parsed = json_tokener_parse(Rendered(msg).c_str()); - REQUIRE(parsed != nullptr); + std::string renderedText = Rendered(msg); + yyjson_doc *parsedDoc = yyjson_read(renderedText.c_str(), renderedText.length(), 0); + REQUIRE(parsedDoc != nullptr); + yyjson_val *parsed = yyjson_doc_get_root(parsedDoc); - json_object *value = nullptr; - REQUIRE(json_object_object_get_ex(parsed, "object_type", &value)); - CHECK(json_object_get_int(value) == NEBTYPE_PROCESS_RESTART); + yyjson_val *value = yyjson_obj_get(parsed, "object_type"); + REQUIRE(value != nullptr); + CHECK(yyjson_get_int(value) == NEBTYPE_PROCESS_RESTART); - REQUIRE(json_object_object_get_ex(parsed, "timestamp", &value)); + value = yyjson_obj_get(parsed, "timestamp"); + REQUIRE(value != nullptr); // The worker reads 0 as "not set", so a real restart must never report 0. - CHECK(json_object_get_int64(value) == 1785470668); + CHECK(yyjson_get_sint(value) == 1785470668); - json_object_put(parsed); + yyjson_doc_free(parsedDoc); } diff --git a/tests/test_support.h b/tests/test_support.h index f774ab9..d1abafc 100644 --- a/tests/test_support.h +++ b/tests/test_support.h @@ -81,7 +81,7 @@ namespace statusengine { } void SendMessage(Queue, const std::string &) override {} void ProcessMessage(WorkerQueue, const std::string &) override {} - void ProcessMessage(WorkerQueue, json_object *) override {} + void ProcessMessage(WorkerQueue, yyjson_val *) override {} unsigned long remaining; bool keepAsking; @@ -154,7 +154,7 @@ namespace statusengine { } void SendMessage(Queue, const std::string &) override {} void ProcessMessage(WorkerQueue, const std::string &) override {} - void ProcessMessage(WorkerQueue, json_object *) override {} + void ProcessMessage(WorkerQueue, yyjson_val *) override {} unsigned long remaining; bool waiting;