diff --git a/AMBuildScript b/AMBuildScript index 56faedafb4..5771402abb 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -678,6 +678,7 @@ else: 'extensions/tf2/AMBuilder', 'extensions/topmenus/AMBuilder', 'extensions/updater/AMBuilder', + 'extensions/json/AMBuilder', ] if builder.backend == 'amb2': diff --git a/extensions/json/AMBuilder b/extensions/json/AMBuilder new file mode 100755 index 0000000000..718ae8d437 --- /dev/null +++ b/extensions/json/AMBuilder @@ -0,0 +1,28 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os + +for cxx in builder.targets: + binary = SM.ExtLibrary(builder, cxx, 'json.ext') + if binary.compiler.family == 'gcc' or binary.compiler.family == 'clang': + binary.compiler.cxxflags += ['-fno-rtti'] + binary.compiler.cflags += ['-fPIC'] + elif binary.compiler.family == 'msvc': + binary.compiler.cxxflags += ['/GR-'] + + binary.compiler.defines += [ + 'YYJSON_DISABLE_INCR_READER', + ] + + binary.compiler.cxxincludes += [ + os.path.join(builder.sourcePath, 'extensions', 'json', 'yyjson'), + ] + + binary.sources += [ + 'extension.cpp', + 'JsonManager.cpp', + 'JsonNatives.cpp', + 'yyjson/yyjson.c', + '../../public/smsdk_ext.cpp', + ] + + SM.extensions += [builder.Add(binary)] diff --git a/extensions/json/IJsonManager.h b/extensions/json/IJsonManager.h new file mode 100755 index 0000000000..ed4090e692 --- /dev/null +++ b/extensions/json/IJsonManager.h @@ -0,0 +1,1880 @@ +#ifndef _INCLUDE_IJSONMANAGER_H_ +#define _INCLUDE_IJSONMANAGER_H_ + +#include +#include + +using SourceMod::Handle_t; +using SourceMod::HandleType_t; +using SourceMod::SMInterface; +using SourcePawn::IPluginContext; + +// Forward declaration +class JsonValue; +class JsonArrIter; +class JsonObjIter; + +#define SMINTERFACE_JSONMANAGER_NAME "IJsonManager" +#define SMINTERFACE_JSONMANAGER_VERSION 2 +#define JSON_PACK_ERROR_SIZE 256 +#define JSON_ERROR_BUFFER_SIZE 256 +#define JSON_INT64_BUFFER_SIZE 32 + +/** + * @brief JSON sorting order + */ +enum JSON_SORT_ORDER +{ + JSON_SORT_ASC = 0, // Ascending order + JSON_SORT_DESC = 1, // Descending order + JSON_SORT_RANDOM = 2 // Random order +}; + +/** + * @brief Parameter provider interface for Pack operation + * + * Allows Pack to retrieve parameters in a platform-independent way. + */ +class IPackParamProvider +{ +public: + virtual ~IPackParamProvider() = default; + + virtual bool GetNextString(const char** out_str) = 0; + virtual bool GetNextInt(int* out_value) = 0; + virtual bool GetNextFloat(float* out_value) = 0; + virtual bool GetNextBool(bool* out_value) = 0; +}; + +/** + * @brief JSON Manager Interface + * + * This interface provides complete JSON manipulation capabilities. + * It's designed to be consumed by other SourceMod C++ extensions + * without requiring them to link against yyjson library. + * + * @usage + * IJsonManager* g_pJsonManager = nullptr; + * + * bool YourExtension::SDK_OnAllLoaded() + * { + * SM_GET_LATE_IFACE(JSONMANAGER, g_pJsonManager); + * } + */ +class IJsonManager : public SMInterface +{ +public: + virtual ~IJsonManager() = default; + + virtual const char *GetInterfaceName() override { + return SMINTERFACE_JSONMANAGER_NAME; + } + + virtual unsigned int GetInterfaceVersion() override { + return SMINTERFACE_JSONMANAGER_VERSION; + } + +public: + /** + * Parse JSON from string or file + * @param json_str JSON string or file path + * @param is_file true if json_str is a file path + * @param is_mutable true to create mutable document (default: false) + * @param read_flg Read flags (YYJSON_READ_FLAG values, default: 0) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return JSON value pointer or nullptr on error + */ + virtual JsonValue* ParseJSON(const char* json_str, bool is_file, bool is_mutable = false, + uint32_t read_flg = 0, char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Write JSON to string + * @param handle JSON value + * @param buffer Output buffer + * @param buffer_size Buffer size + * @param write_flg Write flags (YYJSON_WRITE_FLAG values, default: 0) + * @param out_size Pointer to receive actual size written (including null terminator) optional + * @return true on success, false if buffer is too small or on error + * + * @note The out_size parameter returns the size including null terminator + * @note Use GetSerializedSize() with the same write_flg to determine buffer size + * @warning This method performs multiple memory allocations and copies, resulting in poor performance. + * For better performance, use WriteToStringPtr() instead, which avoids intermediate buffers + */ + virtual bool WriteToString(JsonValue* handle, char* buffer, size_t buffer_size, + uint32_t write_flg = 0, size_t* out_size = nullptr) = 0; + + /** + * Write JSON to string and return allocated string (performance-optimized version) + * @param handle JSON value + * @param write_flg Write flags (YYJSON_WRITE_FLAG values, default: 0) + * @param out_size Pointer to receive actual size written (including null terminator) optional + * @return Allocated string pointer on success, nullptr on error. Caller must free() the returned pointer + * + * @note This is the recommended method for serialization as it avoids intermediate buffer allocations + */ + virtual char* WriteToStringPtr(JsonValue* handle, uint32_t write_flg = 0, size_t* out_size = nullptr) = 0; + + /** + * Apply JSON Patch (RFC 6902) and return a new JSON value + * @param target Target JSON value + * @param patch JSON Patch document + * @param result_mutable true to return mutable result, false for immutable + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return Patched JSON value on success, nullptr on failure + */ + virtual JsonValue* ApplyJsonPatch(JsonValue* target, JsonValue* patch, bool result_mutable, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Apply JSON Patch in place (target must be mutable) + * @param target Target JSON value (mutable document) + * @param patch JSON Patch document + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on failure + */ + virtual bool JsonPatchInPlace(JsonValue* target, JsonValue* patch, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Apply JSON Merge Patch (RFC 7396) and return a new JSON value + * @param target Target JSON value + * @param patch JSON Merge Patch document + * @param result_mutable true to return mutable result, false for immutable + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return Patched JSON value on success, nullptr on failure + */ + virtual JsonValue* ApplyMergePatch(JsonValue* target, JsonValue* patch, bool result_mutable, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Apply JSON Merge Patch in place (target must be mutable) + * @param target Target JSON value (mutable document) + * @param patch JSON Merge Patch document + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on failure + */ + virtual bool MergePatchInPlace(JsonValue* target, JsonValue* patch, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Write JSON to file + * @param handle JSON value + * @param path File path + * @param write_flg Write flags (YYJSON_WRITE_FLAG values, default: 0) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success + */ + virtual bool WriteToFile(JsonValue* handle, const char* path, uint32_t write_flg = 0, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Compare two JSON values for equality + * @param handle1 First JSON value to compare + * @param handle2 Second JSON value to compare + * @return true if values are equal, false otherwise + * @note Compares structure and content recursively + */ + virtual bool Equals(JsonValue* handle1, JsonValue* handle2) = 0; + + /** + * Check if JSON value equals a string + * @param handle JSON value to compare + * @param str String to compare with + * @return true if value is a string and equals the given string, false otherwise + * @note Returns false if handle is null, str is null, or value is not a string + */ + virtual bool EqualsStr(JsonValue* handle, const char* str) = 0; + + /** + * Deep copy a JSON value into a target document + * @param targetDoc Target document that will own the copied value + * @param sourceValue Source value to copy + * @return New JSON value (deep copy) or nullptr on failure + * @note The returned value is owned by targetDoc's document context + */ + virtual JsonValue* DeepCopy(JsonValue* targetDoc, JsonValue* sourceValue) = 0; + + /** + * Get human-readable type description string + * @param handle JSON value + * @return Type description string (e.g., "object", "array", "string", "number", "true", "false", "unknown") + */ + virtual const char* GetTypeDesc(JsonValue* handle) = 0; + + /** + * Get the size needed to serialize this JSON value + * + * @param handle JSON value + * @param write_flg Write flags (YYJSON_WRITE_FLAG values, default: 0) + * @return Size in bytes (including null terminator) + * + * @note The returned size depends on the write_flg parameter. + * You MUST use the same flags when calling both GetSerializedSize() + * and WriteToString(). Using different flags will return + * different sizes and may cause buffer overflow. + * + * @example + * // Correct usage: + * auto flags = YYJSON_WRITE_PRETTY; + * size_t size = g_pJsonManager->GetSerializedSize(handle, flags); + * char* buffer = new char[size]; + * g_pJsonManager->WriteToString(handle, buffer, size, flags); // Use same flags + */ + virtual size_t GetSerializedSize(JsonValue* handle, uint32_t write_flg = 0) = 0; + + /** + * Convert immutable document to mutable + * @param handle Immutable JSON value + * @return New mutable JSON value or nullptr if already mutable or on error + * @note Creates a deep copy as a mutable document + */ + virtual JsonValue* ToMutable(JsonValue* handle) = 0; + + /** + * Convert mutable document to immutable + * @param handle Mutable JSON value + * @return New immutable JSON value or nullptr if already immutable or on error + * @note Creates a deep copy as an immutable document + */ + virtual JsonValue* ToImmutable(JsonValue* handle) = 0; + + /** + * Get JSON type + * @param handle JSON value + * @return YYJSON_TYPE value + */ + virtual uint8_t GetType(JsonValue* handle) = 0; + + /** + * Get JSON subtype + * @param handle JSON value + * @return YYJSON_SUBTYPE value + */ + virtual uint8_t GetSubtype(JsonValue* handle) = 0; + + /** + * Check if value is an array + * @param handle JSON value + * @return true if value is an array + */ + virtual bool IsArray(JsonValue* handle) = 0; + + /** + * Check if value is an object + * @param handle JSON value + * @return true if value is an object + */ + virtual bool IsObject(JsonValue* handle) = 0; + + /** + * Check if value is an integer (signed or unsigned) + * @param handle JSON value + * @return true if value is an integer + */ + virtual bool IsInt(JsonValue* handle) = 0; + + /** + * Check if value is an unsigned integer + * @param handle JSON value + * @return true if value is an unsigned integer + */ + virtual bool IsUint(JsonValue* handle) = 0; + + /** + * Check if value is a signed integer + * @param handle JSON value + * @return true if value is a signed integer + */ + virtual bool IsSint(JsonValue* handle) = 0; + + /** + * Check if value is a number (integer or real) + * @param handle JSON value + * @return true if value is a number + */ + virtual bool IsNum(JsonValue* handle) = 0; + + /** + * Check if value is a boolean (true or false) + * @param handle JSON value + * @return true if value is a boolean + */ + virtual bool IsBool(JsonValue* handle) = 0; + + /** + * Check if value is boolean true + * @param handle JSON value + * @return true if value is boolean true + */ + virtual bool IsTrue(JsonValue* handle) = 0; + + /** + * Check if value is boolean false + * @param handle JSON value + * @return true if value is boolean false + */ + virtual bool IsFalse(JsonValue* handle) = 0; + + /** + * Check if value is a floating-point number + * @param handle JSON value + * @return true if value is a floating-point number + */ + virtual bool IsFloat(JsonValue* handle) = 0; + + /** + * Check if value is a string + * @param handle JSON value + * @return true if value is a string + */ + virtual bool IsStr(JsonValue* handle) = 0; + + /** + * Check if value is null + * @param handle JSON value + * @return true if value is null + */ + virtual bool IsNull(JsonValue* handle) = 0; + + /** + * Check if value is a container (object or array) + * @param handle JSON value + * @return true if value is a container + */ + virtual bool IsCtn(JsonValue* handle) = 0; + + /** + * Check if document is mutable + * @param handle JSON value + * @return true if document is mutable + */ + virtual bool IsMutable(JsonValue* handle) = 0; + + /** + * Check if document is immutable + * @param handle JSON value + * @return true if document is immutable + */ + virtual bool IsImmutable(JsonValue* handle) = 0; + + /** + * Get the number of bytes read when parsing this document + * @param handle JSON value + * @return Number of bytes read during parsing (including null terminator) 0 if not from parsing + * + * @note This value only applies to documents created from parsing + * @note Manually created documents (ObjectInit, CreateBool, etc.) will return 0 + * @note The returned size includes the null terminator + */ + virtual size_t GetReadSize(JsonValue* handle) = 0; + + /** + * Get the reference count of the document + * @param handle JSON value + * @return Reference count of the document (number of JsonValue objects sharing this document) + * @note Returns 0 if handle is null or has no document + */ + virtual size_t GetRefCount(JsonValue* handle) = 0; + + /** + * Create an empty mutable JSON object + * @return New mutable JSON object or nullptr on failure + */ + virtual JsonValue* ObjectInit() = 0; + + /** + * Create a JSON object from key-value string pairs + * @param pairs Array of strings [key1, val1, key2, val2, ...] + * @param count Number of key-value pairs + * @return New JSON object or nullptr on failure + */ + virtual JsonValue* ObjectInitWithStrings(const char** pairs, size_t count) = 0; + + /** + * Parse a JSON object from string + * @param str JSON string to parse + * @param read_flg Read flags (YYJSON_READ_FLAG values, default: 0) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return Parsed JSON object or nullptr on error + * @note Returns error if root is not an object + */ + virtual JsonValue* ObjectParseString(const char* str, uint32_t read_flg = 0, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Parse a JSON object from file + * @param path File path + * @param read_flg Read flags (YYJSON_READ_FLAG values, default: 0) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return Parsed JSON object or nullptr on error + * @note Returns error if root is not an object + */ + virtual JsonValue* ObjectParseFile(const char* path, uint32_t read_flg = 0, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Get number of key-value pairs in object + * @param handle JSON object + * @return Number of key-value pairs + */ + virtual size_t ObjectGetSize(JsonValue* handle) = 0; + + /** + * Get key name at specific index + * @param handle JSON object + * @param index Index of key-value pair + * @param out_key Pointer to receive key string + * @return true on success, false if index out of bounds + */ + virtual bool ObjectGetKey(JsonValue* handle, size_t index, const char** out_key) = 0; + + /** + * Get value at specific index + * @param handle JSON object + * @param index Index of key-value pair + * @return JSON value or nullptr if index out of bounds + */ + virtual JsonValue* ObjectGetValueAt(JsonValue* handle, size_t index) = 0; + + /** + * Get value by key name + * @param handle JSON object + * @param key Key name + * @return JSON value or nullptr if key not found + */ + virtual JsonValue* ObjectGet(JsonValue* handle, const char* key) = 0; + + /** + * Get boolean value by key + * @param handle JSON object + * @param key Key name + * @param out_value Pointer to receive boolean value + * @return true on success, false if key not found or type mismatch + */ + virtual bool ObjectGetBool(JsonValue* handle, const char* key, bool* out_value) = 0; + + /** + * Get float value by key + * @param handle JSON object + * @param key Key name + * @param out_value Pointer to receive float value + * @return true on success, false if key not found or type mismatch + */ + virtual bool ObjectGetFloat(JsonValue* handle, const char* key, double* out_value) = 0; + + /** + * Get integer value by key + * @param handle JSON object + * @param key Key name + * @param out_value Pointer to receive integer value + * @return true on success, false if key not found or type mismatch + */ + virtual bool ObjectGetInt(JsonValue* handle, const char* key, int* out_value) = 0; + + /** + * Get 64-bit integer value by key (auto-detects signed/unsigned) + * @param handle JSON object + * @param key Key name + * @param out_value Pointer to receive 64-bit integer value (std::variant) + * @return true on success, false if key not found or type mismatch + */ + virtual bool ObjectGetInt64(JsonValue* handle, const char* key, std::variant* out_value) = 0; + + /** + * Get string value by key + * @param handle JSON object + * @param key Key name + * @param out_str Pointer to receive string pointer + * @param out_len Pointer to receive string length + * @return true on success, false if key not found or type mismatch + */ + virtual bool ObjectGetString(JsonValue* handle, const char* key, const char** out_str, size_t* out_len) = 0; + + /** + * Check if value at key is null + * @param handle JSON object + * @param key Key name + * @param out_is_null Pointer to receive result + * @return true if key exists, false if key not found + */ + virtual bool ObjectIsNull(JsonValue* handle, const char* key, bool* out_is_null) = 0; + + /** + * Check if object has a specific key + * @param handle JSON object + * @param key Key name (or JSON pointer if use_pointer is true) + * @param use_pointer If true, treat key as JSON pointer + * @return true if key exists + */ + virtual bool ObjectHasKey(JsonValue* handle, const char* key, bool use_pointer) = 0; + + /** + * Rename a key in the object + * @param handle Mutable JSON object + * @param old_key Current key name + * @param new_key New key name + * @param allow_duplicate Allow duplicate key names + * @return true on success + * @note Only works on mutable objects + */ + virtual bool ObjectRenameKey(JsonValue* handle, const char* old_key, const char* new_key, bool allow_duplicate) = 0; + + /** + * Set value by key (mutable only) + * @param handle Mutable JSON object + * @param key Key name + * @param value JSON value to set + * @return true on success + */ + virtual bool ObjectSet(JsonValue* handle, const char* key, JsonValue* value) = 0; + + /** + * Set boolean value by key (mutable only) + * @param handle Mutable JSON object + * @param key Key name + * @param value Boolean value + * @return true on success + */ + virtual bool ObjectSetBool(JsonValue* handle, const char* key, bool value) = 0; + + /** + * Set float value by key (mutable only) + * @param handle Mutable JSON object + * @param key Key name + * @param value Float value + * @return true on success + */ + virtual bool ObjectSetFloat(JsonValue* handle, const char* key, double value) = 0; + + /** + * Set integer value by key (mutable only) + * @param handle Mutable JSON object + * @param key Key name + * @param value Integer value + * @return true on success + */ + virtual bool ObjectSetInt(JsonValue* handle, const char* key, int value) = 0; + + /** + * Set 64-bit integer value by key (mutable only, auto-detects signed/unsigned) + * @param handle Mutable JSON object + * @param key Key name + * @param value 64-bit integer value (std::variant) + * @return true on success + */ + virtual bool ObjectSetInt64(JsonValue* handle, const char* key, std::variant value) = 0; + + /** + * Set null value by key (mutable only) + * @param handle Mutable JSON object + * @param key Key name + * @return true on success + */ + virtual bool ObjectSetNull(JsonValue* handle, const char* key) = 0; + + /** + * Set string value by key (mutable only) + * @param handle Mutable JSON object + * @param key Key name + * @param value String value + * @return true on success + */ + virtual bool ObjectSetString(JsonValue* handle, const char* key, const char* value) = 0; + + /** + * Remove key-value pair by key (mutable only) + * @param handle Mutable JSON object + * @param key Key name + * @return true on success + */ + virtual bool ObjectRemove(JsonValue* handle, const char* key) = 0; + + /** + * Remove all key-value pairs (mutable only) + * @param handle Mutable JSON object + * @return true on success + */ + virtual bool ObjectClear(JsonValue* handle) = 0; + + /** + * Sort object keys + * @param handle Mutable JSON object + * @param sort_mode Sort order (see JSON_SORT_ORDER enum) + * @return true on success + * @note Only works on mutable objects + */ + virtual bool ObjectSort(JsonValue* handle, JSON_SORT_ORDER sort_mode) = 0; + + /** + * Create an empty mutable JSON array + * @return New mutable JSON array or nullptr on failure + */ + virtual JsonValue* ArrayInit() = 0; + + /** + * Create a JSON array from string values + * @param strings Array of string values + * @param count Number of strings + * @return New JSON array or nullptr on failure + */ + virtual JsonValue* ArrayInitWithStrings(const char** strings, size_t count) = 0; + + /** + * Create a JSON array from 32-bit integer values + * @param values Array of int32_t values + * @param count Number of values + * @return New JSON array or nullptr on failure + */ + virtual JsonValue* ArrayInitWithInt32(const int32_t* values, size_t count) = 0; + + /** + * Create a JSON array from 64-bit integer string values (auto-detects signed/unsigned) + * @param values Array of int64 string values (can be signed or unsigned) + * @param count Number of values + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return New JSON array or nullptr on failure + * @note Each string value is parsed and auto-detected as signed or unsigned + */ + virtual JsonValue* ArrayInitWithInt64(const char** values, size_t count, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Create a JSON array from boolean values + * @param values Array of boolean values + * @param count Number of values + * @return New JSON array or nullptr on failure + */ + virtual JsonValue* ArrayInitWithBool(const bool* values, size_t count) = 0; + + /** + * Create a JSON array from float values + * @param values Array of float values + * @param count Number of values + * @return New JSON array or nullptr on failure + */ + virtual JsonValue* ArrayInitWithFloat(const double* values, size_t count) = 0; + + /** + * Parse a JSON array from string + * @param str JSON string to parse + * @param read_flg Read flags (YYJSON_READ_FLAG values, default: 0) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return Parsed JSON array or nullptr on error + * @note Returns error if root is not an array + */ + virtual JsonValue* ArrayParseString(const char* str, uint32_t read_flg = 0, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Parse a JSON array from file + * @param path File path + * @param read_flg Read flags (YYJSON_READ_FLAG values, default: 0) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return Parsed JSON array or nullptr on error + * @note Returns error if root is not an array + */ + virtual JsonValue* ArrayParseFile(const char* path, uint32_t read_flg = 0, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Get number of elements in array + * @param handle JSON array + * @return Number of elements + */ + virtual size_t ArrayGetSize(JsonValue* handle) = 0; + + /** + * Get element at specific index + * @param handle JSON array + * @param index Element index + * @return JSON value or nullptr if index out of bounds + */ + virtual JsonValue* ArrayGet(JsonValue* handle, size_t index) = 0; + + /** + * Get first element in array + * @param handle JSON array + * @return First JSON value or nullptr if array is empty + */ + virtual JsonValue* ArrayGetFirst(JsonValue* handle) = 0; + + /** + * Get last element in array + * @param handle JSON array + * @return Last JSON value or nullptr if array is empty + */ + virtual JsonValue* ArrayGetLast(JsonValue* handle) = 0; + + /** + * Get boolean value at index + * @param handle JSON array + * @param index Element index + * @param out_value Pointer to receive boolean value + * @return true on success, false if index out of bounds or type mismatch + */ + virtual bool ArrayGetBool(JsonValue* handle, size_t index, bool* out_value) = 0; + + /** + * Get float value at index + * @param handle JSON array + * @param index Element index + * @param out_value Pointer to receive float value + * @return true on success, false if index out of bounds or type mismatch + */ + virtual bool ArrayGetFloat(JsonValue* handle, size_t index, double* out_value) = 0; + + /** + * Get integer value at index + * @param handle JSON array + * @param index Element index + * @param out_value Pointer to receive integer value + * @return true on success, false if index out of bounds or type mismatch + */ + virtual bool ArrayGetInt(JsonValue* handle, size_t index, int* out_value) = 0; + + /** + * Get 64-bit integer value at index (auto-detects signed/unsigned) + * @param handle JSON array + * @param index Element index + * @param out_value Pointer to receive 64-bit integer value (std::variant) + * @return true on success, false if index out of bounds or type mismatch + */ + virtual bool ArrayGetInt64(JsonValue* handle, size_t index, std::variant* out_value) = 0; + + /** + * Get string value at index + * @param handle JSON array + * @param index Element index + * @param out_str Pointer to receive string pointer + * @param out_len Pointer to receive string length + * @return true on success, false if index out of bounds or type mismatch + */ + virtual bool ArrayGetString(JsonValue* handle, size_t index, const char** out_str, size_t* out_len) = 0; + + /** + * Check if element at index is null + * @param handle JSON array + * @param index Element index + * @return true if element is null + */ + virtual bool ArrayIsNull(JsonValue* handle, size_t index) = 0; + + /** + * Replace element at index with JSON value (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @param value JSON value to set + * @return true on success + */ + virtual bool ArrayReplace(JsonValue* handle, size_t index, JsonValue* value) = 0; + + /** + * Replace element at index with boolean (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @param value Boolean value + * @return true on success + */ + virtual bool ArrayReplaceBool(JsonValue* handle, size_t index, bool value) = 0; + + /** + * Replace element at index with float (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @param value Float value + * @return true on success + */ + virtual bool ArrayReplaceFloat(JsonValue* handle, size_t index, double value) = 0; + + /** + * Replace element at index with integer (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @param value Integer value + * @return true on success + */ + virtual bool ArrayReplaceInt(JsonValue* handle, size_t index, int value) = 0; + + /** + * Replace element at index with 64-bit integer (mutable only, auto-detects signed/unsigned) + * @param handle Mutable JSON array + * @param index Element index + * @param value 64-bit integer value (std::variant) + * @return true on success + */ + virtual bool ArrayReplaceInt64(JsonValue* handle, size_t index, std::variant value) = 0; + + /** + * Replace element at index with null (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @return true on success + */ + virtual bool ArrayReplaceNull(JsonValue* handle, size_t index) = 0; + + /** + * Replace element at index with string (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @param value String value + * @return true on success + */ + virtual bool ArrayReplaceString(JsonValue* handle, size_t index, const char* value) = 0; + + /** + * Append JSON value to end of array (mutable only) + * @param handle Mutable JSON array + * @param value JSON value to append + * @return true on success + */ + virtual bool ArrayAppend(JsonValue* handle, JsonValue* value) = 0; + + /** + * Append boolean to end of array (mutable only) + * @param handle Mutable JSON array + * @param value Boolean value + * @return true on success + */ + virtual bool ArrayAppendBool(JsonValue* handle, bool value) = 0; + + /** + * Append float to end of array (mutable only) + * @param handle Mutable JSON array + * @param value Float value + * @return true on success + */ + virtual bool ArrayAppendFloat(JsonValue* handle, double value) = 0; + + /** + * Append integer to end of array (mutable only) + * @param handle Mutable JSON array + * @param value Integer value + * @return true on success + */ + virtual bool ArrayAppendInt(JsonValue* handle, int value) = 0; + + /** + * Append 64-bit integer to end of array (mutable only, auto-detects signed/unsigned) + * @param handle Mutable JSON array + * @param value 64-bit integer value (std::variant) + * @return true on success + */ + virtual bool ArrayAppendInt64(JsonValue* handle, std::variant value) = 0; + + /** + * Append null to end of array (mutable only) + * @param handle Mutable JSON array + * @return true on success + */ + virtual bool ArrayAppendNull(JsonValue* handle) = 0; + + /** + * Append string to end of array (mutable only) + * @param handle Mutable JSON array + * @param value String value + * @return true on success + */ + virtual bool ArrayAppendString(JsonValue* handle, const char* value) = 0; + + /** + * Insert JSON value at specific index (mutable only) + * @param handle Mutable JSON array + * @param index Element index (0 to size, size means append) + * @param value JSON value to insert + * @return true on success + */ + virtual bool ArrayInsert(JsonValue* handle, size_t index, JsonValue* value) = 0; + + /** + * Insert boolean at specific index (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @param value Boolean value + * @return true on success + */ + virtual bool ArrayInsertBool(JsonValue* handle, size_t index, bool value) = 0; + + /** + * Insert integer at specific index (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @param value Integer value + * @return true on success + */ + virtual bool ArrayInsertInt(JsonValue* handle, size_t index, int value) = 0; + + /** + * Insert 64-bit integer at specific index (mutable only, auto-detects signed/unsigned) + * @param handle Mutable JSON array + * @param index Element index + * @param value 64-bit integer value (std::variant) + * @return true on success + */ + virtual bool ArrayInsertInt64(JsonValue* handle, size_t index, std::variant value) = 0; + + /** + * Insert float at specific index (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @param value Float value + * @return true on success + */ + virtual bool ArrayInsertFloat(JsonValue* handle, size_t index, double value) = 0; + + /** + * Insert string at specific index (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @param value String value + * @return true on success + */ + virtual bool ArrayInsertString(JsonValue* handle, size_t index, const char* value) = 0; + + /** + * Insert null at specific index (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @return true on success + */ + virtual bool ArrayInsertNull(JsonValue* handle, size_t index) = 0; + + /** + * Prepend JSON value to beginning of array (mutable only) + * @param handle Mutable JSON array + * @param value JSON value to prepend + * @return true on success + */ + virtual bool ArrayPrepend(JsonValue* handle, JsonValue* value) = 0; + + /** + * Prepend boolean to beginning of array (mutable only) + * @param handle Mutable JSON array + * @param value Boolean value + * @return true on success + */ + virtual bool ArrayPrependBool(JsonValue* handle, bool value) = 0; + + /** + * Prepend integer to beginning of array (mutable only) + * @param handle Mutable JSON array + * @param value Integer value + * @return true on success + */ + virtual bool ArrayPrependInt(JsonValue* handle, int value) = 0; + + /** + * Prepend 64-bit integer to beginning of array (mutable only, auto-detects signed/unsigned) + * @param handle Mutable JSON array + * @param value 64-bit integer value (std::variant) + * @return true on success + */ + virtual bool ArrayPrependInt64(JsonValue* handle, std::variant value) = 0; + + /** + * Prepend float to beginning of array (mutable only) + * @param handle Mutable JSON array + * @param value Float value + * @return true on success + */ + virtual bool ArrayPrependFloat(JsonValue* handle, double value) = 0; + + /** + * Prepend string to beginning of array (mutable only) + * @param handle Mutable JSON array + * @param value String value + * @return true on success + */ + virtual bool ArrayPrependString(JsonValue* handle, const char* value) = 0; + + /** + * Prepend null to beginning of array (mutable only) + * @param handle Mutable JSON array + * @return true on success + */ + virtual bool ArrayPrependNull(JsonValue* handle) = 0; + + /** + * Remove element at specific index (mutable only) + * @param handle Mutable JSON array + * @param index Element index + * @return true on success + */ + virtual bool ArrayRemove(JsonValue* handle, size_t index) = 0; + + /** + * Remove first element (mutable only) + * @param handle Mutable JSON array + * @return true on success + */ + virtual bool ArrayRemoveFirst(JsonValue* handle) = 0; + + /** + * Remove last element (mutable only) + * @param handle Mutable JSON array + * @return true on success + */ + virtual bool ArrayRemoveLast(JsonValue* handle) = 0; + + /** + * Remove range of elements (mutable only) + * @param handle JSON array + * @param start_index Start index (inclusive) + * @param count Number of elements to remove + * @return true on success + */ + virtual bool ArrayRemoveRange(JsonValue* handle, size_t start_index, size_t count) = 0; + + /** + * Remove all elements (mutable only) + * @param handle Mutable JSON array + * @return true on success + */ + virtual bool ArrayClear(JsonValue* handle) = 0; + + /** + * Find index of boolean value + * @param handle JSON array + * @param search_value Boolean value to search for + * @return Index of first match, or -1 if not found + */ + virtual int ArrayIndexOfBool(JsonValue* handle, bool search_value) = 0; + + /** + * Find index of string value + * @param handle JSON array + * @param search_value String value to search for + * @return Index of first match, or -1 if not found + */ + virtual int ArrayIndexOfString(JsonValue* handle, const char* search_value) = 0; + + /** + * Find index of integer value + * @param handle JSON array + * @param search_value Integer value to search for + * @return Index of first match, or -1 if not found + */ + virtual int ArrayIndexOfInt(JsonValue* handle, int search_value) = 0; + + /** + * Find index of 64-bit integer value (auto-detects signed/unsigned) + * @param handle JSON array + * @param search_value 64-bit integer value to search for (std::variant) + * @return Index of first match, or -1 if not found + */ + virtual int ArrayIndexOfInt64(JsonValue* handle, std::variant search_value) = 0; + + /** + * Find index of float value + * @param handle JSON array + * @param search_value Float value to search for + * @return Index of first match, or -1 if not found + */ + virtual int ArrayIndexOfFloat(JsonValue* handle, double search_value) = 0; + + /** + * Sort array elements + * @param handle Mutable JSON array + * @param sort_mode Sort order (see JSON_SORT_ORDER enum) + * @return true on success + * @note Only works on mutable arrays + */ + virtual bool ArraySort(JsonValue* handle, JSON_SORT_ORDER sort_mode) = 0; + + /** + * Create JSON value from format string and parameters + * @param format Format string (e.g., "{s:i,s:s}", "[i,s,b]") + * Format specifiers: + * - 's': string + * - 'i': integer + * - 'f': float + * - 'b': boolean + * - 'n': null + * - '{': object start, '}': object end + * - '[': array start, ']': array end + * @param param_provider Parameter provider implementation + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return New JSON value or nullptr on error + * @note Example: format="{s:i,s:s}" with params ["age", 25, "name", "John"] creates {"age":25,"name":"John"} + */ + virtual JsonValue* Pack(const char* format, IPackParamProvider* param_provider, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Create a JSON boolean value + * @param value Boolean value + * @return New JSON boolean or nullptr on failure + */ + virtual JsonValue* CreateBool(bool value) = 0; + + /** + * Create a JSON float value + * @param value Float value + * @return New JSON float or nullptr on failure + */ + virtual JsonValue* CreateFloat(double value) = 0; + + /** + * Create a JSON integer value + * @param value Integer value + * @return New JSON integer or nullptr on failure + */ + virtual JsonValue* CreateInt(int value) = 0; + + /** + * Create a JSON 64-bit integer value (auto-detects signed/unsigned) + * @param value 64-bit integer value (std::variant) + * @return New JSON integer64 or nullptr on failure + */ + virtual JsonValue* CreateInt64(std::variant value) = 0; + + /** + * Create a JSON null value + * @return New JSON null or nullptr on failure + */ + virtual JsonValue* CreateNull() = 0; + + /** + * Create a JSON string value + * @param value String value + * @return New JSON string or nullptr on failure + */ + virtual JsonValue* CreateString(const char* value) = 0; + + /** + * Get boolean value from JSON + * @param handle JSON value + * @param out_value Pointer to receive boolean value + * @return true on success, false on type mismatch + */ + virtual bool GetBool(JsonValue* handle, bool* out_value) = 0; + + /** + * Get float value from JSON + * @param handle JSON value + * @param out_value Pointer to receive float value + * @return true on success, false on type mismatch + */ + virtual bool GetFloat(JsonValue* handle, double* out_value) = 0; + + /** + * Get integer value from JSON + * @param handle JSON value + * @param out_value Pointer to receive integer value + * @return true on success, false on type mismatch + */ + virtual bool GetInt(JsonValue* handle, int* out_value) = 0; + + /** + * Get 64-bit integer value from JSON (auto-detects signed/unsigned) + * @param handle JSON value + * @param out_value Pointer to receive 64-bit integer value (std::variant) + * @return true on success, false on type mismatch + */ + virtual bool GetInt64(JsonValue* handle, std::variant* out_value) = 0; + + /** + * Get string value from JSON + * @param handle JSON value + * @param out_str Pointer to receive string pointer + * @param out_len Pointer to receive string length + * @return true on success, false on type mismatch + */ + virtual bool GetString(JsonValue* handle, const char** out_str, size_t* out_len) = 0; + + /** + * Get value using JSON Pointer + * @param handle JSON value + * @param path JSON Pointer path (e.g., "/users/0/name") + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return JSON value or nullptr on error + */ + virtual JsonValue* PtrGet(JsonValue* handle, const char* path, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Get boolean value using JSON Pointer + * @param handle JSON value + * @param path JSON Pointer path + * @param out_value Pointer to receive boolean value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrGetBool(JsonValue* handle, const char* path, bool* out_value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Get float value using JSON Pointer + * @param handle JSON value + * @param path JSON Pointer path + * @param out_value Pointer to receive float value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrGetFloat(JsonValue* handle, const char* path, double* out_value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Get integer value using JSON Pointer + * @param handle JSON value + * @param path JSON Pointer path + * @param out_value Pointer to receive integer value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrGetInt(JsonValue* handle, const char* path, int* out_value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Get 64-bit integer value using JSON Pointer (auto-detects signed/unsigned) + * @param handle JSON value + * @param path JSON Pointer path + * @param out_value Pointer to receive 64-bit integer value (std::variant) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrGetInt64(JsonValue* handle, const char* path, std::variant* out_value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Get string value using JSON Pointer + * @param handle JSON value + * @param path JSON Pointer path + * @param out_str Pointer to receive string pointer + * @param out_len Pointer to receive string length + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrGetString(JsonValue* handle, const char* path, const char** out_str, + size_t* out_len, char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Check if value is null using JSON Pointer + * @param handle JSON value + * @param path JSON Pointer path + * @param out_is_null Pointer to receive result + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrGetIsNull(JsonValue* handle, const char* path, bool* out_is_null, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Get length of container (array/object) using JSON Pointer + * @param handle JSON value + * @param path JSON Pointer path + * @param out_len Pointer to receive length + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrGetLength(JsonValue* handle, const char* path, size_t* out_len, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Set value using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path + * @param value JSON value to set + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrSet(JsonValue* handle, const char* path, JsonValue* value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Set boolean value using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path + * @param value Boolean value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrSetBool(JsonValue* handle, const char* path, bool value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Set float value using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path + * @param value Float value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrSetFloat(JsonValue* handle, const char* path, double value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Set integer value using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path + * @param value Integer value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrSetInt(JsonValue* handle, const char* path, int value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Set 64-bit integer value using JSON Pointer (mutable only, auto-detects signed/unsigned) + * @param handle Mutable JSON value + * @param path JSON Pointer path + * @param value 64-bit integer value (std::variant) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrSetInt64(JsonValue* handle, const char* path, std::variant value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Set string value using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path + * @param value String value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrSetString(JsonValue* handle, const char* path, const char* value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Set null value using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrSetNull(JsonValue* handle, const char* path, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Add value to array using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path to array + * @param value JSON value to add + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrAdd(JsonValue* handle, const char* path, JsonValue* value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Add boolean to array using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path to array + * @param value Boolean value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrAddBool(JsonValue* handle, const char* path, bool value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Add float to array using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path to array + * @param value Float value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrAddFloat(JsonValue* handle, const char* path, double value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Add integer to array using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path to array + * @param value Integer value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrAddInt(JsonValue* handle, const char* path, int value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Add 64-bit integer to array using JSON Pointer (mutable only, auto-detects signed/unsigned) + * @param handle Mutable JSON value + * @param path JSON Pointer path to array + * @param value 64-bit integer value (std::variant) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrAddInt64(JsonValue* handle, const char* path, std::variant value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Add string to array using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path to array + * @param value String value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrAddString(JsonValue* handle, const char* path, const char* value, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Add null to array using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path to array + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrAddNull(JsonValue* handle, const char* path, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Remove value using JSON Pointer (mutable only) + * @param handle Mutable JSON value + * @param path JSON Pointer path + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on error + */ + virtual bool PtrRemove(JsonValue* handle, const char* path, + char* error = nullptr, size_t error_size = 0) = 0; + + /** + * Try to get value using JSON Pointer (no error on failure) + * @param handle JSON value + * @param path JSON Pointer path + * @return JSON value or nullptr if not found + */ + virtual JsonValue* PtrTryGet(JsonValue* handle, const char* path) = 0; + + /** + * Try to get boolean value using JSON Pointer (returns false on failure) + * @param handle JSON value + * @param path JSON Pointer path + * @param out_value Pointer to receive boolean value + * @return true on success, false if not found or type mismatch + */ + virtual bool PtrTryGetBool(JsonValue* handle, const char* path, bool* out_value) = 0; + + /** + * Try to get float value using JSON Pointer (returns false on failure) + * @param handle JSON value + * @param path JSON Pointer path + * @param out_value Pointer to receive float value + * @return true on success, false if not found or type mismatch + */ + virtual bool PtrTryGetFloat(JsonValue* handle, const char* path, double* out_value) = 0; + + /** + * Try to get integer value using JSON Pointer (returns false on failure) + * @param handle JSON value + * @param path JSON Pointer path + * @param out_value Pointer to receive integer value + * @return true on success, false if not found or type mismatch + */ + virtual bool PtrTryGetInt(JsonValue* handle, const char* path, int* out_value) = 0; + + /** + * Try to get 64-bit integer value using JSON Pointer (auto-detects signed/unsigned, returns false on failure) + * @param handle JSON value + * @param path JSON Pointer path + * @param out_value Pointer to receive 64-bit integer value (std::variant) + * @return true on success, false if not found or type mismatch + */ + virtual bool PtrTryGetInt64(JsonValue* handle, const char* path, std::variant* out_value) = 0; + + /** + * Try to get string value using JSON Pointer (returns false on failure) + * @param handle JSON value + * @param path JSON Pointer path + * @param out_str Pointer to receive string pointer + * @param out_len Pointer to receive string length + * @return true on success, false if not found or type mismatch + */ + virtual bool PtrTryGetString(JsonValue* handle, const char* path, const char** out_str, size_t* out_len) = 0; + + // Note: Iterators are stateful and stored in the JsonValue object + // Call these functions in a loop until they return false + + /** + * Get next key-value pair from object iterator + * @param handle JSON object + * @param out_key Pointer to receive key string + * @param out_key_len Pointer to receive key length (can be nullptr) + * @param out_value Pointer to receive value (creates new JsonValue) + * @return true if iteration continues, false if iteration complete + * @note Iterator state is maintained in handle. Returns false when iteration completes. + * @deprecated Use JSONObjIter instead for better iterator support + */ + virtual bool ObjectForeachNext(JsonValue* handle, const char** out_key, + size_t* out_key_len, JsonValue** out_value) = 0; + + /** + * Get next index-value pair from array iterator + * @param handle JSON array + * @param out_index Pointer to receive current index + * @param out_value Pointer to receive value (creates new JsonValue) + * @return true if iteration continues, false if iteration complete + * @note Iterator state is maintained in handle. Returns false when iteration completes. + * @deprecated Use JSONArrIter instead for better iterator support + */ + virtual bool ArrayForeachNext(JsonValue* handle, size_t* out_index, + JsonValue** out_value) = 0; + + /** + * Get next key from object iterator (key only, no value) + * @param handle JSON object + * @param out_key Pointer to receive key string + * @param out_key_len Pointer to receive key length (can be nullptr) + * @return true if iteration continues, false if iteration complete + * @note Iterator state is maintained in handle. Returns false when iteration completes. + * @deprecated Use JSONObjIter instead for better iterator support + */ + virtual bool ObjectForeachKeyNext(JsonValue* handle, const char** out_key, + size_t* out_key_len) = 0; + + /** + * Get next index from array iterator (index only, no value) + * @param handle JSON array + * @param out_index Pointer to receive current index + * @return true if iteration continues, false if iteration complete + * @note Iterator state is maintained in handle. Returns false when iteration completes. + * @deprecated Use JSONArrIter instead for better iterator support + */ + virtual bool ArrayForeachIndexNext(JsonValue* handle, size_t* out_index) = 0; + + /** + * Release a JsonValue object + * External extensions should use this instead of deleting directly + * @param value The JsonValue to release + */ + virtual void Release(JsonValue* value) = 0; + + /** + * Get the HandleType_t for JSON handles + * External extensions MUST use this method to obtain the handle type + * @return The HandleType_t for JSON handles + */ + virtual HandleType_t GetHandleType() = 0; + + /** + * Read JsonValue from a SourceMod handle + * @param pContext Plugin context + * @param handle Handle to read from + * @return JsonValue pointer, or nullptr on error (error will be reported to context) + */ + virtual JsonValue* GetFromHandle(IPluginContext* pContext, Handle_t handle) = 0; + + /** + * Initialize an array iterator (same as ArrIterWith but returns pointer) + * @param handle JSON array value + * @return New array iterator or nullptr on error + * @note Caller must release the iterator using ReleaseArrIter() once finished + * @note Iterators are single-pass; once ArrIterNext() returns nullptr, create a new iterator or call ArrIterReset() to iterate again + */ + virtual JsonArrIter* ArrIterInit(JsonValue* handle) = 0; + + /** + * Create an array iterator with an array + * @param handle JSON array value + * @return New array iterator or nullptr on error + * @note Caller must release the iterator using ReleaseArrIter() once finished + * @note Iterators are single-pass; once ArrIterNext() returns nullptr, create a new iterator or call ArrIterReset() to iterate again + */ + virtual JsonArrIter* ArrIterWith(JsonValue* handle) = 0; + + /** + * Reset an array iterator to the beginning + * @param iter Array iterator + * @return true on success, false if iterator is invalid or reset failed + */ + virtual bool ArrIterReset(JsonArrIter* iter) = 0; + + /** + * Get next element from array iterator + * @param iter Array iterator + * @return JSON value wrapper for next element, or nullptr if iteration complete + */ + virtual JsonValue* ArrIterNext(JsonArrIter* iter) = 0; + + /** + * Check if array iterator has more elements + * @param iter Array iterator + * @return true if has next element, false otherwise + */ + virtual bool ArrIterHasNext(JsonArrIter* iter) = 0; + + /** + * Get current index in array iteration + * @param iter Array iterator + * @return Current index (0-based), or SIZE_MAX if iterator is not positioned + */ + virtual size_t ArrIterGetIndex(JsonArrIter* iter) = 0; + + /** + * Remove current element from array (mutable only) + * @param iter Mutable array iterator + * @return Pointer to removed value, or nullptr on error + */ + virtual void* ArrIterRemove(JsonArrIter* iter) = 0; + + /** + * Initialize an object iterator (same as ObjIterWith but returns pointer) + * @param handle JSON object value + * @return New object iterator or nullptr on error + * @note Caller must release the iterator using ReleaseObjIter() once finished + * @note Iterators are single-pass; once ObjIterNext() returns nullptr, create a new iterator or call ObjIterReset() to iterate again + */ + virtual JsonObjIter* ObjIterInit(JsonValue* handle) = 0; + + /** + * Create an object iterator with an object + * @param handle JSON object value + * @return New object iterator or nullptr on error + * @note Caller must release the iterator using ReleaseObjIter() once finished + * @note Iterators are single-pass; once ObjIterNext() returns nullptr, create a new iterator or call ObjIterReset() to iterate again + */ + virtual JsonObjIter* ObjIterWith(JsonValue* handle) = 0; + + /** + * Reset an object iterator to the beginning + * @param iter Object iterator + * @return true on success, false if iterator is invalid or reset failed + */ + virtual bool ObjIterReset(JsonObjIter* iter) = 0; + + /** + * Get next key from object iterator + * @param iter Object iterator + * @return Key value (yyjson_val* for immutable, yyjson_mut_val* for mutable), or nullptr if iteration complete + */ + virtual void* ObjIterNext(JsonObjIter* iter) = 0; + + /** + * Check if object iterator has more elements + * @param iter Object iterator + * @return true if has next element, false otherwise + */ + virtual bool ObjIterHasNext(JsonObjIter* iter) = 0; + + /** + * Get value by key from object iterator + * @param iter Object iterator + * @param key Key value (yyjson_val* or yyjson_mut_val*) + * @return JSON value wrapper for the value, or nullptr on error + */ + virtual JsonValue* ObjIterGetVal(JsonObjIter* iter, void* key) = 0; + + /** + * Iterates to a specified key and returns the value + * @param iter Object iterator + * @param key Key name string + * @return JSON value wrapper for the value, or nullptr if key not found + * @note This function searches the object using the iterator structure + * @warning This function takes a linear search time if the key is not nearby. + */ + virtual JsonValue* ObjIterGet(JsonObjIter* iter, const char* key) = 0; + + /** + * Get current index in object iteration + * @param iter Object iterator + * @return Current index (0-based), or SIZE_MAX if iterator is not positioned + */ + virtual size_t ObjIterGetIndex(JsonObjIter* iter) = 0; + + /** + * Remove current key-value pair from object (mutable only) + * @param iter Mutable object iterator + * @return Pointer to removed key, or nullptr on error + */ + virtual void* ObjIterRemove(JsonObjIter* iter) = 0; + + /** + * Release an array iterator + * @param iter Iterator to release + */ + virtual void ReleaseArrIter(JsonArrIter* iter) = 0; + + /** + * Release an object iterator + * @param iter Iterator to release + */ + virtual void ReleaseObjIter(JsonObjIter* iter) = 0; + + /** + * Get the HandleType_t for array iterator handles + * @return The HandleType_t for array iterator handles + */ + virtual HandleType_t GetArrIterHandleType() = 0; + + /** + * Get the HandleType_t for object iterator handles + * @return The HandleType_t for object iterator handles + */ + virtual HandleType_t GetObjIterHandleType() = 0; + + /** + * Read JsonArrIter from a SourceMod handle + * @param pContext Plugin context + * @param handle Handle to read from + * @return JsonArrIter pointer, or nullptr on error + */ + virtual JsonArrIter* GetArrIterFromHandle(IPluginContext* pContext, Handle_t handle) = 0; + + /** + * Read JsonObjIter from a SourceMod handle + * @param pContext Plugin context + * @param handle Handle to read from + * @return JsonObjIter pointer, or nullptr on error + */ + virtual JsonObjIter* GetObjIterFromHandle(IPluginContext* pContext, Handle_t handle) = 0; + + /** + * Read a JSON number from string + * @param dat The JSON data (UTF-8 without BOM), null-terminator is required + * @param read_flg Read flags (YYJSON_READ_FLAG values, default: 0) + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @param out_consumed Pointer to receive number of characters consumed (optional) + * @return New JSON number value or nullptr on error + * @note The returned value is a mutable number value + */ + virtual JsonValue* ReadNumber(const char* dat, uint32_t read_flg = 0, + char* error = nullptr, size_t error_size = 0, size_t* out_consumed = nullptr) = 0; + + /** + * Write a JSON number to string buffer + * @param handle JSON number value + * @param buffer Output buffer (must be at least 40 bytes for floating-point, 21 bytes for integer) + * @param buffer_size Buffer size + * @param out_written Pointer to receive number of characters written (excluding null terminator) (optional) + * @return true on success, false on error + * @note The buffer must be large enough to hold the number string + */ + virtual bool WriteNumber(JsonValue* handle, char* buffer, size_t buffer_size, + size_t* out_written = nullptr) = 0; + + /** + * Set floating-point number's output format to single-precision + * @param handle JSON floating-point number value + * @param flt true to use single-precision (float), false to use double-precision (double) + * @return true on success, false if handle is not a floating-point number + * @note Only works on floating-point numbers (not integers) + * @note This affects how the number is serialized in all write operations + */ + virtual bool SetFpToFloat(JsonValue* handle, bool flt) = 0; + + /** + * Set floating-point number's output format to fixed-point notation + * @param handle JSON floating-point number value + * @param prec Precision (1-15), similar to ECMAScript `Number.prototype.toFixed(prec)` but with trailing zeros removed + * @return true on success, false if handle is not a floating-point number or prec is out of range + * @note Only works on floating-point numbers (not integers) + * @note This will produce shorter output but may lose some precision + * @note This affects how the number is serialized in all write operations + */ + virtual bool SetFpToFixed(JsonValue* handle, int prec) = 0; + + /** + * Directly modify a JSON value to boolean type + * @param handle JSON value to modify (cannot be object or array) + * @param value Boolean value + * @return true on success, false if handle is object or array + * @warning For immutable documents, this breaks immutability. Use with caution. + * @note This modifies the value in-place without creating a new value + */ + virtual bool SetBool(JsonValue* handle, bool value) = 0; + + /** + * Directly modify a JSON value to integer type + * @param handle JSON value to modify (cannot be object or array) + * @param value Integer value + * @return true on success, false if handle is object or array + * @warning For immutable documents, this breaks immutability. Use with caution. + * @note This modifies the value in-place without creating a new value + */ + virtual bool SetInt(JsonValue* handle, int value) = 0; + + /** + * Directly modify a JSON value to 64-bit integer type (auto-detects signed/unsigned) + * @param handle JSON value to modify (cannot be object or array) + * @param value 64-bit integer value (std::variant) + * @return true on success, false if handle is object or array + * @warning For immutable documents, this breaks immutability. Use with caution. + * @note This modifies the value in-place without creating a new value + */ + virtual bool SetInt64(JsonValue* handle, std::variant value) = 0; + + /** + * Directly modify a JSON value to floating-point type + * @param handle JSON value to modify (cannot be object or array) + * @param value Float value + * @return true on success, false if handle is object or array + * @warning For immutable documents, this breaks immutability. Use with caution. + * @note This modifies the value in-place without creating a new value + */ + virtual bool SetFloat(JsonValue* handle, double value) = 0; + + /** + * Directly modify a JSON value to string type + * @param handle JSON value to modify (cannot be object or array) + * @param value String value (will be copied for mutable documents) + * @return true on success, false if handle is object or array or value is null + * @warning For immutable documents, this breaks immutability and does NOT copy the string. Use with caution. + * @warning For immutable documents, the string pointer must remain valid for the document's lifetime + * @note For mutable documents, the string is copied into the document's memory pool + */ + virtual bool SetString(JsonValue* handle, const char* value) = 0; + + /** + * Directly modify a JSON value to null type + * @param handle JSON value to modify (cannot be object or array) + * @return true on success, false if handle is object or array + * @warning For immutable documents, this breaks immutability. Use with caution. + * @note This modifies the value in-place without creating a new value + */ + virtual bool SetNull(JsonValue* handle) = 0; + + /** + * Parse an int64 string value into a variant (int64_t or uint64_t) + * @param value String representation of the integer + * @param out_value Output variant to store the parsed value + * @param error Error buffer (optional) + * @param error_size Error buffer size + * @return true on success, false on parse error + * @note Auto-detects whether to use signed or unsigned based on value range + * @note Negative values are stored as int64_t, large positive values may be stored as uint64_t + */ + virtual bool ParseInt64Variant(const char* value, std::variant* out_value, + char* error = nullptr, size_t error_size = 0) = 0; +}; + +#endif // _INCLUDE_IJSONMANAGER_H_ \ No newline at end of file diff --git a/extensions/json/JsonManager.cpp b/extensions/json/JsonManager.cpp new file mode 100755 index 0000000000..28675497f9 --- /dev/null +++ b/extensions/json/JsonManager.cpp @@ -0,0 +1,5273 @@ +#include "JsonManager.h" +#include "extension.h" + +static inline void ReadInt64FromVal(yyjson_val* val, std::variant* out_value) { + if (yyjson_is_uint(val)) { + *out_value = yyjson_get_uint(val); + } else { + *out_value = yyjson_get_sint(val); + } +} + +static inline void ReadInt64FromMutVal(yyjson_mut_val* val, std::variant* out_value) { + if (yyjson_mut_is_uint(val)) { + *out_value = yyjson_mut_get_uint(val); + } else { + *out_value = yyjson_mut_get_sint(val); + } +} + +// Set error message safely +static inline void SetErrorSafe(char* error, size_t error_size, const char* format, ...) { + if (!error || error_size == 0) return; + + va_list args; + va_start(args, format); + int needed = vsnprintf(error, error_size, format, args); + va_end(args); + + if (needed >= static_cast(error_size)) { + error[error_size - 1] = '\0'; + } +} + +std::unique_ptr JsonManager::CreateWrapper() { + return std::make_unique(); +} + +RefPtr JsonManager::WrapDocument(yyjson_mut_doc* doc) { + if (!doc) { + return RefPtr(); + } + return make_ref(doc); +} + +RefPtr JsonManager::CopyDocument(yyjson_doc* doc) { + return WrapDocument(yyjson_doc_mut_copy(doc, nullptr)); +} + +RefPtr JsonManager::CreateDocument() { + return WrapDocument(yyjson_mut_doc_new(nullptr)); +} + +RefPtr JsonManager::WrapImmutableDocument(yyjson_doc* doc) { + if (!doc) { + return RefPtr(); + } + return make_ref(doc); +} + +RefPtr JsonManager::CloneValueToMutable(JsonValue* value) { + if (!value) { + return RefPtr(); + } + + if (value->IsMutable()) { + yyjson_mut_doc* dup = yyjson_mut_doc_mut_copy(value->m_pDocument_mut->get(), nullptr); + return WrapDocument(dup); + } + + if (!value->m_pDocument) { + return RefPtr(); + } + + return CopyDocument(value->m_pDocument->get()); +} + +static yyjson_mut_val* CopyValueIntoDoc(JsonValue* value, yyjson_mut_doc* doc, char* error, size_t error_size) { + if (!value || !doc) { + SetErrorSafe(error, error_size, "Invalid JSON value or document"); + return nullptr; + } + + yyjson_mut_val* copy = nullptr; + if (value->IsMutable()) { + if (!value->m_pVal_mut) { + SetErrorSafe(error, error_size, "Mutable JSON value has no root"); + return nullptr; + } + copy = yyjson_mut_val_mut_copy(doc, value->m_pVal_mut); + } else { + if (!value->m_pVal) { + SetErrorSafe(error, error_size, "Immutable JSON value has no root"); + return nullptr; + } + copy = yyjson_val_mut_copy(doc, value->m_pVal); + } + + if (!copy) { + SetErrorSafe(error, error_size, "Failed to copy JSON value"); + } + return copy; +} + +JsonManager::JsonManager(): m_randomGenerator(m_randomDevice()) {} + +JsonManager::~JsonManager() {} + +JsonValue* JsonManager::ParseJSON(const char* json_str, bool is_file, bool is_mutable, + yyjson_read_flag read_flg, char* error, size_t error_size) +{ + if (!json_str) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid JSON string"); + } + return nullptr; + } + + yyjson_read_err readError; + yyjson_doc* idoc; + auto pJSONValue = CreateWrapper(); + + if (is_file) { + char realpath[PLATFORM_MAX_PATH]; + smutils->BuildPath(Path_Game, realpath, sizeof(realpath), "%s", json_str); + idoc = yyjson_read_file(realpath, read_flg, nullptr, &readError); + } else { + idoc = yyjson_read_opts(const_cast(json_str), strlen(json_str), read_flg, nullptr, &readError); + } + + if (!idoc || readError.code) { + if (error && error_size > 0) { + if (is_file) { + SetErrorSafe(error, error_size, "Failed to parse JSON file: %s (error code: %u, msg: %s, position: %zu)", + json_str, readError.code, readError.msg, readError.pos); + } else { + SetErrorSafe(error, error_size, "Failed to parse JSON str: %s (error code: %u, position: %zu)", + readError.msg, readError.code, readError.pos); + } + } + if (idoc) { + yyjson_doc_free(idoc); + } + return nullptr; + } + + pJSONValue->m_readSize = yyjson_doc_get_read_size(idoc); + + if (is_mutable) { + pJSONValue->m_pDocument_mut = CopyDocument(idoc); + yyjson_doc_free(idoc); + if (!pJSONValue->m_pDocument_mut) { + SetErrorSafe(error, error_size, "Failed to create mutable JSON document"); + return nullptr; + } + pJSONValue->m_pVal_mut = yyjson_mut_doc_get_root(pJSONValue->m_pDocument_mut->get()); + if (!pJSONValue->m_pVal_mut) { + SetErrorSafe(error, error_size, "Mutable JSON document has no root value"); + return nullptr; + } + } else { + pJSONValue->m_pDocument = WrapImmutableDocument(idoc); + if (!pJSONValue->m_pDocument) { + yyjson_doc_free(idoc); + SetErrorSafe(error, error_size, "Failed to create immutable JSON document"); + return nullptr; + } + pJSONValue->m_pVal = yyjson_doc_get_root(idoc); + if (!pJSONValue->m_pVal) { + yyjson_doc_free(idoc); + SetErrorSafe(error, error_size, "Immutable JSON document has no root value"); + return nullptr; + } + } + + return pJSONValue.release(); +} + +bool JsonManager::WriteToString(JsonValue* handle, char* buffer, size_t buffer_size, + yyjson_write_flag write_flg, size_t* out_size) +{ + if (!handle || !buffer || buffer_size == 0) { + return false; + } + + size_t written; + + if (handle->IsMutable()) { + written = yyjson_mut_val_write_buf(buffer, buffer_size, handle->m_pVal_mut, write_flg, nullptr); + } else { + written = yyjson_val_write_buf(buffer, buffer_size, handle->m_pVal, write_flg, nullptr); + } + + if (written == 0) { + return false; + } + + if (written + 1 > buffer_size) { + return false; + } + + buffer[written] = '\0'; + + if (out_size) { + *out_size = written + 1; + } + return true; +} + +char* JsonManager::WriteToStringPtr(JsonValue* handle, yyjson_write_flag write_flg, size_t* out_size) +{ + if (!handle) { + return nullptr; + } + + size_t json_size = 0; + char* json_str; + + if (handle->IsMutable()) { + json_str = yyjson_mut_val_write(handle->m_pVal_mut, write_flg, &json_size); + } else { + json_str = yyjson_val_write(handle->m_pVal, write_flg, &json_size); + } + + if (json_str && out_size) { + *out_size = json_size + 1; + } + + return json_str; +} + +JsonValue* JsonManager::ApplyJsonPatch(JsonValue* target, JsonValue* patch, bool result_mutable, + char* error, size_t error_size) +{ + if (!target || !patch) { + SetErrorSafe(error, error_size, "Target or patch JSON value is null"); + return nullptr; + } + + auto docRef = CloneValueToMutable(target); + if (!docRef) { + SetErrorSafe(error, error_size, "Failed to clone target JSON value"); + return nullptr; + } + + yyjson_mut_doc* doc = docRef->get(); + yyjson_mut_val* root = yyjson_mut_doc_get_root(doc); + if (!root) { + SetErrorSafe(error, error_size, "Target JSON has no root value"); + return nullptr; + } + + yyjson_mut_val* patchCopy = CopyValueIntoDoc(patch, doc, error, error_size); + if (!patchCopy) { + return nullptr; + } + + yyjson_patch_err patch_err = {0}; + yyjson_mut_val* resultRoot = yyjson_mut_patch(doc, root, patchCopy, &patch_err); + if (!resultRoot) { + SetErrorSafe(error, error_size, "JSON patch failed (code %u, op index %zu, message: %s)", + patch_err.code, patch_err.idx, patch_err); + return nullptr; + } + + yyjson_mut_doc_set_root(doc, resultRoot); + + if (result_mutable) { + auto wrapper = CreateWrapper(); + wrapper->m_pDocument_mut = docRef; + wrapper->m_pVal_mut = yyjson_mut_doc_get_root(doc); + docRef.reset(); + return wrapper.release(); + } + + yyjson_doc* imutDoc = yyjson_mut_doc_imut_copy(doc, nullptr); + if (!imutDoc) { + SetErrorSafe(error, error_size, "Failed to convert patched JSON to immutable document"); + return nullptr; + } + + auto wrapper = CreateWrapper(); + wrapper->m_pDocument = WrapImmutableDocument(imutDoc); + if (!wrapper->m_pDocument) { + yyjson_doc_free(imutDoc); + SetErrorSafe(error, error_size, "Failed to wrap immutable JSON document"); + return nullptr; + } + wrapper->m_pVal = yyjson_doc_get_root(imutDoc); + return wrapper.release(); +} + +bool JsonManager::JsonPatchInPlace(JsonValue* target, JsonValue* patch, + char* error, size_t error_size) +{ + if (!target || !patch) { + SetErrorSafe(error, error_size, "Target or patch JSON value is null"); + return false; + } + + if (!target->IsMutable()) { + SetErrorSafe(error, error_size, "Target JSON must be mutable for in-place JSON Patch"); + return false; + } + + yyjson_mut_doc* doc = target->m_pDocument_mut->get(); + yyjson_mut_val* root = target->m_pVal_mut; + + if (!doc || !root) { + SetErrorSafe(error, error_size, "Target JSON has no root value"); + return false; + } + + yyjson_mut_val* patchCopy = CopyValueIntoDoc(patch, doc, error, error_size); + if (!patchCopy) { + return false; + } + + yyjson_patch_err patch_err = {0}; + yyjson_mut_val* resultRoot = yyjson_mut_patch(doc, root, patchCopy, &patch_err); + if (!resultRoot) { + SetErrorSafe(error, error_size, "JSON patch failed (code %u, op index %zu, message: %s)", + patch_err.code, patch_err.idx, patch_err); + return false; + } + + yyjson_mut_doc_set_root(doc, resultRoot); + target->m_pVal_mut = yyjson_mut_doc_get_root(doc); + return true; +} + +JsonValue* JsonManager::ApplyMergePatch(JsonValue* target, JsonValue* patch, bool result_mutable, + char* error, size_t error_size) +{ + if (!target || !patch) { + SetErrorSafe(error, error_size, "Target or patch JSON value is null"); + return nullptr; + } + + auto docRef = CloneValueToMutable(target); + if (!docRef) { + SetErrorSafe(error, error_size, "Failed to clone target JSON value"); + return nullptr; + } + + yyjson_mut_doc* doc = docRef->get(); + yyjson_mut_val* root = yyjson_mut_doc_get_root(doc); + if (!root) { + SetErrorSafe(error, error_size, "Target JSON has no root value"); + return nullptr; + } + + yyjson_mut_val* patchCopy = CopyValueIntoDoc(patch, doc, error, error_size); + if (!patchCopy) { + return nullptr; + } + + yyjson_mut_val* resultRoot = yyjson_mut_merge_patch(doc, root, patchCopy); + if (!resultRoot) { + SetErrorSafe(error, error_size, "Failed to apply JSON Merge Patch"); + return nullptr; + } + + yyjson_mut_doc_set_root(doc, resultRoot); + + if (result_mutable) { + auto wrapper = CreateWrapper(); + wrapper->m_pDocument_mut = docRef; + wrapper->m_pVal_mut = yyjson_mut_doc_get_root(doc); + docRef.reset(); + return wrapper.release(); + } + + yyjson_doc* imutDoc = yyjson_mut_doc_imut_copy(doc, nullptr); + if (!imutDoc) { + SetErrorSafe(error, error_size, "Failed to convert patched JSON to immutable document"); + return nullptr; + } + + auto wrapper = CreateWrapper(); + wrapper->m_pDocument = WrapImmutableDocument(imutDoc); + if (!wrapper->m_pDocument) { + yyjson_doc_free(imutDoc); + SetErrorSafe(error, error_size, "Failed to wrap immutable JSON document"); + return nullptr; + } + wrapper->m_pVal = yyjson_doc_get_root(imutDoc); + return wrapper.release(); +} + +bool JsonManager::MergePatchInPlace(JsonValue* target, JsonValue* patch, + char* error, size_t error_size) +{ + if (!target || !patch) { + SetErrorSafe(error, error_size, "Target or patch JSON value is null"); + return false; + } + + if (!target->IsMutable()) { + SetErrorSafe(error, error_size, "Target JSON must be mutable for in-place merge patch"); + return false; + } + + yyjson_mut_doc* doc = target->m_pDocument_mut->get(); + yyjson_mut_val* root = target->m_pVal_mut; + + if (!doc || !root) { + SetErrorSafe(error, error_size, "Target JSON has no root value"); + return false; + } + + yyjson_mut_val* patchCopy = CopyValueIntoDoc(patch, doc, error, error_size); + if (!patchCopy) { + return false; + } + + yyjson_mut_val* resultRoot = yyjson_mut_merge_patch(doc, root, patchCopy); + if (!resultRoot) { + SetErrorSafe(error, error_size, "Failed to apply JSON Merge Patch in place"); + return false; + } + + yyjson_mut_doc_set_root(doc, resultRoot); + target->m_pVal_mut = yyjson_mut_doc_get_root(doc); + return true; +} + +bool JsonManager::WriteToFile(JsonValue* handle, const char* path, yyjson_write_flag write_flg, + char* error, size_t error_size) +{ + if (!handle || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters"); + } + return false; + } + + char realpath[PLATFORM_MAX_PATH]; + smutils->BuildPath(Path_Game, realpath, sizeof(realpath), "%s", path); + + yyjson_write_err writeError; + bool is_success; + + if (handle->IsMutable()) { + is_success = yyjson_mut_write_file(realpath, handle->m_pDocument_mut->get(), write_flg, nullptr, &writeError); + } else { + is_success = yyjson_write_file(realpath, handle->m_pDocument->get(), write_flg, nullptr, &writeError); + } + + if (writeError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to write JSON to file: %s (error code: %u)", writeError.msg, writeError.code); + } + + return is_success; +} + +bool JsonManager::Equals(JsonValue* handle1, JsonValue* handle2) +{ + if (!handle1 || !handle2) { + return false; + } + + if (handle1->IsMutable() && handle2->IsMutable()) { + return yyjson_mut_equals(handle1->m_pVal_mut, handle2->m_pVal_mut); + } + + if (!handle1->IsMutable() && !handle2->IsMutable()) { + auto doc1_mut = CopyDocument(handle1->m_pDocument->get()); + auto doc2_mut = CopyDocument(handle2->m_pDocument->get()); + + if (!doc1_mut || !doc2_mut) { + return false; + } + + yyjson_mut_val* val1_mut = yyjson_mut_doc_get_root(doc1_mut->get()); + yyjson_mut_val* val2_mut = yyjson_mut_doc_get_root(doc2_mut->get()); + + if (!val1_mut || !val2_mut) { + return false; + } + + return yyjson_mut_equals(val1_mut, val2_mut); + } + + JsonValue* immutable = handle1->IsMutable() ? handle2 : handle1; + JsonValue* mutable_doc = handle1->IsMutable() ? handle1 : handle2; + + auto doc_mut = CopyDocument(immutable->m_pDocument->get()); + if (!doc_mut) { + return false; + } + + yyjson_mut_val* val_mut = yyjson_mut_doc_get_root(doc_mut->get()); + if (!val_mut) { + return false; + } + + return yyjson_mut_equals(mutable_doc->m_pVal_mut, val_mut); +} + +bool JsonManager::EqualsStr(JsonValue* handle, const char* str) +{ + if (!handle || !str) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_equals_str(handle->m_pVal_mut, str); + } else { + return yyjson_equals_str(handle->m_pVal, str); + } +} + +JsonValue* JsonManager::DeepCopy(JsonValue* targetDoc, JsonValue* sourceValue) +{ + if (!targetDoc || !sourceValue) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + + if (targetDoc->IsMutable()) { + pJSONValue->m_pDocument_mut = CreateDocument(); + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + yyjson_mut_val* val_copy; + if (sourceValue->IsMutable()) { + val_copy = yyjson_mut_val_mut_copy(pJSONValue->m_pDocument_mut->get(), sourceValue->m_pVal_mut); + } else { + val_copy = yyjson_val_mut_copy(pJSONValue->m_pDocument_mut->get(), sourceValue->m_pVal); + } + + if (!val_copy) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), val_copy); + pJSONValue->m_pVal_mut = val_copy; + } else { + yyjson_mut_doc* temp_doc = yyjson_mut_doc_new(nullptr); + if (!temp_doc) { + return nullptr; + } + + yyjson_mut_val* temp_val; + if (sourceValue->IsMutable()) { + temp_val = yyjson_mut_val_mut_copy(temp_doc, sourceValue->m_pVal_mut); + } else { + temp_val = yyjson_val_mut_copy(temp_doc, sourceValue->m_pVal); + } + + if (!temp_val) { + yyjson_mut_doc_free(temp_doc); + return nullptr; + } + + yyjson_mut_doc_set_root(temp_doc, temp_val); + + yyjson_doc* doc = yyjson_mut_doc_imut_copy(temp_doc, nullptr); + yyjson_mut_doc_free(temp_doc); + + if (!doc) { + return nullptr; + } + + pJSONValue->m_pDocument = WrapImmutableDocument(doc); + pJSONValue->m_pVal = yyjson_doc_get_root(doc); + } + + return pJSONValue.release(); +} + +const char* JsonManager::GetTypeDesc(JsonValue* handle) +{ + if (!handle) { + return "invalid"; + } + + if (handle->IsMutable()) { + return yyjson_mut_get_type_desc(handle->m_pVal_mut); + } else { + return yyjson_get_type_desc(handle->m_pVal); + } +} + +size_t JsonManager::GetSerializedSize(JsonValue* handle, yyjson_write_flag write_flg) +{ + if (!handle) { + return 0; + } + + size_t json_size; + char* json_str; + + if (handle->IsMutable()) { + json_str = yyjson_mut_val_write(handle->m_pVal_mut, write_flg, &json_size); + } else { + json_str = yyjson_val_write(handle->m_pVal, write_flg, &json_size); + } + + if (json_str) { + free(json_str); + return json_size + 1; + } + + return 0; +} + +JsonValue* JsonManager::ToMutable(JsonValue* handle) +{ + if (!handle || handle->IsMutable()) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CopyDocument(handle->m_pDocument->get()); + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + pJSONValue->m_pVal_mut = yyjson_mut_doc_get_root(pJSONValue->m_pDocument_mut->get()); + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ToImmutable(JsonValue* handle) +{ + if (!handle || !handle->IsMutable()) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + yyjson_doc* mdoc = yyjson_mut_doc_imut_copy(handle->m_pDocument_mut->get(), nullptr); + if (!mdoc) { + return nullptr; + } + pJSONValue->m_pDocument = WrapImmutableDocument(mdoc); + if (!pJSONValue->m_pDocument) { + yyjson_doc_free(mdoc); + return nullptr; + } + pJSONValue->m_pVal = yyjson_doc_get_root(pJSONValue->m_pDocument->get()); + + return pJSONValue.release(); +} + +yyjson_type JsonManager::GetType(JsonValue* handle) +{ + if (!handle) { + return YYJSON_TYPE_NONE; + } + + if (handle->IsMutable()) { + return yyjson_mut_get_type(handle->m_pVal_mut); + } else { + return yyjson_get_type(handle->m_pVal); + } +} + +yyjson_subtype JsonManager::GetSubtype(JsonValue* handle) +{ + if (!handle) { + return YYJSON_SUBTYPE_NONE; + } + + if (handle->IsMutable()) { + return yyjson_mut_get_subtype(handle->m_pVal_mut); + } else { + return yyjson_get_subtype(handle->m_pVal); + } +} + +bool JsonManager::IsArray(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_arr(handle->m_pVal_mut); + } else { + return yyjson_is_arr(handle->m_pVal); + } +} + +bool JsonManager::IsObject(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_obj(handle->m_pVal_mut); + } else { + return yyjson_is_obj(handle->m_pVal); + } +} + +bool JsonManager::IsInt(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_int(handle->m_pVal_mut); + } else { + return yyjson_is_int(handle->m_pVal); + } +} + +bool JsonManager::IsUint(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_uint(handle->m_pVal_mut); + } else { + return yyjson_is_uint(handle->m_pVal); + } +} + +bool JsonManager::IsSint(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_sint(handle->m_pVal_mut); + } else { + return yyjson_is_sint(handle->m_pVal); + } +} + +bool JsonManager::IsNum(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_num(handle->m_pVal_mut); + } else { + return yyjson_is_num(handle->m_pVal); + } +} + +bool JsonManager::IsBool(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_bool(handle->m_pVal_mut); + } else { + return yyjson_is_bool(handle->m_pVal); + } +} + +bool JsonManager::IsTrue(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_true(handle->m_pVal_mut); + } else { + return yyjson_is_true(handle->m_pVal); + } +} + +bool JsonManager::IsFalse(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_false(handle->m_pVal_mut); + } else { + return yyjson_is_false(handle->m_pVal); + } +} + +bool JsonManager::IsFloat(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_real(handle->m_pVal_mut); + } else { + return yyjson_is_real(handle->m_pVal); + } +} + +bool JsonManager::IsStr(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_str(handle->m_pVal_mut); + } else { + return yyjson_is_str(handle->m_pVal); + } +} + +bool JsonManager::IsNull(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_null(handle->m_pVal_mut); + } else { + return yyjson_is_null(handle->m_pVal); + } +} + +bool JsonManager::IsCtn(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_is_ctn(handle->m_pVal_mut); + } else { + return yyjson_is_ctn(handle->m_pVal); + } +} + +bool JsonManager::IsMutable(JsonValue* handle) +{ + if (!handle) { + return false; + } + + return handle->IsMutable(); +} + +bool JsonManager::IsImmutable(JsonValue* handle) +{ + if (!handle) { + return false; + } + + return handle->IsImmutable(); +} + +size_t JsonManager::GetReadSize(JsonValue* handle) +{ + if (!handle) { + return 0; + } + + if (handle->m_readSize == 0) { + return 0; + } + + return handle->m_readSize + 1; +} + +size_t JsonManager::GetRefCount(JsonValue* handle) +{ + if (!handle) { + return 0; + } + return handle->GetDocumentRefCount(); +} + +JsonValue* JsonManager::ObjectInit() +{ + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_obj(pJSONValue->m_pDocument_mut->get()); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ObjectInitWithStrings(const char** pairs, size_t count) +{ + if (!pairs || count == 0) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_obj_with_kv( + pJSONValue->m_pDocument_mut->get(), + pairs, + count + ); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ObjectParseString(const char* str, yyjson_read_flag read_flg, + char* error, size_t error_size) +{ + if (!str) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid string"); + } + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + + yyjson_read_err readError; + yyjson_doc* idoc = yyjson_read_opts(const_cast(str), strlen(str), read_flg, nullptr, &readError); + + if (!idoc || readError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to parse JSON str: %s (error code: %u, position: %zu)", + readError.msg, readError.code, readError.pos); + } + if (idoc) { + yyjson_doc_free(idoc); + } + return nullptr; + } + + yyjson_val* root = yyjson_doc_get_root(idoc); + + if (!yyjson_is_obj(root)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Root value is not an object (got %s)", yyjson_get_type_desc(root)); + } + yyjson_doc_free(idoc); + return nullptr; + } + + pJSONValue->m_readSize = yyjson_doc_get_read_size(idoc); + pJSONValue->m_pDocument = WrapImmutableDocument(idoc); + pJSONValue->m_pVal = root; + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ObjectParseFile(const char* path, yyjson_read_flag read_flg, + char* error, size_t error_size) +{ + if (!path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid path"); + } + return nullptr; + } + + char realpath[PLATFORM_MAX_PATH]; + smutils->BuildPath(Path_Game, realpath, sizeof(realpath), "%s", path); + auto pJSONValue = CreateWrapper(); + + yyjson_read_err readError; + yyjson_doc* idoc = yyjson_read_file(realpath, read_flg, nullptr, &readError); + + if (!idoc || readError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to parse JSON file: %s (error code: %u, msg: %s, position: %zu)", + realpath, readError.code, readError.msg, readError.pos); + } + if (idoc) { + yyjson_doc_free(idoc); + } + return nullptr; + } + + yyjson_val* root = yyjson_doc_get_root(idoc); + + if (!yyjson_is_obj(root)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Root value in file is not an object (got %s)", yyjson_get_type_desc(root)); + } + yyjson_doc_free(idoc); + return nullptr; + } + + pJSONValue->m_readSize = yyjson_doc_get_read_size(idoc); + pJSONValue->m_pDocument = WrapImmutableDocument(idoc); + pJSONValue->m_pVal = root; + + return pJSONValue.release(); +} + +size_t JsonManager::ObjectGetSize(JsonValue* handle) +{ + if (!handle) { + return 0; + } + + if (handle->IsMutable()) { + return yyjson_mut_obj_size(handle->m_pVal_mut); + } else { + return yyjson_obj_size(handle->m_pVal); + } +} + +bool JsonManager::ObjectGetKey(JsonValue* handle, size_t index, const char** out_key) +{ + if (!handle || !out_key) { + return false; + } + + if (handle->IsMutable()) { + size_t obj_size = yyjson_mut_obj_size(handle->m_pVal_mut); + if (index >= obj_size) { + return false; + } + + yyjson_mut_obj_iter iter; + yyjson_mut_obj_iter_init(handle->m_pVal_mut, &iter); + + for (size_t i = 0; i < index; i++) { + yyjson_mut_obj_iter_next(&iter); + } + + yyjson_mut_val* key = yyjson_mut_obj_iter_next(&iter); + if (!key) { + return false; + } + + *out_key = yyjson_mut_get_str(key); + return true; + } else { + size_t obj_size = yyjson_obj_size(handle->m_pVal); + if (index >= obj_size) { + return false; + } + + yyjson_obj_iter iter; + yyjson_obj_iter_init(handle->m_pVal, &iter); + + for (size_t i = 0; i < index; i++) { + yyjson_obj_iter_next(&iter); + } + + yyjson_val* key = yyjson_obj_iter_next(&iter); + if (!key) { + return false; + } + + *out_key = yyjson_get_str(key); + return true; + } +} + +JsonValue* JsonManager::ObjectGetValueAt(JsonValue* handle, size_t index) +{ + if (!handle) { + return nullptr; + } + + size_t obj_size = handle->IsMutable() ? yyjson_mut_obj_size(handle->m_pVal_mut) : yyjson_obj_size(handle->m_pVal); + + if (index >= obj_size) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + + if (handle->IsMutable()) { + yyjson_mut_obj_iter iter; + yyjson_mut_obj_iter_init(handle->m_pVal_mut, &iter); + + for (size_t i = 0; i < index; i++) { + yyjson_mut_obj_iter_next(&iter); + } + + yyjson_mut_val* key = yyjson_mut_obj_iter_next(&iter); + if (!key) { + return nullptr; + } + + pJSONValue->m_pDocument_mut = handle->m_pDocument_mut; + pJSONValue->m_pVal_mut = yyjson_mut_obj_iter_get_val(key); + } else { + yyjson_obj_iter iter; + yyjson_obj_iter_init(handle->m_pVal, &iter); + + yyjson_val* key; + for (size_t i = 0; i <= index; i++) { + key = yyjson_obj_iter_next(&iter); + if (!key) { + return nullptr; + } + } + + pJSONValue->m_pDocument = handle->m_pDocument; + pJSONValue->m_pVal = yyjson_obj_iter_get_val(key); + } + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ObjectGet(JsonValue* handle, const char* key) +{ + if (!handle || !key) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_obj_get(handle->m_pVal_mut, key); + if (!val) { + return nullptr; + } + + pJSONValue->m_pDocument_mut = handle->m_pDocument_mut; + pJSONValue->m_pVal_mut = val; + } else { + yyjson_val* val = yyjson_obj_get(handle->m_pVal, key); + if (!val) { + return nullptr; + } + + pJSONValue->m_pDocument = handle->m_pDocument; + pJSONValue->m_pVal = val; + } + + return pJSONValue.release(); +} + +bool JsonManager::ObjectGetBool(JsonValue* handle, const char* key, bool* out_value) +{ + if (!handle || !key || !out_value) { + return false; + } + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_obj_get(handle->m_pVal_mut, key); + if (!val || !yyjson_mut_is_bool(val)) { + return false; + } + + *out_value = yyjson_mut_get_bool(val); + return true; + } else { + yyjson_val* val = yyjson_obj_get(handle->m_pVal, key); + if (!val || !yyjson_is_bool(val)) { + return false; + } + + *out_value = yyjson_get_bool(val); + return true; + } +} + +bool JsonManager::ObjectGetFloat(JsonValue* handle, const char* key, double* out_value) +{ + if (!handle || !key || !out_value) { + return false; + } + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_obj_get(handle->m_pVal_mut, key); + if (!val || !yyjson_mut_is_real(val)) { + return false; + } + + *out_value = yyjson_mut_get_real(val); + return true; + } else { + yyjson_val* val = yyjson_obj_get(handle->m_pVal, key); + if (!val || !yyjson_is_real(val)) { + return false; + } + + *out_value = yyjson_get_real(val); + return true; + } +} + +bool JsonManager::ObjectGetInt(JsonValue* handle, const char* key, int* out_value) +{ + if (!handle || !key || !out_value) { + return false; + } + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_obj_get(handle->m_pVal_mut, key); + if (!val || !yyjson_mut_is_int(val)) { + return false; + } + + *out_value = yyjson_mut_get_int(val); + return true; + } else { + yyjson_val* val = yyjson_obj_get(handle->m_pVal, key); + if (!val || !yyjson_is_int(val)) { + return false; + } + + *out_value = yyjson_get_int(val); + return true; + } +} + +bool JsonManager::ObjectGetInt64(JsonValue* handle, const char* key, std::variant* out_value) +{ + if (!handle || !key || !out_value) { + return false; + } + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_obj_get(handle->m_pVal_mut, key); + if (!val || !yyjson_mut_is_int(val)) { + return false; + } + + ReadInt64FromMutVal(val, out_value); + return true; + } else { + yyjson_val* val = yyjson_obj_get(handle->m_pVal, key); + if (!val || !yyjson_is_int(val)) { + return false; + } + + ReadInt64FromVal(val, out_value); + return true; + } +} + +bool JsonManager::ObjectGetString(JsonValue* handle, const char* key, const char** out_str, size_t* out_len) +{ + if (!handle || !key || !out_str) { + return false; + } + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_obj_get(handle->m_pVal_mut, key); + if (!val || !yyjson_mut_is_str(val)) { + return false; + } + + *out_str = yyjson_mut_get_str(val); + if (out_len) { + *out_len = yyjson_mut_get_len(val); + } + return true; + } else { + yyjson_val* val = yyjson_obj_get(handle->m_pVal, key); + if (!val || !yyjson_is_str(val)) { + return false; + } + + *out_str = yyjson_get_str(val); + if (out_len) { + *out_len = yyjson_get_len(val); + } + return true; + } +} + +bool JsonManager::ObjectIsNull(JsonValue* handle, const char* key, bool* out_is_null) +{ + if (!handle || !key || !out_is_null) { + return false; + } + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_obj_get(handle->m_pVal_mut, key); + if (!val) { + return false; + } + + *out_is_null = yyjson_mut_is_null(val); + return true; + } else { + yyjson_val* val = yyjson_obj_get(handle->m_pVal, key); + if (!val) { + return false; + } + + *out_is_null = yyjson_is_null(val); + return true; + } +} + +bool JsonManager::ObjectHasKey(JsonValue* handle, const char* key, bool use_pointer) +{ + if (!handle || !key) { + return false; + } + + if (handle->IsMutable()) { + if (use_pointer) { + return yyjson_mut_doc_ptr_get(handle->m_pDocument_mut->get(), key) != nullptr; + } else { + yyjson_mut_obj_iter iter = yyjson_mut_obj_iter_with(handle->m_pVal_mut); + return yyjson_mut_obj_iter_get(&iter, key) != nullptr; + } + } else { + if (use_pointer) { + return yyjson_doc_ptr_get(handle->m_pDocument->get(), key) != nullptr; + } else { + yyjson_obj_iter iter = yyjson_obj_iter_with(handle->m_pVal); + return yyjson_obj_iter_get(&iter, key) != nullptr; + } + } +} + +bool JsonManager::ObjectRenameKey(JsonValue* handle, const char* old_key, const char* new_key, bool allow_duplicate) +{ + if (!handle || !handle->IsMutable() || !old_key || !new_key) { + return false; + } + + if (!yyjson_mut_obj_get(handle->m_pVal_mut, old_key)) { + return false; + } + + if (!allow_duplicate && yyjson_mut_obj_get(handle->m_pVal_mut, new_key)) { + return false; + } + + return yyjson_mut_obj_rename_key(handle->m_pDocument_mut->get(), handle->m_pVal_mut, old_key, new_key); +} + +bool JsonManager::ObjectSet(JsonValue* handle, const char* key, JsonValue* value) +{ + if (!handle || !handle->IsMutable() || !key || !value) { + return false; + } + + yyjson_mut_val* val_copy; + if (value->IsMutable()) { + val_copy = yyjson_mut_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal_mut); + } else { + val_copy = yyjson_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal); + } + + if (!val_copy) { + return false; + } + + return yyjson_mut_obj_put(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), key), val_copy); +} + +bool JsonManager::ObjectSetBool(JsonValue* handle, const char* key, bool value) +{ + if (!handle || !handle->IsMutable() || !key) { + return false; + } + + return yyjson_mut_obj_put(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), key), yyjson_mut_bool(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ObjectSetFloat(JsonValue* handle, const char* key, double value) +{ + if (!handle || !handle->IsMutable() || !key) { + return false; + } + + return yyjson_mut_obj_put(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), key), yyjson_mut_real(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ObjectSetInt(JsonValue* handle, const char* key, int value) +{ + if (!handle || !handle->IsMutable() || !key) { + return false; + } + + return yyjson_mut_obj_put(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), key), yyjson_mut_int(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ObjectSetInt64(JsonValue* handle, const char* key, std::variant value) +{ + if (!handle || !handle->IsMutable() || !key) { + return false; + } + + return std::visit([&](auto&& val) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return yyjson_mut_obj_put(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), key), yyjson_mut_sint(handle->m_pDocument_mut->get(), val)); + } else if constexpr (std::is_same_v) { + return yyjson_mut_obj_put(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), key), yyjson_mut_uint(handle->m_pDocument_mut->get(), val)); + } + return false; + }, value); +} + +bool JsonManager::ObjectSetNull(JsonValue* handle, const char* key) +{ + if (!handle || !handle->IsMutable() || !key) { + return false; + } + + return yyjson_mut_obj_put(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), key), yyjson_mut_null(handle->m_pDocument_mut->get())); +} + +bool JsonManager::ObjectSetString(JsonValue* handle, const char* key, const char* value) +{ + if (!handle || !handle->IsMutable() || !key || !value) { + return false; + } + + return yyjson_mut_obj_put(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), key), yyjson_mut_strcpy(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ObjectRemove(JsonValue* handle, const char* key) +{ + if (!handle || !handle->IsMutable() || !key) { + return false; + } + + return yyjson_mut_obj_remove_key(handle->m_pVal_mut, key) != nullptr; +} + +bool JsonManager::ObjectClear(JsonValue* handle) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_obj_clear(handle->m_pVal_mut); +} + +bool JsonManager::ObjectSort(JsonValue* handle, JSON_SORT_ORDER sort_mode) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + if (!yyjson_mut_is_obj(handle->m_pVal_mut)) { + return false; + } + + if (sort_mode < JSON_SORT_ASC || sort_mode > JSON_SORT_RANDOM) { + return false; + } + + size_t obj_size = yyjson_mut_obj_size(handle->m_pVal_mut); + if (obj_size <= 1) return true; + + struct KeyValuePair { + yyjson_mut_val* key; + const char* key_str; + size_t key_len; + yyjson_mut_val* val; + }; + std::vector pairs; + pairs.reserve(obj_size); + + size_t idx, max; + yyjson_mut_val *key, *val; + yyjson_mut_obj_foreach(handle->m_pVal_mut, idx, max, key, val) { + const char* key_str = yyjson_mut_get_str(key); + size_t key_len = yyjson_mut_get_len(key); + pairs.push_back({key, key_str, key_len, val}); + } + + if (sort_mode == JSON_SORT_RANDOM) { + std::shuffle(pairs.begin(), pairs.end(), m_randomGenerator); + } + else { + auto compare = [sort_mode](const KeyValuePair& a, const KeyValuePair& b) { + size_t min_len = a.key_len < b.key_len ? a.key_len : b.key_len; + int cmp = memcmp(a.key_str, b.key_str, min_len); + if (cmp == 0) { + cmp = (a.key_len < b.key_len) ? -1 : (a.key_len > b.key_len ? 1 : 0); + } + return sort_mode == JSON_SORT_ASC ? cmp < 0 : cmp > 0; + }; + + std::sort(pairs.begin(), pairs.end(), compare); + } + + yyjson_mut_obj_clear(handle->m_pVal_mut); + + for (const auto& pair : pairs) { + yyjson_mut_obj_add(handle->m_pVal_mut, pair.key, pair.val); + } + + return true; +} + +JsonValue* JsonManager::ArrayInit() +{ + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_arr(pJSONValue->m_pDocument_mut->get()); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ArrayInitWithStrings(const char** strings, size_t count) +{ + if (!strings) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_arr_with_strcpy( + pJSONValue->m_pDocument_mut->get(), + strings, + count + ); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ArrayInitWithInt32(const int32_t* values, size_t count) +{ + if (!values) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_arr_with_sint32( + pJSONValue->m_pDocument_mut->get(), + values, + count + ); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ArrayInitWithInt64(const char** values, size_t count, char* error, size_t error_size) +{ + if (!values) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid values parameter"); + } + return nullptr; + } + + if (count == 0) { + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_arr(pJSONValue->m_pDocument_mut->get()); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create document"); + } + return nullptr; + } + + auto doc = pJSONValue->m_pDocument_mut->get(); + pJSONValue->m_pVal_mut = yyjson_mut_arr(doc); + + if (!pJSONValue->m_pVal_mut) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create array"); + } + return nullptr; + } + + for (size_t i = 0; i < count; i++) { + std::variant variant_value; + if (!ParseInt64Variant(values[i], &variant_value, error, error_size)) { + return nullptr; + } + + yyjson_mut_val* val = std::visit([&](auto&& arg) -> yyjson_mut_val* { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return yyjson_mut_sint(doc, arg); + } else if constexpr (std::is_same_v) { + return yyjson_mut_uint(doc, arg); + } + return nullptr; + }, variant_value); + + if (!val || !yyjson_mut_arr_append(pJSONValue->m_pVal_mut, val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to append value at index %zu", i); + } + return nullptr; + } + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ArrayInitWithBool(const bool* values, size_t count) +{ + if (!values) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_arr_with_bool( + pJSONValue->m_pDocument_mut->get(), + values, + count + ); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ArrayInitWithFloat(const double* values, size_t count) +{ + if (!values) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_arr_with_real( + pJSONValue->m_pDocument_mut->get(), + values, + count + ); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ArrayParseString(const char* str, yyjson_read_flag read_flg, + char* error, size_t error_size) +{ + if (!str) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid string"); + } + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + + yyjson_read_err readError; + yyjson_doc* idoc = yyjson_read_opts(const_cast(str), strlen(str), read_flg, nullptr, &readError); + + if (!idoc || readError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to parse JSON string: %s (error code: %u, position: %zu)", + readError.msg, readError.code, readError.pos); + } + if (idoc) { + yyjson_doc_free(idoc); + } + return nullptr; + } + + yyjson_val* root = yyjson_doc_get_root(idoc); + + if (!yyjson_is_arr(root)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Root value is not an array (got %s)", yyjson_get_type_desc(root)); + } + yyjson_doc_free(idoc); + return nullptr; + } + + pJSONValue->m_readSize = yyjson_doc_get_read_size(idoc); + pJSONValue->m_pDocument = WrapImmutableDocument(idoc); + pJSONValue->m_pVal = root; + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ArrayParseFile(const char* path, yyjson_read_flag read_flg, + char* error, size_t error_size) +{ + if (!path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid path"); + } + return nullptr; + } + + char realpath[PLATFORM_MAX_PATH]; + smutils->BuildPath(Path_Game, realpath, sizeof(realpath), "%s", path); + auto pJSONValue = CreateWrapper(); + + yyjson_read_err readError; + yyjson_doc* idoc = yyjson_read_file(realpath, read_flg, nullptr, &readError); + + if (!idoc || readError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to parse JSON file: %s (error code: %u, msg: %s, position: %zu)", + realpath, readError.code, readError.msg, readError.pos); + } + if (idoc) { + yyjson_doc_free(idoc); + } + return nullptr; + } + + yyjson_val* root = yyjson_doc_get_root(idoc); + + if (!yyjson_is_arr(root)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Root value in file is not an array (got %s)", yyjson_get_type_desc(root)); + } + yyjson_doc_free(idoc); + return nullptr; + } + + pJSONValue->m_readSize = yyjson_doc_get_read_size(idoc); + pJSONValue->m_pDocument = WrapImmutableDocument(idoc); + pJSONValue->m_pVal = root; + + return pJSONValue.release(); +} + +size_t JsonManager::ArrayGetSize(JsonValue* handle) +{ + if (!handle) { + return 0; + } + + if (handle->IsMutable()) { + return yyjson_mut_arr_size(handle->m_pVal_mut); + } else { + return yyjson_arr_size(handle->m_pVal); + } +} + +JsonValue* JsonManager::ArrayGet(JsonValue* handle, size_t index) +{ + if (!handle) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + + if (handle->IsMutable()) { + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return nullptr; + } + + yyjson_mut_val* val = yyjson_mut_arr_get(handle->m_pVal_mut, index); + if (!val) { + return nullptr; + } + + pJSONValue->m_pDocument_mut = handle->m_pDocument_mut; + pJSONValue->m_pVal_mut = val; + } else { + size_t arr_size = yyjson_arr_size(handle->m_pVal); + if (index >= arr_size) { + return nullptr; + } + + yyjson_val* val = yyjson_arr_get(handle->m_pVal, index); + if (!val) { + return nullptr; + } + + pJSONValue->m_pDocument = handle->m_pDocument; + pJSONValue->m_pVal = val; + } + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ArrayGetFirst(JsonValue* handle) +{ + if (!handle) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + + if (handle->IsMutable()) { + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (arr_size == 0) { + return nullptr; + } + + yyjson_mut_val* val = yyjson_mut_arr_get_first(handle->m_pVal_mut); + if (!val) { + return nullptr; + } + + pJSONValue->m_pDocument_mut = handle->m_pDocument_mut; + pJSONValue->m_pVal_mut = val; + } else { + size_t arr_size = yyjson_arr_size(handle->m_pVal); + if (arr_size == 0) { + return nullptr; + } + + yyjson_val* val = yyjson_arr_get_first(handle->m_pVal); + if (!val) { + return nullptr; + } + + pJSONValue->m_pDocument = handle->m_pDocument; + pJSONValue->m_pVal = val; + } + + return pJSONValue.release(); +} + +JsonValue* JsonManager::ArrayGetLast(JsonValue* handle) +{ + if (!handle) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + + if (handle->IsMutable()) { + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (arr_size == 0) { + return nullptr; + } + + yyjson_mut_val* val = yyjson_mut_arr_get_last(handle->m_pVal_mut); + if (!val) { + return nullptr; + } + + pJSONValue->m_pDocument_mut = handle->m_pDocument_mut; + pJSONValue->m_pVal_mut = val; + } else { + size_t arr_size = yyjson_arr_size(handle->m_pVal); + if (arr_size == 0) { + return nullptr; + } + + yyjson_val* val = yyjson_arr_get_last(handle->m_pVal); + if (!val) { + return nullptr; + } + + pJSONValue->m_pDocument = handle->m_pDocument; + pJSONValue->m_pVal = val; + } + + return pJSONValue.release(); +} + +bool JsonManager::ArrayGetBool(JsonValue* handle, size_t index, bool* out_value) +{ + if (!handle || !out_value) { + return false; + } + + if (handle->IsMutable()) { + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + yyjson_mut_val* val = yyjson_mut_arr_get(handle->m_pVal_mut, index); + if (!yyjson_mut_is_bool(val)) { + return false; + } + + *out_value = yyjson_mut_get_bool(val); + return true; + } else { + size_t arr_size = yyjson_arr_size(handle->m_pVal); + if (index >= arr_size) { + return false; + } + + yyjson_val* val = yyjson_arr_get(handle->m_pVal, index); + if (!yyjson_is_bool(val)) { + return false; + } + + *out_value = yyjson_get_bool(val); + return true; + } +} + +bool JsonManager::ArrayGetFloat(JsonValue* handle, size_t index, double* out_value) +{ + if (!handle || !out_value) { + return false; + } + + if (handle->IsMutable()) { + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + yyjson_mut_val* val = yyjson_mut_arr_get(handle->m_pVal_mut, index); + if (!yyjson_mut_is_real(val)) { + return false; + } + + *out_value = yyjson_mut_get_real(val); + return true; + } else { + size_t arr_size = yyjson_arr_size(handle->m_pVal); + if (index >= arr_size) { + return false; + } + + yyjson_val* val = yyjson_arr_get(handle->m_pVal, index); + if (!yyjson_is_real(val)) { + return false; + } + + *out_value = yyjson_get_real(val); + return true; + } +} + +bool JsonManager::ArrayGetInt(JsonValue* handle, size_t index, int* out_value) +{ + if (!handle || !out_value) { + return false; + } + + if (handle->IsMutable()) { + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + yyjson_mut_val* val = yyjson_mut_arr_get(handle->m_pVal_mut, index); + if (!yyjson_mut_is_int(val)) { + return false; + } + + *out_value = yyjson_mut_get_int(val); + return true; + } else { + size_t arr_size = yyjson_arr_size(handle->m_pVal); + if (index >= arr_size) { + return false; + } + + yyjson_val* val = yyjson_arr_get(handle->m_pVal, index); + if (!yyjson_is_int(val)) { + return false; + } + + *out_value = yyjson_get_int(val); + return true; + } +} + +bool JsonManager::ArrayGetInt64(JsonValue* handle, size_t index, std::variant* out_value) +{ + if (!handle || !out_value) { + return false; + } + + if (handle->IsMutable()) { + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + yyjson_mut_val* val = yyjson_mut_arr_get(handle->m_pVal_mut, index); + if (!yyjson_mut_is_int(val)) { + return false; + } + + ReadInt64FromMutVal(val, out_value); + return true; + } else { + size_t arr_size = yyjson_arr_size(handle->m_pVal); + if (index >= arr_size) { + return false; + } + + yyjson_val* val = yyjson_arr_get(handle->m_pVal, index); + if (!yyjson_is_int(val)) { + return false; + } + + ReadInt64FromVal(val, out_value); + return true; + } +} + +bool JsonManager::ArrayGetString(JsonValue* handle, size_t index, const char** out_str, size_t* out_len) +{ + if (!handle || !out_str) { + return false; + } + + if (handle->IsMutable()) { + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + yyjson_mut_val* val = yyjson_mut_arr_get(handle->m_pVal_mut, index); + if (!yyjson_mut_is_str(val)) { + return false; + } + + *out_str = yyjson_mut_get_str(val); + if (out_len) { + *out_len = yyjson_mut_get_len(val); + } + return true; + } else { + size_t arr_size = yyjson_arr_size(handle->m_pVal); + if (index >= arr_size) { + return false; + } + + yyjson_val* val = yyjson_arr_get(handle->m_pVal, index); + if (!yyjson_is_str(val)) { + return false; + } + + *out_str = yyjson_get_str(val); + if (out_len) { + *out_len = yyjson_get_len(val); + } + return true; + } +} + +bool JsonManager::ArrayIsNull(JsonValue* handle, size_t index) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + yyjson_mut_val* val = yyjson_mut_arr_get(handle->m_pVal_mut, index); + return yyjson_mut_is_null(val); + } else { + size_t arr_size = yyjson_arr_size(handle->m_pVal); + if (index >= arr_size) { + return false; + } + + yyjson_val* val = yyjson_arr_get(handle->m_pVal, index); + return yyjson_is_null(val); + } +} + +bool JsonManager::ArrayReplace(JsonValue* handle, size_t index, JsonValue* value) +{ + if (!handle || !handle->IsMutable() || !value) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + yyjson_mut_val* val_copy; + if (value->IsMutable()) { + val_copy = yyjson_mut_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal_mut); + } else { + val_copy = yyjson_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal); + } + + if (!val_copy) { + return false; + } + + return yyjson_mut_arr_replace(handle->m_pVal_mut, index, val_copy) != nullptr; +} + +bool JsonManager::ArrayReplaceBool(JsonValue* handle, size_t index, bool value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + return yyjson_mut_arr_replace(handle->m_pVal_mut, index, yyjson_mut_bool(handle->m_pDocument_mut->get(), value)) != nullptr; +} + +bool JsonManager::ArrayReplaceFloat(JsonValue* handle, size_t index, double value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + return yyjson_mut_arr_replace(handle->m_pVal_mut, index, yyjson_mut_real(handle->m_pDocument_mut->get(), value)) != nullptr; +} + +bool JsonManager::ArrayReplaceInt(JsonValue* handle, size_t index, int value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + return yyjson_mut_arr_replace(handle->m_pVal_mut, index, yyjson_mut_int(handle->m_pDocument_mut->get(), value)) != nullptr; +} + +bool JsonManager::ArrayReplaceInt64(JsonValue* handle, size_t index, std::variant value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + return std::visit([&](auto&& val) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return yyjson_mut_arr_replace(handle->m_pVal_mut, index, yyjson_mut_sint(handle->m_pDocument_mut->get(), val)) != nullptr; + } else if constexpr (std::is_same_v) { + return yyjson_mut_arr_replace(handle->m_pVal_mut, index, yyjson_mut_uint(handle->m_pDocument_mut->get(), val)) != nullptr; + } + return false; + }, value); +} + +bool JsonManager::ArrayReplaceNull(JsonValue* handle, size_t index) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + return yyjson_mut_arr_replace(handle->m_pVal_mut, index, yyjson_mut_null(handle->m_pDocument_mut->get())) != nullptr; +} + +bool JsonManager::ArrayReplaceString(JsonValue* handle, size_t index, const char* value) +{ + if (!handle || !handle->IsMutable() || !value) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + return yyjson_mut_arr_replace(handle->m_pVal_mut, index, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), value)) != nullptr; +} + +bool JsonManager::ArrayAppend(JsonValue* handle, JsonValue* value) +{ + if (!handle || !handle->IsMutable() || !value) { + return false; + } + + yyjson_mut_val* val_copy; + if (value->IsMutable()) { + val_copy = yyjson_mut_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal_mut); + } else { + val_copy = yyjson_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal); + } + + if (!val_copy) { + return false; + } + + return yyjson_mut_arr_append(handle->m_pVal_mut, val_copy); +} + +bool JsonManager::ArrayAppendBool(JsonValue* handle, bool value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_append(handle->m_pVal_mut, yyjson_mut_bool(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ArrayAppendFloat(JsonValue* handle, double value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_append(handle->m_pVal_mut, yyjson_mut_real(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ArrayAppendInt(JsonValue* handle, int value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_append(handle->m_pVal_mut, yyjson_mut_int(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ArrayAppendInt64(JsonValue* handle, std::variant value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return std::visit([&](auto&& val) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return yyjson_mut_arr_append(handle->m_pVal_mut, yyjson_mut_sint(handle->m_pDocument_mut->get(), val)); + } else if constexpr (std::is_same_v) { + return yyjson_mut_arr_append(handle->m_pVal_mut, yyjson_mut_uint(handle->m_pDocument_mut->get(), val)); + } + return false; + }, value); +} + +bool JsonManager::ArrayAppendNull(JsonValue* handle) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_append(handle->m_pVal_mut, yyjson_mut_null(handle->m_pDocument_mut->get())); +} + +bool JsonManager::ArrayAppendString(JsonValue* handle, const char* value) +{ + if (!handle || !handle->IsMutable() || !value) { + return false; + } + + return yyjson_mut_arr_append(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ArrayInsert(JsonValue* handle, size_t index, JsonValue* value) +{ + if (!handle || !handle->IsMutable() || !value) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index > arr_size) { + return false; + } + + yyjson_mut_val* val_copy; + if (value->IsMutable()) { + val_copy = yyjson_mut_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal_mut); + } else { + val_copy = yyjson_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal); + } + + if (!val_copy) { + return false; + } + + return yyjson_mut_arr_insert(handle->m_pVal_mut, val_copy, index); +} + +bool JsonManager::ArrayInsertBool(JsonValue* handle, size_t index, bool value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_insert(handle->m_pVal_mut, yyjson_mut_bool(handle->m_pDocument_mut->get(), value), index); +} + +bool JsonManager::ArrayInsertInt(JsonValue* handle, size_t index, int value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_insert(handle->m_pVal_mut, yyjson_mut_sint(handle->m_pDocument_mut->get(), value), index); +} + +bool JsonManager::ArrayInsertInt64(JsonValue* handle, size_t index, std::variant value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + yyjson_mut_val* val = std::visit([&](auto&& arg) -> yyjson_mut_val* { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return yyjson_mut_sint(handle->m_pDocument_mut->get(), arg); + } else if constexpr (std::is_same_v) { + return yyjson_mut_uint(handle->m_pDocument_mut->get(), arg); + } + return nullptr; + }, value); + + if (!val) { + return false; + } + + return yyjson_mut_arr_insert(handle->m_pVal_mut, val, index); +} + +bool JsonManager::ArrayInsertFloat(JsonValue* handle, size_t index, double value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_insert(handle->m_pVal_mut, yyjson_mut_real(handle->m_pDocument_mut->get(), value), index); +} + +bool JsonManager::ArrayInsertString(JsonValue* handle, size_t index, const char* value) +{ + if (!handle || !handle->IsMutable() || !value) { + return false; + } + + return yyjson_mut_arr_insert(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), value), index); +} + +bool JsonManager::ArrayInsertNull(JsonValue* handle, size_t index) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_insert(handle->m_pVal_mut, yyjson_mut_null(handle->m_pDocument_mut->get()), index); +} + +bool JsonManager::ArrayPrepend(JsonValue* handle, JsonValue* value) +{ + if (!handle || !handle->IsMutable() || !value) { + return false; + } + + yyjson_mut_val* val_copy; + if (value->IsMutable()) { + val_copy = yyjson_mut_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal_mut); + } else { + val_copy = yyjson_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal); + } + + if (!val_copy) { + return false; + } + + return yyjson_mut_arr_prepend(handle->m_pVal_mut, val_copy); +} + +bool JsonManager::ArrayPrependBool(JsonValue* handle, bool value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_prepend(handle->m_pVal_mut, yyjson_mut_bool(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ArrayPrependInt(JsonValue* handle, int value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_prepend(handle->m_pVal_mut, yyjson_mut_sint(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ArrayPrependInt64(JsonValue* handle, std::variant value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + yyjson_mut_val* val = std::visit([&](auto&& arg) -> yyjson_mut_val* { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return yyjson_mut_sint(handle->m_pDocument_mut->get(), arg); + } else if constexpr (std::is_same_v) { + return yyjson_mut_uint(handle->m_pDocument_mut->get(), arg); + } + return nullptr; + }, value); + + if (!val) { + return false; + } + + return yyjson_mut_arr_prepend(handle->m_pVal_mut, val); +} + +bool JsonManager::ArrayPrependFloat(JsonValue* handle, double value) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_prepend(handle->m_pVal_mut, yyjson_mut_real(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ArrayPrependString(JsonValue* handle, const char* value) +{ + if (!handle || !handle->IsMutable() || !value) { + return false; + } + + return yyjson_mut_arr_prepend(handle->m_pVal_mut, yyjson_mut_strcpy(handle->m_pDocument_mut->get(), value)); +} + +bool JsonManager::ArrayPrependNull(JsonValue* handle) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_prepend(handle->m_pVal_mut, yyjson_mut_null(handle->m_pDocument_mut->get())); +} + +bool JsonManager::ArrayRemove(JsonValue* handle, size_t index) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (index >= arr_size) { + return false; + } + + return yyjson_mut_arr_remove(handle->m_pVal_mut, index) != nullptr; +} + +bool JsonManager::ArrayRemoveFirst(JsonValue* handle) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + if (yyjson_mut_arr_size(handle->m_pVal_mut) == 0) { + return false; + } + + return yyjson_mut_arr_remove_first(handle->m_pVal_mut) != nullptr; +} + +bool JsonManager::ArrayRemoveLast(JsonValue* handle) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + if (yyjson_mut_arr_size(handle->m_pVal_mut) == 0) { + return false; + } + + return yyjson_mut_arr_remove_last(handle->m_pVal_mut) != nullptr; +} + +bool JsonManager::ArrayRemoveRange(JsonValue* handle, size_t start_index, size_t count) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + + if (start_index >= arr_size) { + return false; + } + + if (count == 0) { + return true; + } + + if (count > (arr_size - start_index)) { + return false; + } + + return yyjson_mut_arr_remove_range(handle->m_pVal_mut, start_index, count); +} + +bool JsonManager::ArrayClear(JsonValue* handle) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + return yyjson_mut_arr_clear(handle->m_pVal_mut); +} + +int JsonManager::ArrayIndexOfBool(JsonValue* handle, bool search_value) +{ + if (!handle) { + return -1; + } + + if (handle->IsMutable()) { + size_t idx, max; + yyjson_mut_val *val; + yyjson_mut_arr_foreach(handle->m_pVal_mut, idx, max, val) { + if (yyjson_mut_is_bool(val) && yyjson_mut_get_bool(val) == search_value) { + return static_cast(idx); + } + } + } else { + size_t idx, max; + yyjson_val *val; + yyjson_arr_foreach(handle->m_pVal, idx, max, val) { + if (yyjson_is_bool(val) && yyjson_get_bool(val) == search_value) { + return static_cast(idx); + } + } + } + + return -1; +} + +int JsonManager::ArrayIndexOfString(JsonValue* handle, const char* search_value) +{ + if (!handle || !search_value) { + return -1; + } + + if (handle->IsMutable()) { + size_t idx, max; + yyjson_mut_val *val; + yyjson_mut_arr_foreach(handle->m_pVal_mut, idx, max, val) { + if (yyjson_mut_is_str(val) && strcmp(yyjson_mut_get_str(val), search_value) == 0) { + return static_cast(idx); + } + } + } else { + size_t idx, max; + yyjson_val *val; + yyjson_arr_foreach(handle->m_pVal, idx, max, val) { + if (yyjson_is_str(val) && strcmp(yyjson_get_str(val), search_value) == 0) { + return static_cast(idx); + } + } + } + + return -1; +} + +int JsonManager::ArrayIndexOfInt(JsonValue* handle, int search_value) +{ + if (!handle) { + return -1; + } + + if (handle->IsMutable()) { + size_t idx, max; + yyjson_mut_val *val; + yyjson_mut_arr_foreach(handle->m_pVal_mut, idx, max, val) { + if (yyjson_mut_is_int(val) && yyjson_mut_get_int(val) == search_value) { + return static_cast(idx); + } + } + } else { + size_t idx, max; + yyjson_val *val; + yyjson_arr_foreach(handle->m_pVal, idx, max, val) { + if (yyjson_is_int(val) && yyjson_get_int(val) == search_value) { + return static_cast(idx); + } + } + } + + return -1; +} + +int JsonManager::ArrayIndexOfInt64(JsonValue* handle, std::variant search_value) +{ + if (!handle) { + return -1; + } + + bool is_unsigned = std::holds_alternative(search_value); + + if (handle->IsMutable()) { + size_t idx, max; + yyjson_mut_val *val; + yyjson_mut_arr_foreach(handle->m_pVal_mut, idx, max, val) { + if (yyjson_mut_is_int(val)) { + if (is_unsigned) { + if (yyjson_mut_get_uint(val) == std::get(search_value)) { + return static_cast(idx); + } + } else { + if (yyjson_mut_get_sint(val) == std::get(search_value)) { + return static_cast(idx); + } + } + } + } + } else { + size_t idx, max; + yyjson_val *val; + yyjson_arr_foreach(handle->m_pVal, idx, max, val) { + if (yyjson_is_int(val)) { + if (is_unsigned) { + if (yyjson_get_uint(val) == std::get(search_value)) { + return static_cast(idx); + } + } else { + if (yyjson_get_sint(val) == std::get(search_value)) { + return static_cast(idx); + } + } + } + } + } + + return -1; +} + +int JsonManager::ArrayIndexOfFloat(JsonValue* handle, double search_value) +{ + if (!handle) { + return -1; + } + + if (handle->IsMutable()) { + size_t idx, max; + yyjson_mut_val *val; + yyjson_mut_arr_foreach(handle->m_pVal_mut, idx, max, val) { + if (yyjson_mut_is_real(val)) { + double val_num = yyjson_mut_get_real(val); + if (yyjson_equals_fp(val_num, search_value)) { + return static_cast(idx); + } + } + } + } else { + size_t idx, max; + yyjson_val *val; + yyjson_arr_foreach(handle->m_pVal, idx, max, val) { + if (yyjson_is_real(val)) { + double val_num = yyjson_get_real(val); + if (yyjson_equals_fp(val_num, search_value)) { + return static_cast(idx); + } + } + } + } + + return -1; +} + +bool JsonManager::ArraySort(JsonValue* handle, JSON_SORT_ORDER sort_mode) +{ + if (!handle || !handle->IsMutable()) { + return false; + } + + if (!yyjson_mut_is_arr(handle->m_pVal_mut)) { + return false; + } + + if (sort_mode < JSON_SORT_ASC || sort_mode > JSON_SORT_RANDOM) { + return false; + } + + size_t arr_size = yyjson_mut_arr_size(handle->m_pVal_mut); + if (arr_size <= 1) return true; + + struct ValueInfo { + yyjson_mut_val* val; + uint8_t type; + uint8_t subtype; // 0=float, 1=signed int, 2=unsigned int + }; + + std::vector values; + values.reserve(arr_size); + + size_t idx, max; + yyjson_mut_val *val; + yyjson_mut_arr_foreach(handle->m_pVal_mut, idx, max, val) { + uint8_t type = yyjson_mut_get_type(val); + uint8_t subtype = 0; + + if (type == YYJSON_TYPE_NUM) { + if (yyjson_mut_is_int(val)) { + subtype = yyjson_mut_is_sint(val) ? 1 : 2; + } + } + + values.push_back({val, type, subtype}); + } + + if (sort_mode == JSON_SORT_RANDOM) { + std::shuffle(values.begin(), values.end(), m_randomGenerator); + } + else { + auto compare = [sort_mode](const ValueInfo& a, const ValueInfo& b) { + if (a.val == b.val) return false; + + if (a.type != b.type) { + return sort_mode == JSON_SORT_ASC ? a.type < b.type : a.type > b.type; + } + + switch (a.type) { + case YYJSON_TYPE_STR: { + const char* str_a = yyjson_mut_get_str(a.val); + const char* str_b = yyjson_mut_get_str(b.val); + int cmp = strcmp(str_a, str_b); + return sort_mode == JSON_SORT_ASC ? cmp < 0 : cmp > 0; + } + case YYJSON_TYPE_NUM: { + if (a.subtype > 0 && b.subtype > 0) { + if (a.subtype == 1 && b.subtype == 1) { + int64_t num_a = yyjson_mut_get_sint(a.val); + int64_t num_b = yyjson_mut_get_sint(b.val); + return sort_mode == JSON_SORT_ASC ? num_a < num_b : num_a > num_b; + } + else if (a.subtype == 2 && b.subtype == 2) { + uint64_t num_a = yyjson_mut_get_uint(a.val); + uint64_t num_b = yyjson_mut_get_uint(b.val); + return sort_mode == JSON_SORT_ASC ? num_a < num_b : num_a > num_b; + } + else { + int64_t signed_val; + uint64_t unsigned_val; + bool a_is_signed = (a.subtype == 1); + + if (a_is_signed) { + signed_val = yyjson_mut_get_sint(a.val); + unsigned_val = yyjson_mut_get_uint(b.val); + + if (signed_val < 0) { + return sort_mode == JSON_SORT_ASC; + } + uint64_t a_as_unsigned = static_cast(signed_val); + return sort_mode == JSON_SORT_ASC ? + a_as_unsigned < unsigned_val : + a_as_unsigned > unsigned_val; + } else { + unsigned_val = yyjson_mut_get_uint(a.val); + signed_val = yyjson_mut_get_sint(b.val); + + if (signed_val < 0) { + return sort_mode == JSON_SORT_DESC; + } + uint64_t b_as_unsigned = static_cast(signed_val); + return sort_mode == JSON_SORT_ASC ? + unsigned_val < b_as_unsigned : + unsigned_val > b_as_unsigned; + } + } + } + double num_a = yyjson_mut_get_num(a.val); + double num_b = yyjson_mut_get_num(b.val); + return sort_mode == JSON_SORT_ASC ? num_a < num_b : num_a > num_b; + } + case YYJSON_TYPE_BOOL: { + bool val_a = yyjson_mut_get_bool(a.val); + bool val_b = yyjson_mut_get_bool(b.val); + return sort_mode == JSON_SORT_ASC ? val_a < val_b : val_a > val_b; + } + default: + return false; + } + }; + + std::sort(values.begin(), values.end(), compare); + } + + yyjson_mut_arr_clear(handle->m_pVal_mut); + for (const auto& info : values) { + yyjson_mut_arr_append(handle->m_pVal_mut, info.val); + } + + return true; +} + +const char* JsonManager::SkipSeparators(const char* ptr) +{ + while (*ptr && (isspace(*ptr) || *ptr == ':' || *ptr == ',')) { + ptr++; + } + return ptr; +} + +yyjson_mut_val* JsonManager::PackImpl(yyjson_mut_doc* doc, const char* format, + IPackParamProvider* provider, + char* error, size_t error_size, + const char** out_end_ptr) +{ + if (!doc || !format || !*format) { + SetErrorSafe(error, error_size, "Invalid argument(s)"); + return nullptr; + } + + yyjson_mut_val* root; + const char* ptr = format; + + bool is_obj = false; + if (*ptr == '{') { + root = yyjson_mut_obj(doc); + is_obj = true; + ptr = SkipSeparators(ptr + 1); + } else if (*ptr == '[') { + root = yyjson_mut_arr(doc); + ptr = SkipSeparators(ptr + 1); + } else { + SetErrorSafe(error, error_size, "Invalid format string: expected '{' or '['"); + return nullptr; + } + + if (!root) { + SetErrorSafe(error, error_size, "Failed to create root object/array"); + return nullptr; + } + + yyjson_mut_val* key_val; + yyjson_mut_val* val; + + while (*ptr && *ptr != '}' && *ptr != ']') { + if (is_obj) { + if (*ptr != 's') { + SetErrorSafe(error, error_size, "Object key must be string, got '%c'", *ptr); + return nullptr; + } + } + switch (*ptr) { + case 's': { + if (is_obj) { + const char* key; + if (!provider->GetNextString(&key)) { + SetErrorSafe(error, error_size, "Invalid string key"); + return nullptr; + } + key_val = yyjson_mut_strcpy(doc, key); + if (!key_val) { + SetErrorSafe(error, error_size, "Failed to create key"); + return nullptr; + } + + ptr = SkipSeparators(ptr + 1); + if (*ptr != 's' && *ptr != 'i' && *ptr != 'f' && *ptr != 'b' && + *ptr != 'n' && *ptr != '{' && *ptr != '[') { + SetErrorSafe(error, error_size, "Invalid value type after key"); + return nullptr; + } + + if (*ptr == '{' || *ptr == '[') { + val = PackImpl(doc, ptr, provider, error, error_size, &ptr); + if (!val) { + return nullptr; + } + } else { + switch (*ptr) { + case 's': { + const char* val_str; + if (!provider->GetNextString(&val_str)) { + SetErrorSafe(error, error_size, "Invalid string value"); + return nullptr; + } + val = yyjson_mut_strcpy(doc, val_str); + ptr++; + break; + } + case 'i': { + int val_int; + if (!provider->GetNextInt(&val_int)) { + SetErrorSafe(error, error_size, "Invalid integer value"); + return nullptr; + } + val = yyjson_mut_int(doc, val_int); + ptr++; + break; + } + case 'f': { + float val_float; + if (!provider->GetNextFloat(&val_float)) { + SetErrorSafe(error, error_size, "Invalid float value"); + return nullptr; + } + val = yyjson_mut_real(doc, val_float); + ptr++; + break; + } + case 'b': { + bool val_bool; + if (!provider->GetNextBool(&val_bool)) { + SetErrorSafe(error, error_size, "Invalid boolean value"); + return nullptr; + } + val = yyjson_mut_bool(doc, val_bool); + ptr++; + break; + } + case 'n': { + val = yyjson_mut_null(doc); + ptr++; + break; + } + } + } + + if (!val) { + SetErrorSafe(error, error_size, "Failed to create value"); + return nullptr; + } + + if (!yyjson_mut_obj_add(root, key_val, val)) { + SetErrorSafe(error, error_size, "Failed to add value to object"); + return nullptr; + } + } else { + const char* val_str; + if (!provider->GetNextString(&val_str)) { + SetErrorSafe(error, error_size, "Invalid string value"); + return nullptr; + } + if (!yyjson_mut_arr_add_strcpy(doc, root, val_str)) { + SetErrorSafe(error, error_size, "Failed to add string to array"); + return nullptr; + } + ptr++; + } + break; + } + case 'i': { + int val_int; + if (!provider->GetNextInt(&val_int)) { + SetErrorSafe(error, error_size, "Invalid integer value"); + return nullptr; + } + if (!yyjson_mut_arr_add_int(doc, root, val_int)) { + SetErrorSafe(error, error_size, "Failed to add integer to array"); + return nullptr; + } + ptr++; + break; + } + case 'b': { + bool val_bool; + if (!provider->GetNextBool(&val_bool)) { + SetErrorSafe(error, error_size, "Invalid boolean value"); + return nullptr; + } + if (!yyjson_mut_arr_add_bool(doc, root, val_bool)) { + SetErrorSafe(error, error_size, "Failed to add boolean to array"); + return nullptr; + } + ptr++; + break; + } + case 'n': { + if (!yyjson_mut_arr_add_null(doc, root)) { + SetErrorSafe(error, error_size, "Failed to add null to array"); + return nullptr; + } + ptr++; + break; + } + case 'f': { + float val_float; + if (!provider->GetNextFloat(&val_float)) { + SetErrorSafe(error, error_size, "Invalid float value"); + return nullptr; + } + if (!yyjson_mut_arr_add_real(doc, root, val_float)) { + SetErrorSafe(error, error_size, "Failed to add float to array"); + return nullptr; + } + ptr++; + break; + } + case '{': + case '[': { + val = PackImpl(doc, ptr, provider, error, error_size, &ptr); + if (!val) { + return nullptr; + } + if (!yyjson_mut_arr_append(root, val)) { + SetErrorSafe(error, error_size, "Failed to add nested value to array"); + return nullptr; + } + break; + } + default: { + SetErrorSafe(error, error_size, "Invalid format character: %c", *ptr); + return nullptr; + } + } + ptr = SkipSeparators(ptr); + } + + if (*ptr != (is_obj ? '}' : ']')) { + SetErrorSafe(error, error_size, "Unexpected end of format string"); + return nullptr; + } + + if (out_end_ptr) { + *out_end_ptr = ptr + 1; + } + + return root; +} + +JsonValue* JsonManager::Pack(const char* format, IPackParamProvider* param_provider, char* error, size_t error_size) +{ + if (!format || !param_provider) { + SetErrorSafe(error, error_size, "Invalid arguments"); + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + SetErrorSafe(error, error_size, "Failed to create document"); + return nullptr; + } + + const char* end_ptr; + pJSONValue->m_pVal_mut = PackImpl(pJSONValue->m_pDocument_mut->get(), format, + param_provider, error, error_size, &end_ptr); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::CreateBool(bool value) +{ + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_bool(pJSONValue->m_pDocument_mut->get(), value); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::CreateFloat(double value) +{ + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_real(pJSONValue->m_pDocument_mut->get(), value); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::CreateInt(int value) +{ + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_int(pJSONValue->m_pDocument_mut->get(), value); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::CreateInt64(std::variant value) +{ + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + auto* doc = pJSONValue->m_pDocument_mut->get(); + + std::visit([&](auto&& val) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + pJSONValue->m_pVal_mut = yyjson_mut_sint(doc, val); + } else if constexpr (std::is_same_v) { + pJSONValue->m_pVal_mut = yyjson_mut_uint(doc, val); + } + }, value); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::CreateNull() +{ + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_null(pJSONValue->m_pDocument_mut->get()); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +JsonValue* JsonManager::CreateString(const char* value) +{ + if (!value) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + return nullptr; + } + + pJSONValue->m_pVal_mut = yyjson_mut_strcpy(pJSONValue->m_pDocument_mut->get(), value); + + if (!pJSONValue->m_pVal_mut) { + return nullptr; + } + + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), pJSONValue->m_pVal_mut); + + return pJSONValue.release(); +} + +bool JsonManager::GetBool(JsonValue* handle, bool* out_value) +{ + if (!handle || !out_value) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_bool(handle->m_pVal_mut)) { + return false; + } + *out_value = yyjson_mut_get_bool(handle->m_pVal_mut); + return true; + } else { + if (!yyjson_is_bool(handle->m_pVal)) { + return false; + } + *out_value = yyjson_get_bool(handle->m_pVal); + return true; + } +} + +bool JsonManager::GetFloat(JsonValue* handle, double* out_value) +{ + if (!handle || !out_value) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_real(handle->m_pVal_mut)) { + return false; + } + *out_value = yyjson_mut_get_real(handle->m_pVal_mut); + return true; + } else { + if (!yyjson_is_real(handle->m_pVal)) { + return false; + } + *out_value = yyjson_get_real(handle->m_pVal); + return true; + } +} + +bool JsonManager::GetInt(JsonValue* handle, int* out_value) +{ + if (!handle || !out_value) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_int(handle->m_pVal_mut)) { + return false; + } + *out_value = yyjson_mut_get_int(handle->m_pVal_mut); + return true; + } else { + if (!yyjson_is_int(handle->m_pVal)) { + return false; + } + *out_value = yyjson_get_int(handle->m_pVal); + return true; + } +} + +bool JsonManager::GetInt64(JsonValue* handle, std::variant* out_value) +{ + if (!handle || !out_value) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_int(handle->m_pVal_mut)) { + return false; + } + ReadInt64FromMutVal(handle->m_pVal_mut, out_value); + return true; + } else { + if (!yyjson_is_int(handle->m_pVal)) { + return false; + } + ReadInt64FromVal(handle->m_pVal, out_value); + return true; + } +} + +bool JsonManager::GetString(JsonValue* handle, const char** out_str, size_t* out_len) +{ + if (!handle || !out_str) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_str(handle->m_pVal_mut)) { + return false; + } + *out_str = yyjson_mut_get_str(handle->m_pVal_mut); + if (out_len) { + *out_len = yyjson_mut_get_len(handle->m_pVal_mut); + } + return true; + } else { + if (!yyjson_is_str(handle->m_pVal)) { + return false; + } + *out_str = yyjson_get_str(handle->m_pVal); + if (out_len) { + *out_len = yyjson_get_len(handle->m_pVal); + } + return true; + } +} + +JsonValue* JsonManager::PtrGet(JsonValue* handle, const char* path, char* error, size_t error_size) +{ + if (!handle || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters"); + } + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + yyjson_ptr_err ptrGetError; + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_doc_ptr_getx(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrGetError); + + if (!val || ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return nullptr; + } + + pJSONValue->m_pDocument_mut = handle->m_pDocument_mut; + pJSONValue->m_pVal_mut = val; + } else { + yyjson_val* val = yyjson_doc_ptr_getx(handle->m_pDocument->get(), path, strlen(path), &ptrGetError); + + if (!val || ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return nullptr; + } + + pJSONValue->m_pDocument = handle->m_pDocument; + pJSONValue->m_pVal = val; + } + + return pJSONValue.release(); +} + +bool JsonManager::PtrGetBool(JsonValue* handle, const char* path, bool* out_value, char* error, size_t error_size) +{ + if (!handle || !path || !out_value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters"); + } + return false; + } + + yyjson_ptr_err ptrGetError; + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_doc_ptr_getx(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_mut_is_bool(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected boolean value, got %s", path, yyjson_mut_get_type_desc(val)); + } + return false; + } + + *out_value = yyjson_mut_get_bool(val); + return true; + } else { + yyjson_val* val = yyjson_doc_ptr_getx(handle->m_pDocument->get(), path, strlen(path), &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_is_bool(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected boolean value, got %s", path, yyjson_get_type_desc(val)); + } + return false; + } + + *out_value = yyjson_get_bool(val); + return true; + } +} + +bool JsonManager::PtrGetFloat(JsonValue* handle, const char* path, double* out_value, char* error, size_t error_size) +{ + if (!handle || !path || !out_value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters"); + } + return false; + } + + yyjson_ptr_err ptrGetError; + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_doc_ptr_getx(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_mut_is_real(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected float value, got %s", path, yyjson_mut_get_type_desc(val)); + } + return false; + } + + *out_value = yyjson_mut_get_real(val); + return true; + } else { + yyjson_val* val = yyjson_doc_ptr_getx(handle->m_pDocument->get(), path, strlen(path), &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_is_real(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected float value, got %s", path, yyjson_get_type_desc(val)); + } + return false; + } + + *out_value = yyjson_get_real(val); + return true; + } +} + +bool JsonManager::PtrGetInt(JsonValue* handle, const char* path, int* out_value, char* error, size_t error_size) +{ + if (!handle || !path || !out_value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters"); + } + return false; + } + + yyjson_ptr_err ptrGetError; + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_doc_ptr_getx(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_mut_is_int(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected integer value, got %s", path, yyjson_mut_get_type_desc(val)); + } + return false; + } + + *out_value = yyjson_mut_get_int(val); + return true; + } else { + yyjson_val* val = yyjson_doc_ptr_getx(handle->m_pDocument->get(), path, strlen(path), &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_is_int(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected integer value, got %s", path, yyjson_get_type_desc(val)); + } + return false; + } + + *out_value = yyjson_get_int(val); + return true; + } +} + +bool JsonManager::PtrGetInt64(JsonValue* handle, const char* path, std::variant* out_value, char* error, size_t error_size) +{ + if (!handle || !path || !out_value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters"); + } + return false; + } + + yyjson_ptr_err ptrGetError; + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_doc_ptr_getx(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrGetError); + + if (!val || ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_mut_is_int(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected integer64 value, got %s", path, yyjson_mut_get_type_desc(val)); + } + return false; + } + + ReadInt64FromMutVal(val, out_value); + return true; + } else { + yyjson_val* val = yyjson_doc_ptr_getx(handle->m_pDocument->get(), path, strlen(path), &ptrGetError); + + if (!val || ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_is_int(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected integer64 value, got %s", path, yyjson_get_type_desc(val)); + } + return false; + } + + ReadInt64FromVal(val, out_value); + return true; + } +} + +bool JsonManager::PtrGetString(JsonValue* handle, const char* path, const char** out_str, size_t* out_len, char* error, size_t error_size) +{ + if (!handle || !path || !out_str) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters"); + } + return false; + } + + yyjson_ptr_err ptrGetError; + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_doc_ptr_getx(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_mut_is_str(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected string value, got %s", path, yyjson_mut_get_type_desc(val)); + } + return false; + } + + *out_str = yyjson_mut_get_str(val); + if (out_len) { + *out_len = yyjson_mut_get_len(val); + } + return true; + } else { + yyjson_val* val = yyjson_doc_ptr_getx(handle->m_pDocument->get(), path, strlen(path), &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (!yyjson_is_str(val)) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Type mismatch at path '%s': expected string value, got %s", path, yyjson_get_type_desc(val)); + } + return false; + } + + *out_str = yyjson_get_str(val); + if (out_len) { + *out_len = yyjson_get_len(val); + } + return true; + } +} + +bool JsonManager::PtrGetIsNull(JsonValue* handle, const char* path, bool* out_is_null, char* error, size_t error_size) +{ + if (!handle || !path || !out_is_null) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters"); + } + return false; + } + + yyjson_ptr_err ptrGetError; + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_doc_ptr_getx(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + *out_is_null = yyjson_mut_is_null(val); + return true; + } else { + yyjson_val* val = yyjson_doc_ptr_getx(handle->m_pDocument->get(), path, strlen(path), &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + *out_is_null = yyjson_is_null(val); + return true; + } +} + +bool JsonManager::PtrGetLength(JsonValue* handle, const char* path, size_t* out_len, char* error, size_t error_size) +{ + if (!handle || !path || !out_len) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters"); + } + return false; + } + + yyjson_ptr_err ptrGetError; + + if (handle->IsMutable()) { + yyjson_mut_val* val = yyjson_mut_doc_ptr_getx(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (yyjson_mut_is_str(val)) { + *out_len = yyjson_mut_get_len(val) + 1; + } else { + *out_len = yyjson_mut_get_len(val); + } + return true; + } else { + yyjson_val* val = yyjson_doc_ptr_getx(handle->m_pDocument->get(), path, strlen(path), &ptrGetError); + + if (ptrGetError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to resolve JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrGetError.msg, ptrGetError.code, ptrGetError.pos, path); + } + return false; + } + + if (yyjson_is_str(val)) { + *out_len = yyjson_get_len(val) + 1; + } else { + *out_len = yyjson_get_len(val); + } + return true; + } +} + +bool JsonManager::PtrSet(JsonValue* handle, const char* path, JsonValue* value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path || !value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val_copy; + if (value->IsMutable()) { + val_copy = yyjson_mut_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal_mut); + } else { + val_copy = yyjson_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal); + } + + if (!val_copy) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to copy JSON value"); + } + return false; + } + + yyjson_ptr_err ptrSetError; + bool success = yyjson_mut_doc_ptr_setx(handle->m_pDocument_mut->get(), path, strlen(path), val_copy, true, nullptr, &ptrSetError); + + if (!success && ptrSetError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to set JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrSetError.msg, ptrSetError.code, ptrSetError.pos, path); + } + + return success; +} + +bool JsonManager::PtrSetBool(JsonValue* handle, const char* path, bool value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_bool(handle->m_pDocument_mut->get(), value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrSetError; + bool success = yyjson_mut_doc_ptr_setx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrSetError); + + if (!success && ptrSetError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to set JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrSetError.msg, ptrSetError.code, ptrSetError.pos, path); + } + + return success; +} + +bool JsonManager::PtrSetFloat(JsonValue* handle, const char* path, double value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_real(handle->m_pDocument_mut->get(), value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrSetError; + bool success = yyjson_mut_doc_ptr_setx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrSetError); + + if (!success && ptrSetError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to set JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrSetError.msg, ptrSetError.code, ptrSetError.pos, path); + } + + return success; +} + +bool JsonManager::PtrSetInt(JsonValue* handle, const char* path, int value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_int(handle->m_pDocument_mut->get(), value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrSetError; + bool success = yyjson_mut_doc_ptr_setx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrSetError); + + if (!success && ptrSetError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to set JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrSetError.msg, ptrSetError.code, ptrSetError.pos, path); + } + + return success; +} + +bool JsonManager::PtrSetInt64(JsonValue* handle, const char* path, std::variant value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = std::visit([&](auto&& val_input) -> yyjson_mut_val* { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return yyjson_mut_sint(handle->m_pDocument_mut->get(), val_input); + } else { + return yyjson_mut_uint(handle->m_pDocument_mut->get(), val_input); + } + }, value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrSetError; + bool success = yyjson_mut_doc_ptr_setx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrSetError); + + if (!success && ptrSetError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to set JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrSetError.msg, ptrSetError.code, ptrSetError.pos, path); + } + + return success; +} + +bool JsonManager::PtrSetString(JsonValue* handle, const char* path, const char* value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path || !value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_strcpy(handle->m_pDocument_mut->get(), value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrSetError; + bool success = yyjson_mut_doc_ptr_setx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrSetError); + + if (!success && ptrSetError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to set JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrSetError.msg, ptrSetError.code, ptrSetError.pos, path); + } + + return success; +} + +bool JsonManager::PtrSetNull(JsonValue* handle, const char* path, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_null(handle->m_pDocument_mut->get()); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrSetError; + bool success = yyjson_mut_doc_ptr_setx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrSetError); + + if (!success && ptrSetError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to set JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrSetError.msg, ptrSetError.code, ptrSetError.pos, path); + } + + return success; +} + +bool JsonManager::PtrAdd(JsonValue* handle, const char* path, JsonValue* value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path || !value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val_copy; + if (value->IsMutable()) { + val_copy = yyjson_mut_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal_mut); + } else { + val_copy = yyjson_val_mut_copy(handle->m_pDocument_mut->get(), value->m_pVal); + } + + if (!val_copy) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to copy JSON value"); + } + return false; + } + + yyjson_ptr_err ptrAddError; + bool success = yyjson_mut_doc_ptr_addx(handle->m_pDocument_mut->get(), path, strlen(path), val_copy, true, nullptr, &ptrAddError); + + if (!success && ptrAddError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to add JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrAddError.msg, ptrAddError.code, ptrAddError.pos, path); + } + + return success; +} + +bool JsonManager::PtrAddBool(JsonValue* handle, const char* path, bool value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_bool(handle->m_pDocument_mut->get(), value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrAddError; + bool success = yyjson_mut_doc_ptr_addx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrAddError); + + if (!success && ptrAddError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to add JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrAddError.msg, ptrAddError.code, ptrAddError.pos, path); + } + + return success; +} + +bool JsonManager::PtrAddFloat(JsonValue* handle, const char* path, double value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_real(handle->m_pDocument_mut->get(), value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrAddError; + bool success = yyjson_mut_doc_ptr_addx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrAddError); + + if (!success && ptrAddError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to add JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrAddError.msg, ptrAddError.code, ptrAddError.pos, path); + } + + return success; +} + +bool JsonManager::PtrAddInt(JsonValue* handle, const char* path, int value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_int(handle->m_pDocument_mut->get(), value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrAddError; + bool success = yyjson_mut_doc_ptr_addx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrAddError); + + if (!success && ptrAddError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to add JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrAddError.msg, ptrAddError.code, ptrAddError.pos, path); + } + + return success; +} + +bool JsonManager::PtrAddInt64(JsonValue* handle, const char* path, std::variant value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = std::visit([&](auto&& val_input) -> yyjson_mut_val* { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return yyjson_mut_sint(handle->m_pDocument_mut->get(), val_input); + } else { + return yyjson_mut_uint(handle->m_pDocument_mut->get(), val_input); + } + }, value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrAddError; + bool success = yyjson_mut_doc_ptr_addx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrAddError); + + if (!success && ptrAddError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to add JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrAddError.msg, ptrAddError.code, ptrAddError.pos, path); + } + + return success; +} + +bool JsonManager::PtrAddString(JsonValue* handle, const char* path, const char* value, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path || !value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_strcpy(handle->m_pDocument_mut->get(), value); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrAddError; + bool success = yyjson_mut_doc_ptr_addx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrAddError); + + if (!success && ptrAddError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to add JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrAddError.msg, ptrAddError.code, ptrAddError.pos, path); + } + + return success; +} + +bool JsonManager::PtrAddNull(JsonValue* handle, const char* path, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_mut_val* val = yyjson_mut_null(handle->m_pDocument_mut->get()); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create JSON value"); + } + return false; + } + + yyjson_ptr_err ptrAddError; + bool success = yyjson_mut_doc_ptr_addx(handle->m_pDocument_mut->get(), path, strlen(path), val, true, nullptr, &ptrAddError); + + if (!success && ptrAddError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to add JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrAddError.msg, ptrAddError.code, ptrAddError.pos, path); + } + + return success; +} + +bool JsonManager::PtrRemove(JsonValue* handle, const char* path, char* error, size_t error_size) +{ + if (!handle || !handle->IsMutable() || !path) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid parameters or immutable document"); + } + return false; + } + + yyjson_ptr_err ptrRemoveError; + bool success = yyjson_mut_doc_ptr_removex(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrRemoveError) != nullptr; + + if (!success && ptrRemoveError.code && error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to remove JSON pointer: %s (error code: %u, position: %zu, path: %s)", + ptrRemoveError.msg, ptrRemoveError.code, ptrRemoveError.pos, path); + } + + return success; +} + +JsonManager::PtrGetValueResult JsonManager::PtrGetValueInternal(JsonValue* handle, const char* path) +{ + PtrGetValueResult result; + result.success = false; + + if (!handle || !path) { + return result; + } + + yyjson_ptr_err ptrGetError; + + if (handle->IsMutable()) { + result.mut_val = yyjson_mut_doc_ptr_getx(handle->m_pDocument_mut->get(), path, strlen(path), nullptr, &ptrGetError); + if (result.mut_val && !ptrGetError.code) { + result.success = true; + } + } else { + result.imm_val = yyjson_doc_ptr_getx(handle->m_pDocument->get(), path, strlen(path), &ptrGetError); + if (result.imm_val && !ptrGetError.code) { + result.success = true; + } + } + + return result; +} + +JsonValue* JsonManager::PtrTryGet(JsonValue* handle, const char* path) +{ + if (!handle || !path) { + return nullptr; + } + + auto result = PtrGetValueInternal(handle, path); + if (!result.success) { + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + if (handle->IsMutable()) { + pJSONValue->m_pDocument_mut = handle->m_pDocument_mut; + pJSONValue->m_pVal_mut = result.mut_val; + } else { + pJSONValue->m_pDocument = handle->m_pDocument; + pJSONValue->m_pVal = result.imm_val; + } + + return pJSONValue.release(); +} + +bool JsonManager::PtrTryGetBool(JsonValue* handle, const char* path, bool* out_value) +{ + if (!handle || !path || !out_value) { + return false; + } + + auto result = PtrGetValueInternal(handle, path); + if (!result.success) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_bool(result.mut_val)) { + return false; + } + *out_value = yyjson_mut_get_bool(result.mut_val); + return true; + } else { + if (!yyjson_is_bool(result.imm_val)) { + return false; + } + *out_value = yyjson_get_bool(result.imm_val); + return true; + } +} + +bool JsonManager::PtrTryGetFloat(JsonValue* handle, const char* path, double* out_value) +{ + if (!handle || !path || !out_value) { + return false; + } + + auto result = PtrGetValueInternal(handle, path); + if (!result.success) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_num(result.mut_val)) { + return false; + } + *out_value = yyjson_mut_get_num(result.mut_val); + return true; + } else { + if (!yyjson_is_num(result.imm_val)) { + return false; + } + *out_value = yyjson_get_num(result.imm_val); + return true; + } +} + +bool JsonManager::PtrTryGetInt(JsonValue* handle, const char* path, int* out_value) +{ + if (!handle || !path || !out_value) { + return false; + } + + auto result = PtrGetValueInternal(handle, path); + if (!result.success) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_int(result.mut_val)) { + return false; + } + *out_value = yyjson_mut_get_int(result.mut_val); + return true; + } else { + if (!yyjson_is_int(result.imm_val)) { + return false; + } + *out_value = yyjson_get_int(result.imm_val); + return true; + } +} + +bool JsonManager::PtrTryGetInt64(JsonValue* handle, const char* path, std::variant* out_value) +{ + if (!handle || !path || !out_value) { + return false; + } + + auto result = PtrGetValueInternal(handle, path); + if (!result.success) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_int(result.mut_val)) { + return false; + } + ReadInt64FromMutVal(result.mut_val, out_value); + return true; + } else { + if (!yyjson_is_int(result.imm_val)) { + return false; + } + ReadInt64FromVal(result.imm_val, out_value); + return true; + } +} + +bool JsonManager::PtrTryGetString(JsonValue* handle, const char* path, const char** out_str, size_t* out_len) +{ + if (!handle || !path || !out_str) { + return false; + } + + auto result = PtrGetValueInternal(handle, path); + if (!result.success) { + return false; + } + + if (handle->IsMutable()) { + if (!yyjson_mut_is_str(result.mut_val)) { + return false; + } + *out_str = yyjson_mut_get_str(result.mut_val); + if (out_len) { + *out_len = yyjson_mut_get_len(result.mut_val); + } + return true; + } else { + if (!yyjson_is_str(result.imm_val)) { + return false; + } + *out_str = yyjson_get_str(result.imm_val); + if (out_len) { + *out_len = yyjson_get_len(result.imm_val); + } + return true; + } +} + +bool JsonManager::ObjectForeachNext(JsonValue* handle, const char** out_key, + size_t* out_key_len, JsonValue** out_value) +{ + if (!handle || !IsObject(handle)) { + return false; + } + + if (IsMutable(handle)) { + if (!handle->m_iterInitialized) { + if (!yyjson_mut_obj_iter_init(handle->m_pVal_mut, &handle->m_iterObj)) { + return false; + } + handle->m_iterInitialized = true; + } + + yyjson_mut_val* key = yyjson_mut_obj_iter_next(&handle->m_iterObj); + if (key) { + yyjson_mut_val* val = yyjson_mut_obj_iter_get_val(key); + + *out_key = yyjson_mut_get_str(key); + if (out_key_len) { + *out_key_len = yyjson_mut_get_len(key); + } + + auto pWrapper = CreateWrapper(); + pWrapper->m_pDocument_mut = handle->m_pDocument_mut; + pWrapper->m_pVal_mut = val; + *out_value = pWrapper.release(); + + return true; + } + } else { + if (!handle->m_iterInitialized) { + if (!yyjson_obj_iter_init(handle->m_pVal, &handle->m_iterObjImm)) { + return false; + } + handle->m_iterInitialized = true; + } + + yyjson_val* key = yyjson_obj_iter_next(&handle->m_iterObjImm); + if (key) { + yyjson_val* val = yyjson_obj_iter_get_val(key); + + *out_key = yyjson_get_str(key); + if (out_key_len) { + *out_key_len = yyjson_get_len(key); + } + + auto pWrapper = CreateWrapper(); + pWrapper->m_pDocument = handle->m_pDocument; + pWrapper->m_pVal = val; + *out_value = pWrapper.release(); + + return true; + } + } + + handle->ResetObjectIterator(); + return false; +} + +bool JsonManager::ArrayForeachNext(JsonValue* handle, size_t* out_index, + JsonValue** out_value) +{ + if (!handle || !IsArray(handle)) { + return false; + } + + if (IsMutable(handle)) { + if (!handle->m_iterInitialized) { + if (!yyjson_mut_arr_iter_init(handle->m_pVal_mut, &handle->m_iterArr)) { + return false; + } + handle->m_iterInitialized = true; + } + + yyjson_mut_val* val = yyjson_mut_arr_iter_next(&handle->m_iterArr); + if (val) { + *out_index = handle->m_arrayIndex; + + auto pWrapper = CreateWrapper(); + pWrapper->m_pDocument_mut = handle->m_pDocument_mut; + pWrapper->m_pVal_mut = val; + *out_value = pWrapper.release(); + + handle->m_arrayIndex++; + return true; + } + } else { + if (!handle->m_iterInitialized) { + if (!yyjson_arr_iter_init(handle->m_pVal, &handle->m_iterArrImm)) { + return false; + } + handle->m_iterInitialized = true; + } + + yyjson_val* val = yyjson_arr_iter_next(&handle->m_iterArrImm); + if (val) { + *out_index = handle->m_arrayIndex; + + auto pWrapper = CreateWrapper(); + pWrapper->m_pDocument = handle->m_pDocument; + pWrapper->m_pVal = val; + *out_value = pWrapper.release(); + + handle->m_arrayIndex++; + return true; + } + } + + handle->ResetArrayIterator(); + return false; +} + +bool JsonManager::ObjectForeachKeyNext(JsonValue* handle, const char** out_key, + size_t* out_key_len) +{ + if (!handle || !IsObject(handle)) { + return false; + } + + if (IsMutable(handle)) { + if (!handle->m_iterInitialized) { + if (!yyjson_mut_obj_iter_init(handle->m_pVal_mut, &handle->m_iterObj)) { + return false; + } + handle->m_iterInitialized = true; + } + + yyjson_mut_val* key = yyjson_mut_obj_iter_next(&handle->m_iterObj); + if (key) { + *out_key = yyjson_mut_get_str(key); + if (out_key_len) { + *out_key_len = yyjson_mut_get_len(key); + } + return true; + } + } else { + if (!handle->m_iterInitialized) { + if (!yyjson_obj_iter_init(handle->m_pVal, &handle->m_iterObjImm)) { + return false; + } + handle->m_iterInitialized = true; + } + + yyjson_val* key = yyjson_obj_iter_next(&handle->m_iterObjImm); + if (key) { + *out_key = yyjson_get_str(key); + if (out_key_len) { + *out_key_len = yyjson_get_len(key); + } + return true; + } + } + + handle->ResetObjectIterator(); + return false; +} + +bool JsonManager::ArrayForeachIndexNext(JsonValue* handle, size_t* out_index) +{ + if (!handle || !IsArray(handle)) { + return false; + } + + if (IsMutable(handle)) { + if (!handle->m_iterInitialized) { + if (!yyjson_mut_arr_iter_init(handle->m_pVal_mut, &handle->m_iterArr)) { + return false; + } + handle->m_iterInitialized = true; + } + + yyjson_mut_val* val = yyjson_mut_arr_iter_next(&handle->m_iterArr); + if (val) { + *out_index = handle->m_arrayIndex; + handle->m_arrayIndex++; + return true; + } + } else { + if (!handle->m_iterInitialized) { + if (!yyjson_arr_iter_init(handle->m_pVal, &handle->m_iterArrImm)) { + return false; + } + handle->m_iterInitialized = true; + } + + yyjson_val* val = yyjson_arr_iter_next(&handle->m_iterArrImm); + if (val) { + *out_index = handle->m_arrayIndex; + handle->m_arrayIndex++; + return true; + } + } + + handle->ResetArrayIterator(); + return false; +} + +void JsonManager::Release(JsonValue* value) +{ + if (value) { + delete value; + } +} + +HandleType_t JsonManager::GetHandleType() +{ + return g_JsonType; +} + +JsonValue* JsonManager::GetFromHandle(IPluginContext* pContext, Handle_t handle) +{ + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + JsonValue* pJSONValue; + if ((err = handlesys->ReadHandle(handle, g_JsonType, &sec, (void**)&pJSONValue)) != HandleError_None) + { + pContext->ReportError("Invalid JSON handle %x (error %d)", handle, err); + return nullptr; + } + + return pJSONValue; +} + +JsonArrIter* JsonManager::ArrIterInit(JsonValue* handle) +{ + return ArrIterWith(handle); +} + +JsonArrIter* JsonManager::ArrIterWith(JsonValue* handle) +{ + if (!handle || !IsArray(handle)) { + return nullptr; + } + + auto iter = new JsonArrIter(); + iter->m_isMutable = handle->IsMutable(); + iter->m_pDocument_mut = handle->m_pDocument_mut; + iter->m_pDocument = handle->m_pDocument; + iter->m_rootMut = nullptr; + iter->m_rootImm = nullptr; + + if (handle->IsMutable()) { + iter->m_rootMut = handle->m_pVal_mut; + if (!iter->m_rootMut || !yyjson_mut_arr_iter_init(iter->m_rootMut, &iter->m_iterMut)) { + delete iter; + return nullptr; + } + } else { + iter->m_rootImm = handle->m_pVal; + if (!iter->m_rootImm || !yyjson_arr_iter_init(iter->m_rootImm, &iter->m_iterImm)) { + delete iter; + return nullptr; + } + } + + iter->m_initialized = true; + return iter; +} + +bool JsonManager::ArrIterReset(JsonArrIter* iter) +{ + if (!iter || !iter->m_initialized) { + return false; + } + + bool success; + if (iter->m_isMutable) { + success = iter->m_rootMut && yyjson_mut_arr_iter_init(iter->m_rootMut, &iter->m_iterMut); + } else { + success = iter->m_rootImm && yyjson_arr_iter_init(iter->m_rootImm, &iter->m_iterImm); + } + + if (!success) { + iter->m_initialized = false; + return false; + } + + return true; +} + +JsonValue* JsonManager::ArrIterNext(JsonArrIter* iter) +{ + if (!iter || !iter->m_initialized) { + return nullptr; + } + + JsonValue* val; + + if (iter->m_isMutable) { + yyjson_mut_val* raw_val = yyjson_mut_arr_iter_next(&iter->m_iterMut); + if (!raw_val) { + return nullptr; + } + + auto pWrapper = CreateWrapper(); + pWrapper->m_pDocument_mut = iter->m_pDocument_mut; + pWrapper->m_pVal_mut = raw_val; + val = pWrapper.release(); + } else { + yyjson_val* raw_val = yyjson_arr_iter_next(&iter->m_iterImm); + if (!raw_val) { + return nullptr; + } + + auto pWrapper = CreateWrapper(); + pWrapper->m_pDocument = iter->m_pDocument; + pWrapper->m_pVal = raw_val; + val = pWrapper.release(); + } + + return val; +} + +bool JsonManager::ArrIterHasNext(JsonArrIter* iter) +{ + if (!iter || !iter->m_initialized) { + return false; + } + + if (iter->m_isMutable) { + return yyjson_mut_arr_iter_has_next(&iter->m_iterMut); + } else { + return yyjson_arr_iter_has_next(&iter->m_iterImm); + } +} + +size_t JsonManager::ArrIterGetIndex(JsonArrIter* iter) +{ + if (!iter || !iter->m_initialized) { + return SIZE_MAX; + } + + if (iter->m_isMutable) { + if (iter->m_iterMut.idx == 0) { + return SIZE_MAX; + } + return iter->m_iterMut.idx - 1; + } else { + if (iter->m_iterImm.idx == 0) { + return SIZE_MAX; + } + return iter->m_iterImm.idx - 1; + } +} + +void* JsonManager::ArrIterRemove(JsonArrIter* iter) +{ + if (!iter || !iter->m_isMutable) { + return nullptr; + } + + return yyjson_mut_arr_iter_remove(&iter->m_iterMut); +} + +JsonObjIter* JsonManager::ObjIterInit(JsonValue* handle) +{ + return ObjIterWith(handle); +} + +JsonObjIter* JsonManager::ObjIterWith(JsonValue* handle) +{ + if (!handle || !IsObject(handle)) { + return nullptr; + } + + auto iter = new JsonObjIter(); + iter->m_isMutable = handle->IsMutable(); + iter->m_pDocument_mut = handle->m_pDocument_mut; + iter->m_pDocument = handle->m_pDocument; + iter->m_rootMut = nullptr; + iter->m_rootImm = nullptr; + + if (handle->IsMutable()) { + iter->m_rootMut = handle->m_pVal_mut; + if (!iter->m_rootMut || !yyjson_mut_obj_iter_init(iter->m_rootMut, &iter->m_iterMut)) { + delete iter; + return nullptr; + } + } else { + iter->m_rootImm = handle->m_pVal; + if (!iter->m_rootImm || !yyjson_obj_iter_init(iter->m_rootImm, &iter->m_iterImm)) { + delete iter; + return nullptr; + } + } + + iter->m_initialized = true; + return iter; +} + +bool JsonManager::ObjIterReset(JsonObjIter* iter) +{ + if (!iter || !iter->m_initialized) { + return false; + } + + bool success; + if (iter->m_isMutable) { + success = iter->m_rootMut && yyjson_mut_obj_iter_init(iter->m_rootMut, &iter->m_iterMut); + } else { + success = iter->m_rootImm && yyjson_obj_iter_init(iter->m_rootImm, &iter->m_iterImm); + } + + if (!success) { + iter->m_initialized = false; + return false; + } + + iter->m_currentKey = nullptr; + return true; +} + +void* JsonManager::ObjIterNext(JsonObjIter* iter) +{ + if (!iter || !iter->m_initialized) { + return nullptr; + } + + if (iter->m_isMutable) { + yyjson_mut_val* current_key = yyjson_mut_obj_iter_next(&iter->m_iterMut); + if (!current_key) { + return nullptr; + } + iter->m_currentKey = current_key; + return current_key; + } else { + void* key = yyjson_obj_iter_next(&iter->m_iterImm); + iter->m_currentKey = key; + return key; + } +} + +bool JsonManager::ObjIterHasNext(JsonObjIter* iter) +{ + if (!iter || !iter->m_initialized) { + return false; + } + + if (iter->m_isMutable) { + return yyjson_mut_obj_iter_has_next(&iter->m_iterMut); + } else { + return yyjson_obj_iter_has_next(&iter->m_iterImm); + } +} + +JsonValue* JsonManager::ObjIterGetVal(JsonObjIter* iter, void* key) +{ + if (!iter || !iter->m_initialized || !key) { + return nullptr; + } + + auto pWrapper = CreateWrapper(); + + if (iter->m_isMutable) { + yyjson_mut_val* val = yyjson_mut_obj_iter_get_val(reinterpret_cast(key)); + if (!val) { + return nullptr; + } + pWrapper->m_pDocument_mut = iter->m_pDocument_mut; + pWrapper->m_pVal_mut = val; + } else { + yyjson_val* val = yyjson_obj_iter_get_val(reinterpret_cast(key)); + if (!val) { + return nullptr; + } + pWrapper->m_pDocument = iter->m_pDocument; + pWrapper->m_pVal = val; + } + + return pWrapper.release(); +} + +JsonValue* JsonManager::ObjIterGet(JsonObjIter* iter, const char* key) +{ + if (!iter || !iter->m_initialized || !key) { + return nullptr; + } + + auto pWrapper = CreateWrapper(); + + if (iter->m_isMutable) { + yyjson_mut_val* val = yyjson_mut_obj_iter_get(&iter->m_iterMut, key); + if (!val) { + return nullptr; + } + pWrapper->m_pDocument_mut = iter->m_pDocument_mut; + pWrapper->m_pVal_mut = val; + } else { + yyjson_val* val = yyjson_obj_iter_get(&iter->m_iterImm, key); + if (!val) { + return nullptr; + } + pWrapper->m_pDocument = iter->m_pDocument; + pWrapper->m_pVal = val; + } + + return pWrapper.release(); +} + +size_t JsonManager::ObjIterGetIndex(JsonObjIter* iter) +{ + if (!iter || !iter->m_initialized) { + return SIZE_MAX; + } + if (iter->m_isMutable) { + if (iter->m_currentKey == nullptr) { + return SIZE_MAX; + } + if (iter->m_iterMut.idx >= iter->m_iterMut.max) { + return iter->m_iterMut.max - 1; + } + return iter->m_iterMut.idx - 1; + } else { + if (iter->m_iterImm.idx == 0) { + return SIZE_MAX; + } + if (iter->m_iterImm.idx >= iter->m_iterImm.max) { + return iter->m_iterImm.max - 1; + } + return iter->m_iterImm.idx - 1; + } +} + +void* JsonManager::ObjIterRemove(JsonObjIter* iter) +{ + if (!iter || !iter->m_isMutable) { + return nullptr; + } + + return yyjson_mut_obj_iter_remove(&iter->m_iterMut); +} + +void JsonManager::ReleaseArrIter(JsonArrIter* iter) +{ + if (iter) { + delete iter; + } +} + +void JsonManager::ReleaseObjIter(JsonObjIter* iter) +{ + if (iter) { + delete iter; + } +} + +HandleType_t JsonManager::GetArrIterHandleType() +{ + return g_ArrIterType; +} + +HandleType_t JsonManager::GetObjIterHandleType() +{ + return g_ObjIterType; +} + +JsonArrIter* JsonManager::GetArrIterFromHandle(IPluginContext* pContext, Handle_t handle) +{ + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + JsonArrIter* pIter; + if ((err = handlesys->ReadHandle(handle, g_ArrIterType, &sec, (void**)&pIter)) != HandleError_None) + { + pContext->ReportError("Invalid JSONArrIter handle %x (error %d)", handle, err); + return nullptr; + } + + return pIter; +} + +JsonObjIter* JsonManager::GetObjIterFromHandle(IPluginContext* pContext, Handle_t handle) +{ + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + + JsonObjIter* pIter; + if ((err = handlesys->ReadHandle(handle, g_ObjIterType, &sec, (void**)&pIter)) != HandleError_None) + { + pContext->ReportError("Invalid JSONObjIter handle %x (error %d)", handle, err); + return nullptr; + } + + return pIter; +} + +JsonValue* JsonManager::ReadNumber(const char* dat, uint32_t read_flg, char* error, size_t error_size, size_t* out_consumed) +{ + if (!dat) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid input data"); + } + return nullptr; + } + + auto pJSONValue = CreateWrapper(); + pJSONValue->m_pDocument_mut = CreateDocument(); + + if (!pJSONValue->m_pDocument_mut) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create number document"); + } + return nullptr; + } + + yyjson_mut_val* val = yyjson_mut_int(pJSONValue->m_pDocument_mut->get(), 0); + if (!val) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to create number value"); + } + return nullptr; + } + + yyjson_read_err readError; + const char* end_ptr = yyjson_mut_read_number(dat, val, + static_cast(read_flg), nullptr, &readError); + + if (!end_ptr || readError.code) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Failed to read number: %s (error code: %u, position: %zu)", + readError.msg, readError.code, readError.pos); + } + return nullptr; + } + + if (out_consumed) { + *out_consumed = end_ptr - dat; + } + + pJSONValue->m_pVal_mut = val; + yyjson_mut_doc_set_root(pJSONValue->m_pDocument_mut->get(), val); + + return pJSONValue.release(); +} + +bool JsonManager::WriteNumber(JsonValue* handle, char* buffer, size_t buffer_size, size_t* out_written) +{ + if (!handle || !buffer || buffer_size == 0) { + return false; + } + + if (!IsNum(handle)) { + return false; + } + + char* result; + if (handle->IsMutable()) { + result = yyjson_mut_write_number(handle->m_pVal_mut, buffer); + } else { + result = yyjson_write_number(handle->m_pVal, buffer); + } + + if (!result) { + return false; + } + + size_t written = result - buffer; + if (written >= buffer_size) { + return false; + } + + if (out_written) { + *out_written = written; + } + + return true; +} + +bool JsonManager::SetFpToFloat(JsonValue* handle, bool flt) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_set_fp_to_float(handle->m_pVal_mut, flt); + } else { + return yyjson_set_fp_to_float(handle->m_pVal, flt); + } +} + +bool JsonManager::SetFpToFixed(JsonValue* handle, int prec) +{ + if (!handle) { + return false; + } + + if (prec < 1 || prec > 15) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_set_fp_to_fixed(handle->m_pVal_mut, prec); + } else { + return yyjson_set_fp_to_fixed(handle->m_pVal, prec); + } +} + +bool JsonManager::SetBool(JsonValue* handle, bool value) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_set_bool(handle->m_pVal_mut, value); + } else { + return yyjson_set_bool(handle->m_pVal, value); + } +} + +bool JsonManager::SetInt(JsonValue* handle, int value) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_set_int(handle->m_pVal_mut, value); + } else { + return yyjson_set_int(handle->m_pVal, value); + } +} + +bool JsonManager::SetInt64(JsonValue* handle, std::variant value) +{ + if (!handle) { + return false; + } + + return std::visit([&](auto&& val) -> bool { + using T = std::decay_t; + if (handle->IsMutable()) { + if constexpr (std::is_same_v) { + return yyjson_mut_set_sint(handle->m_pVal_mut, val); + } else if constexpr (std::is_same_v) { + return yyjson_mut_set_uint(handle->m_pVal_mut, val); + } + } else { + if constexpr (std::is_same_v) { + return yyjson_set_sint(handle->m_pVal, val); + } else if constexpr (std::is_same_v) { + return yyjson_set_uint(handle->m_pVal, val); + } + } + return false; + }, value); +} + +bool JsonManager::SetFloat(JsonValue* handle, double value) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_set_real(handle->m_pVal_mut, value); + } else { + return yyjson_set_real(handle->m_pVal, value); + } +} + +bool JsonManager::SetString(JsonValue* handle, const char* value) +{ + if (!handle || !value) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_set_str(handle->m_pVal_mut, value); + } else { + return yyjson_set_str(handle->m_pVal, value); + } +} + +bool JsonManager::SetNull(JsonValue* handle) +{ + if (!handle) { + return false; + } + + if (handle->IsMutable()) { + return yyjson_mut_set_null(handle->m_pVal_mut); + } else { + return yyjson_set_null(handle->m_pVal); + } +} + +bool JsonManager::ParseInt64Variant(const char* value, std::variant* out_value, char* error, size_t error_size) +{ + if (!value || !*value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Empty integer64 value"); + } + return false; + } + + if (!out_value) { + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid output parameter"); + } + return false; + } + + std::string_view sv(value); + bool is_negative = (sv[0] == '-'); + + if (is_negative) { + int64_t signed_val; + auto result = std::from_chars(sv.data(), sv.data() + sv.size(), signed_val); + + if (result.ec == std::errc{} && result.ptr == sv.data() + sv.size()) { + *out_value = signed_val; + return true; + } + + if (error && error_size > 0) { + if (result.ec == std::errc::result_out_of_range) { + SetErrorSafe(error, error_size, "Integer64 value out of range: %s", value); + } else { + SetErrorSafe(error, error_size, "Invalid integer64 value: %s", value); + } + } + return false; + } + + int64_t signed_val; + auto result = std::from_chars(sv.data(), sv.data() + sv.size(), signed_val); + + if (result.ec == std::errc{} && result.ptr == sv.data() + sv.size()) { + *out_value = signed_val; + return true; + } + + if (result.ec == std::errc::result_out_of_range) { + uint64_t unsigned_val; + auto unsigned_result = std::from_chars(sv.data(), sv.data() + sv.size(), unsigned_val); + + if (unsigned_result.ec == std::errc{} && unsigned_result.ptr == sv.data() + sv.size()) { + *out_value = unsigned_val; + return true; + } + + if (error && error_size > 0) { + if (unsigned_result.ec == std::errc::result_out_of_range) { + SetErrorSafe(error, error_size, "Integer64 value out of range: %s", value); + } else { + SetErrorSafe(error, error_size, "Invalid integer64 value: %s", value); + } + } + return false; + } + + if (error && error_size > 0) { + SetErrorSafe(error, error_size, "Invalid integer64 value: %s", value); + } + return false; +} \ No newline at end of file diff --git a/extensions/json/JsonManager.h b/extensions/json/JsonManager.h new file mode 100755 index 0000000000..2fc6a14800 --- /dev/null +++ b/extensions/json/JsonManager.h @@ -0,0 +1,583 @@ +#ifndef _INCLUDE_JSONMANAGER_H_ +#define _INCLUDE_JSONMANAGER_H_ + +#include +#include +#include +#include +#include + +/** + * @brief Base class for intrusive reference counting + * + * Objects inheriting from this class can be managed by RefPtr. + * Reference count starts at 0 and is incremented when RefPtr takes ownership. + */ +class RefCounted { +private: + mutable size_t ref_count_ = 0; + +protected: + virtual ~RefCounted() = default; + RefCounted() = default; + + RefCounted(const RefCounted &) = delete; + RefCounted &operator=(const RefCounted &) = delete; + + RefCounted(RefCounted &&) noexcept : ref_count_(0) {} + RefCounted &operator=(RefCounted &&) noexcept { return *this; } + +public: + void add_ref() const noexcept { ++ref_count_; } + + void release() const noexcept { + assert(ref_count_ > 0 && "Reference count underflow"); + if (--ref_count_ == 0) { + delete this; + } + } + + size_t use_count() const noexcept { return ref_count_; } +}; + +/** + * @brief Smart pointer for intrusive reference counting + * + * Similar to std::shared_ptr but uses intrusive reference counting. + * Automatically manages object lifetime through add_ref() and release(). + * @warning This implementation is not thread-safe. It must only be used + * in single-threaded contexts or with external synchronization. + */ +template class RefPtr { +private: + T *ptr_; + +public: + RefPtr() noexcept : ptr_(nullptr) {} + + explicit RefPtr(T *p) noexcept : ptr_(p) { + if (ptr_) + ptr_->add_ref(); + } + + RefPtr(const RefPtr &other) noexcept : ptr_(other.ptr_) { + if (ptr_) + ptr_->add_ref(); + } + + RefPtr(RefPtr &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; } + + ~RefPtr() { + if (ptr_) + ptr_->release(); + } + + RefPtr &operator=(const RefPtr &other) noexcept { + if (ptr_ != other.ptr_) { + if (other.ptr_) + other.ptr_->add_ref(); + if (ptr_) + ptr_->release(); + ptr_ = other.ptr_; + } + return *this; + } + + RefPtr &operator=(RefPtr &&other) noexcept { + if (this != &other) { + if (ptr_) + ptr_->release(); + ptr_ = other.ptr_; + other.ptr_ = nullptr; + } + return *this; + } + + RefPtr &operator=(T *p) noexcept { + reset(p); + return *this; + } + + T *operator->() const noexcept { return ptr_; } + T &operator*() const noexcept { return *ptr_; } + T *get() const noexcept { return ptr_; } + + explicit operator bool() const noexcept { return ptr_ != nullptr; } + + size_t use_count() const noexcept { return ptr_ ? ptr_->use_count() : 0; } + + void reset(T *p = nullptr) noexcept { + if (ptr_ != p) { + if (p) + p->add_ref(); + if (ptr_) + ptr_->release(); + ptr_ = p; + } + } + + bool operator==(const RefPtr &other) const noexcept { return ptr_ == other.ptr_; } + bool operator!=(const RefPtr &other) const noexcept { return ptr_ != other.ptr_; } + bool operator==(std::nullptr_t) const noexcept { return ptr_ == nullptr; } + bool operator!=(std::nullptr_t) const noexcept { return ptr_ != nullptr; } +}; + +/** + * @brief Factory function to create RefPtr from new object + */ +template RefPtr make_ref(Args &&...args) { + return RefPtr(new T(std::forward(args)...)); +} + +/** + * @brief Wrapper for yyjson_mut_doc with intrusive reference counting + */ +class RefCountedMutDoc : public RefCounted { +private: + yyjson_mut_doc *doc_; + +public: + explicit RefCountedMutDoc(yyjson_mut_doc *doc) noexcept : doc_(doc) {} + + RefCountedMutDoc(const RefCountedMutDoc &) = delete; + RefCountedMutDoc &operator=(const RefCountedMutDoc &) = delete; + + ~RefCountedMutDoc() noexcept override { + if (doc_) { + yyjson_mut_doc_free(doc_); + } + } + + yyjson_mut_doc *get() const noexcept { return doc_; } +}; + +/** + * @brief Wrapper for yyjson_doc with intrusive reference counting + */ +class RefCountedImmutableDoc : public RefCounted { +private: + yyjson_doc *doc_; + +public: + explicit RefCountedImmutableDoc(yyjson_doc *doc) noexcept : doc_(doc) {} + + RefCountedImmutableDoc(const RefCountedImmutableDoc &) = delete; + RefCountedImmutableDoc &operator=(const RefCountedImmutableDoc &) = delete; + + ~RefCountedImmutableDoc() noexcept override { + if (doc_) { + yyjson_doc_free(doc_); + } + } + + yyjson_doc *get() const noexcept { return doc_; } +}; + +/** + * @brief JSON value wrapper + * + * Wraps json mutable/immutable documents and values. + * Used as the primary data type for JSON operations. + */ +class JsonValue { +public: + JsonValue() = default; + ~JsonValue() = default; + + JsonValue(const JsonValue&) = delete; + JsonValue& operator=(const JsonValue&) = delete; + + void ResetObjectIterator() { + m_iterInitialized = false; + } + + void ResetArrayIterator() { + m_iterInitialized = false; + m_arrayIndex = 0; + } + + bool IsMutable() const { + return m_pDocument_mut != nullptr; + } + + bool IsImmutable() const { + return m_pDocument != nullptr; + } + + size_t GetDocumentRefCount() const { + if (m_pDocument_mut) { + return m_pDocument_mut.use_count(); + } else if (m_pDocument) { + return m_pDocument.use_count(); + } + return 0; + } + + // Mutable document + RefPtr m_pDocument_mut; + yyjson_mut_val* m_pVal_mut{ nullptr }; + + // Immutable document + RefPtr m_pDocument; + yyjson_val* m_pVal{ nullptr }; + + // Mutable document iterators + yyjson_mut_obj_iter m_iterObj; + yyjson_mut_arr_iter m_iterArr; + + // Immutable document iterators + yyjson_obj_iter m_iterObjImm; + yyjson_arr_iter m_iterArrImm; + + Handle_t m_handle{ BAD_HANDLE }; + size_t m_arrayIndex{ 0 }; + size_t m_readSize{ 0 }; + bool m_iterInitialized{ false }; +}; + +/** + * @brief Array iterator wrapper + * + * Wraps yyjson_arr_iter and yyjson_mut_arr_iter for array iteration. + */ +class JsonArrIter { +public: + JsonArrIter() = default; + ~JsonArrIter() = default; + + JsonArrIter(const JsonArrIter&) = delete; + JsonArrIter& operator=(const JsonArrIter&) = delete; + + bool IsMutable() const { + return m_isMutable; + } + + RefPtr m_pDocument_mut; + RefPtr m_pDocument; + + yyjson_mut_arr_iter m_iterMut; + yyjson_arr_iter m_iterImm; + yyjson_mut_val* m_rootMut{ nullptr }; + yyjson_val* m_rootImm{ nullptr }; + + Handle_t m_handle{ BAD_HANDLE }; + bool m_isMutable{ false }; + bool m_initialized{ false }; +}; + +/** + * @brief Object iterator wrapper + * + * Wraps yyjson_obj_iter and yyjson_mut_obj_iter for object iteration. + */ +class JsonObjIter { +public: + JsonObjIter() = default; + ~JsonObjIter() = default; + + JsonObjIter(const JsonObjIter&) = delete; + JsonObjIter& operator=(const JsonObjIter&) = delete; + + bool IsMutable() const { + return m_isMutable; + } + + RefPtr m_pDocument_mut; + RefPtr m_pDocument; + + yyjson_mut_obj_iter m_iterMut; + yyjson_obj_iter m_iterImm; + yyjson_mut_val* m_rootMut{ nullptr }; + yyjson_val* m_rootImm{ nullptr }; + + void* m_currentKey{ nullptr }; + + Handle_t m_handle{ BAD_HANDLE }; + bool m_isMutable{ false }; + bool m_initialized{ false }; +}; + +class JsonManager : public IJsonManager +{ +public: + JsonManager(); + ~JsonManager(); + +public: + // ========== Document Operations ========== + virtual JsonValue* ParseJSON(const char* json_str, bool is_file, bool is_mutable, + yyjson_read_flag read_flg, char* error, size_t error_size) override; + virtual bool WriteToString(JsonValue* handle, char* buffer, size_t buffer_size, + yyjson_write_flag write_flg, size_t* out_size) override; + virtual char* WriteToStringPtr(JsonValue* handle, yyjson_write_flag write_flg, size_t* out_size) override; + virtual JsonValue* ApplyJsonPatch(JsonValue* target, JsonValue* patch, bool result_mutable, + char* error, size_t error_size) override; + virtual bool JsonPatchInPlace(JsonValue* target, JsonValue* patch, + char* error, size_t error_size) override; + virtual JsonValue* ApplyMergePatch(JsonValue* target, JsonValue* patch, bool result_mutable, + char* error, size_t error_size) override; + virtual bool MergePatchInPlace(JsonValue* target, JsonValue* patch, + char* error, size_t error_size) override; + virtual bool WriteToFile(JsonValue* handle, const char* path, yyjson_write_flag write_flg, + char* error, size_t error_size) override; + virtual bool Equals(JsonValue* handle1, JsonValue* handle2) override; + virtual bool EqualsStr(JsonValue* handle, const char* str) override; + virtual JsonValue* DeepCopy(JsonValue* targetDoc, JsonValue* sourceValue) override; + virtual const char* GetTypeDesc(JsonValue* handle) override; + virtual size_t GetSerializedSize(JsonValue* handle, yyjson_write_flag write_flg) override; + virtual JsonValue* ToMutable(JsonValue* handle) override; + virtual JsonValue* ToImmutable(JsonValue* handle) override; + virtual yyjson_type GetType(JsonValue* handle) override; + virtual yyjson_subtype GetSubtype(JsonValue* handle) override; + virtual bool IsArray(JsonValue* handle) override; + virtual bool IsObject(JsonValue* handle) override; + virtual bool IsInt(JsonValue* handle) override; + virtual bool IsUint(JsonValue* handle) override; + virtual bool IsSint(JsonValue* handle) override; + virtual bool IsNum(JsonValue* handle) override; + virtual bool IsBool(JsonValue* handle) override; + virtual bool IsTrue(JsonValue* handle) override; + virtual bool IsFalse(JsonValue* handle) override; + virtual bool IsFloat(JsonValue* handle) override; + virtual bool IsStr(JsonValue* handle) override; + virtual bool IsNull(JsonValue* handle) override; + virtual bool IsCtn(JsonValue* handle) override; + virtual bool IsMutable(JsonValue* handle) override; + virtual bool IsImmutable(JsonValue* handle) override; + virtual size_t GetReadSize(JsonValue* handle) override; + virtual size_t GetRefCount(JsonValue* handle) override; + + // ========== Object Operations ========== + virtual JsonValue* ObjectInit() override; + virtual JsonValue* ObjectInitWithStrings(const char** pairs, size_t count) override; + virtual JsonValue* ObjectParseString(const char* str, yyjson_read_flag read_flg, + char* error, size_t error_size) override; + virtual JsonValue* ObjectParseFile(const char* path, yyjson_read_flag read_flg, + char* error, size_t error_size) override; + virtual size_t ObjectGetSize(JsonValue* handle) override; + virtual bool ObjectGetKey(JsonValue* handle, size_t index, const char** out_key) override; + virtual JsonValue* ObjectGetValueAt(JsonValue* handle, size_t index) override; + virtual JsonValue* ObjectGet(JsonValue* handle, const char* key) override; + virtual bool ObjectGetBool(JsonValue* handle, const char* key, bool* out_value) override; + virtual bool ObjectGetFloat(JsonValue* handle, const char* key, double* out_value) override; + virtual bool ObjectGetInt(JsonValue* handle, const char* key, int* out_value) override; + virtual bool ObjectGetInt64(JsonValue* handle, const char* key, std::variant* out_value) override; + virtual bool ObjectGetString(JsonValue* handle, const char* key, const char** out_str, size_t* out_len) override; + virtual bool ObjectIsNull(JsonValue* handle, const char* key, bool* out_is_null) override; + virtual bool ObjectHasKey(JsonValue* handle, const char* key, bool use_pointer) override; + virtual bool ObjectRenameKey(JsonValue* handle, const char* old_key, const char* new_key, bool allow_duplicate) override; + virtual bool ObjectSet(JsonValue* handle, const char* key, JsonValue* value) override; + virtual bool ObjectSetBool(JsonValue* handle, const char* key, bool value) override; + virtual bool ObjectSetFloat(JsonValue* handle, const char* key, double value) override; + virtual bool ObjectSetInt(JsonValue* handle, const char* key, int value) override; + virtual bool ObjectSetInt64(JsonValue* handle, const char* key, std::variant value) override; + virtual bool ObjectSetNull(JsonValue* handle, const char* key) override; + virtual bool ObjectSetString(JsonValue* handle, const char* key, const char* value) override; + virtual bool ObjectRemove(JsonValue* handle, const char* key) override; + virtual bool ObjectClear(JsonValue* handle) override; + virtual bool ObjectSort(JsonValue* handle, JSON_SORT_ORDER sort_mode) override; + + // ========== Array Operations ========== + virtual JsonValue* ArrayInit() override; + virtual JsonValue* ArrayInitWithStrings(const char** strings, size_t count) override; + virtual JsonValue* ArrayInitWithInt32(const int32_t* values, size_t count) override; + virtual JsonValue* ArrayInitWithInt64(const char** values, size_t count, + char* error, size_t error_size) override; + virtual JsonValue* ArrayInitWithBool(const bool* values, size_t count) override; + virtual JsonValue* ArrayInitWithFloat(const double* values, size_t count) override; + virtual JsonValue* ArrayParseString(const char* str, yyjson_read_flag read_flg, + char* error, size_t error_size) override; + virtual JsonValue* ArrayParseFile(const char* path, yyjson_read_flag read_flg, + char* error, size_t error_size) override; + virtual size_t ArrayGetSize(JsonValue* handle) override; + virtual JsonValue* ArrayGet(JsonValue* handle, size_t index) override; + virtual JsonValue* ArrayGetFirst(JsonValue* handle) override; + virtual JsonValue* ArrayGetLast(JsonValue* handle) override; + virtual bool ArrayGetBool(JsonValue* handle, size_t index, bool* out_value) override; + virtual bool ArrayGetFloat(JsonValue* handle, size_t index, double* out_value) override; + virtual bool ArrayGetInt(JsonValue* handle, size_t index, int* out_value) override; + virtual bool ArrayGetInt64(JsonValue* handle, size_t index, std::variant* out_value) override; + virtual bool ArrayGetString(JsonValue* handle, size_t index, const char** out_str, size_t* out_len) override; + virtual bool ArrayIsNull(JsonValue* handle, size_t index) override; + virtual bool ArrayReplace(JsonValue* handle, size_t index, JsonValue* value) override; + virtual bool ArrayReplaceBool(JsonValue* handle, size_t index, bool value) override; + virtual bool ArrayReplaceFloat(JsonValue* handle, size_t index, double value) override; + virtual bool ArrayReplaceInt(JsonValue* handle, size_t index, int value) override; + virtual bool ArrayReplaceInt64(JsonValue* handle, size_t index, std::variant value) override; + virtual bool ArrayReplaceNull(JsonValue* handle, size_t index) override; + virtual bool ArrayReplaceString(JsonValue* handle, size_t index, const char* value) override; + virtual bool ArrayAppend(JsonValue* handle, JsonValue* value) override; + virtual bool ArrayAppendBool(JsonValue* handle, bool value) override; + virtual bool ArrayAppendFloat(JsonValue* handle, double value) override; + virtual bool ArrayAppendInt(JsonValue* handle, int value) override; + virtual bool ArrayAppendInt64(JsonValue* handle, std::variant value) override; + virtual bool ArrayAppendNull(JsonValue* handle) override; + virtual bool ArrayAppendString(JsonValue* handle, const char* value) override; + virtual bool ArrayInsert(JsonValue* handle, size_t index, JsonValue* value) override; + virtual bool ArrayInsertBool(JsonValue* handle, size_t index, bool value) override; + virtual bool ArrayInsertInt(JsonValue* handle, size_t index, int value) override; + virtual bool ArrayInsertInt64(JsonValue* handle, size_t index, std::variant value) override; + virtual bool ArrayInsertFloat(JsonValue* handle, size_t index, double value) override; + virtual bool ArrayInsertString(JsonValue* handle, size_t index, const char* value) override; + virtual bool ArrayInsertNull(JsonValue* handle, size_t index) override; + virtual bool ArrayPrepend(JsonValue* handle, JsonValue* value) override; + virtual bool ArrayPrependBool(JsonValue* handle, bool value) override; + virtual bool ArrayPrependInt(JsonValue* handle, int value) override; + virtual bool ArrayPrependInt64(JsonValue* handle, std::variant value) override; + virtual bool ArrayPrependFloat(JsonValue* handle, double value) override; + virtual bool ArrayPrependString(JsonValue* handle, const char* value) override; + virtual bool ArrayPrependNull(JsonValue* handle) override; + virtual bool ArrayRemove(JsonValue* handle, size_t index) override; + virtual bool ArrayRemoveFirst(JsonValue* handle) override; + virtual bool ArrayRemoveLast(JsonValue* handle) override; + virtual bool ArrayRemoveRange(JsonValue* handle, size_t start_index, size_t count) override; + virtual bool ArrayClear(JsonValue* handle) override; + virtual int ArrayIndexOfBool(JsonValue* handle, bool search_value) override; + virtual int ArrayIndexOfString(JsonValue* handle, const char* search_value) override; + virtual int ArrayIndexOfInt(JsonValue* handle, int search_value) override; + virtual int ArrayIndexOfInt64(JsonValue* handle, std::variant search_value) override; + virtual int ArrayIndexOfFloat(JsonValue* handle, double search_value) override; + virtual bool ArraySort(JsonValue* handle, JSON_SORT_ORDER sort_mode) override; + + // ========== Value Operations ========== + virtual JsonValue* Pack(const char* format, IPackParamProvider* param_provider, char* error, size_t error_size) override; + virtual JsonValue* CreateBool(bool value) override; + virtual JsonValue* CreateFloat(double value) override; + virtual JsonValue* CreateInt(int value) override; + virtual JsonValue* CreateInt64(std::variant value) override; + virtual JsonValue* CreateNull() override; + virtual JsonValue* CreateString(const char* value) override; + virtual bool GetBool(JsonValue* handle, bool* out_value) override; + virtual bool GetFloat(JsonValue* handle, double* out_value) override; + virtual bool GetInt(JsonValue* handle, int* out_value) override; + virtual bool GetInt64(JsonValue* handle, std::variant* out_value) override; + virtual bool GetString(JsonValue* handle, const char** out_str, size_t* out_len) override; + + // ========== Pointer Operations ========== + virtual JsonValue* PtrGet(JsonValue* handle, const char* path, char* error, size_t error_size) override; + virtual bool PtrGetBool(JsonValue* handle, const char* path, bool* out_value, char* error, size_t error_size) override; + virtual bool PtrGetFloat(JsonValue* handle, const char* path, double* out_value, char* error, size_t error_size) override; + virtual bool PtrGetInt(JsonValue* handle, const char* path, int* out_value, char* error, size_t error_size) override; + virtual bool PtrGetInt64(JsonValue* handle, const char* path, std::variant* out_value, char* error, size_t error_size) override; + virtual bool PtrGetString(JsonValue* handle, const char* path, const char** out_str, size_t* out_len, char* error, size_t error_size) override; + virtual bool PtrGetIsNull(JsonValue* handle, const char* path, bool* out_is_null, char* error, size_t error_size) override; + virtual bool PtrGetLength(JsonValue* handle, const char* path, size_t* out_len, char* error, size_t error_size) override; + virtual bool PtrSet(JsonValue* handle, const char* path, JsonValue* value, char* error, size_t error_size) override; + virtual bool PtrSetBool(JsonValue* handle, const char* path, bool value, char* error, size_t error_size) override; + virtual bool PtrSetFloat(JsonValue* handle, const char* path, double value, char* error, size_t error_size) override; + virtual bool PtrSetInt(JsonValue* handle, const char* path, int value, char* error, size_t error_size) override; + virtual bool PtrSetInt64(JsonValue* handle, const char* path, std::variant value, char* error, size_t error_size) override; + virtual bool PtrSetString(JsonValue* handle, const char* path, const char* value, char* error, size_t error_size) override; + virtual bool PtrSetNull(JsonValue* handle, const char* path, char* error, size_t error_size) override; + virtual bool PtrAdd(JsonValue* handle, const char* path, JsonValue* value, char* error, size_t error_size) override; + virtual bool PtrAddBool(JsonValue* handle, const char* path, bool value, char* error, size_t error_size) override; + virtual bool PtrAddFloat(JsonValue* handle, const char* path, double value, char* error, size_t error_size) override; + virtual bool PtrAddInt(JsonValue* handle, const char* path, int value, char* error, size_t error_size) override; + virtual bool PtrAddInt64(JsonValue* handle, const char* path, std::variant value, char* error, size_t error_size) override; + virtual bool PtrAddString(JsonValue* handle, const char* path, const char* value, char* error, size_t error_size) override; + virtual bool PtrAddNull(JsonValue* handle, const char* path, char* error, size_t error_size) override; + virtual bool PtrRemove(JsonValue* handle, const char* path, char* error, size_t error_size) override; + virtual JsonValue* PtrTryGet(JsonValue* handle, const char* path) override; + virtual bool PtrTryGetBool(JsonValue* handle, const char* path, bool* out_value) override; + virtual bool PtrTryGetFloat(JsonValue* handle, const char* path, double* out_value) override; + virtual bool PtrTryGetInt(JsonValue* handle, const char* path, int* out_value) override; + virtual bool PtrTryGetInt64(JsonValue* handle, const char* path, std::variant* out_value) override; + virtual bool PtrTryGetString(JsonValue* handle, const char* path, const char** out_str, size_t* out_len) override; + + // ========== Iterator Operations ========== + virtual bool ObjectForeachNext(JsonValue* handle, const char** out_key, + size_t* out_key_len, JsonValue** out_value) override; + virtual bool ArrayForeachNext(JsonValue* handle, size_t* out_index, + JsonValue** out_value) override; + virtual bool ObjectForeachKeyNext(JsonValue* handle, const char** out_key, + size_t* out_key_len) override; + virtual bool ArrayForeachIndexNext(JsonValue* handle, size_t* out_index) override; + + // ========== Array Iterator Operations ========== + virtual JsonArrIter* ArrIterInit(JsonValue* handle) override; + virtual JsonArrIter* ArrIterWith(JsonValue* handle) override; + virtual bool ArrIterReset(JsonArrIter* iter) override; + virtual JsonValue* ArrIterNext(JsonArrIter* iter) override; + virtual bool ArrIterHasNext(JsonArrIter* iter) override; + virtual size_t ArrIterGetIndex(JsonArrIter* iter) override; + virtual void* ArrIterRemove(JsonArrIter* iter) override; + + // ========== Object Iterator Operations ========== + virtual JsonObjIter* ObjIterInit(JsonValue* handle) override; + virtual JsonObjIter* ObjIterWith(JsonValue* handle) override; + virtual bool ObjIterReset(JsonObjIter* iter) override; + virtual void* ObjIterNext(JsonObjIter* iter) override; + virtual bool ObjIterHasNext(JsonObjIter* iter) override; + virtual JsonValue* ObjIterGetVal(JsonObjIter* iter, void* key) override; + virtual JsonValue* ObjIterGet(JsonObjIter* iter, const char* key) override; + virtual size_t ObjIterGetIndex(JsonObjIter* iter) override; + virtual void* ObjIterRemove(JsonObjIter* iter) override; + + // ========== Iterator Release Operations ========== + virtual void ReleaseArrIter(JsonArrIter* iter) override; + virtual void ReleaseObjIter(JsonObjIter* iter) override; + + // ========== Iterator Handle Type Operations ========== + virtual HandleType_t GetArrIterHandleType() override; + virtual HandleType_t GetObjIterHandleType() override; + virtual JsonArrIter* GetArrIterFromHandle(IPluginContext* pContext, Handle_t handle) override; + virtual JsonObjIter* GetObjIterFromHandle(IPluginContext* pContext, Handle_t handle) override; + + // ========== Release Operations ========== + virtual void Release(JsonValue* value) override; + + // ========== Handle Type Operations ========== + virtual HandleType_t GetHandleType() override; + + // ========== Handle Operations ========== + virtual JsonValue* GetFromHandle(IPluginContext* pContext, Handle_t handle) override; + + // ========== Number Read/Write Operations ========== + virtual JsonValue* ReadNumber(const char* dat, uint32_t read_flg = 0, + char* error = nullptr, size_t error_size = 0, size_t* out_consumed = nullptr) override; + virtual bool WriteNumber(JsonValue* handle, char* buffer, size_t buffer_size, + size_t* out_written = nullptr) override; + + // ========== Floating-Point Format Operations ========== + virtual bool SetFpToFloat(JsonValue* handle, bool flt) override; + virtual bool SetFpToFixed(JsonValue* handle, int prec) override; + + // ========== Direct Value Modification Operations ========== + virtual bool SetBool(JsonValue* handle, bool value) override; + virtual bool SetInt(JsonValue* handle, int value) override; + virtual bool SetInt64(JsonValue* handle, std::variant value) override; + virtual bool SetFloat(JsonValue* handle, double value) override; + virtual bool SetString(JsonValue* handle, const char* value) override; + virtual bool SetNull(JsonValue* handle) override; + + virtual bool ParseInt64Variant(const char* value, std::variant* out_value, + char* error = nullptr, size_t error_size = 0) override; + +private: + std::random_device m_randomDevice; + std::mt19937 m_randomGenerator; + + // Helper methods + static std::unique_ptr CreateWrapper(); + static RefPtr WrapDocument(yyjson_mut_doc* doc); + static RefPtr CopyDocument(yyjson_doc* doc); + static RefPtr CreateDocument(); + static RefPtr WrapImmutableDocument(yyjson_doc* doc); + static RefPtr CloneValueToMutable(JsonValue* value); + + // Pack helper methods + static const char* SkipSeparators(const char* ptr); + static yyjson_mut_val* PackImpl(yyjson_mut_doc* doc, const char* format, + IPackParamProvider* provider, char* error, + size_t error_size, const char** out_end_ptr); + + // PtrTryGet helper methods + struct PtrGetValueResult { + yyjson_mut_val* mut_val{ nullptr }; + yyjson_val* imm_val{ nullptr }; + bool success{ false }; + }; + static PtrGetValueResult PtrGetValueInternal(JsonValue* handle, const char* path); +}; + +#endif // _INCLUDE_JSONMANAGER_H_ \ No newline at end of file diff --git a/extensions/json/JsonNatives.cpp b/extensions/json/JsonNatives.cpp new file mode 100755 index 0000000000..c964a434ee --- /dev/null +++ b/extensions/json/JsonNatives.cpp @@ -0,0 +1,3498 @@ +#include "extension.h" +#include "JsonManager.h" + +class SourceModPackParamProvider : public IPackParamProvider +{ +private: + IPluginContext* m_pContext; + const cell_t* m_pParams; + unsigned int m_currentIndex; + +public: + SourceModPackParamProvider(IPluginContext* pContext, const cell_t* params, unsigned int startIndex) + : m_pContext(pContext), m_pParams(params), m_currentIndex(startIndex) {} + + bool GetNextString(const char** out_str) override { + char* str; + if (m_pContext->LocalToString(m_pParams[m_currentIndex++], &str) != SP_ERROR_NONE) { + return false; + } + *out_str = str; + return str != nullptr; + } + + bool GetNextInt(int* out_value) override { + cell_t* val; + if (m_pContext->LocalToPhysAddr(m_pParams[m_currentIndex++], &val) != SP_ERROR_NONE) { + return false; + } + *out_value = *val; + return true; + } + + bool GetNextFloat(float* out_value) override { + cell_t* val; + if (m_pContext->LocalToPhysAddr(m_pParams[m_currentIndex++], &val) != SP_ERROR_NONE) { + return false; + } + *out_value = sp_ctof(*val); + return true; + } + + bool GetNextBool(bool* out_value) override { + cell_t* val; + if (m_pContext->LocalToPhysAddr(m_pParams[m_currentIndex++], &val) != SP_ERROR_NONE) { + return false; + } + *out_value = (*val != 0); + return true; + } +}; + +/** + * Helper function: Create a SourceMod handle for JsonValue and return it directly + * Used by functions that return Handle_t + * + * @param pContext Plugin context + * @param pJSONValue JSON value to wrap (will be released on failure) + * @param error_context Descriptive context for error messages + * @return Handle on success, throws native error on failure + */ +static cell_t CreateAndReturnHandle(IPluginContext* pContext, JsonValue* pJSONValue, const char* error_context) +{ + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to create %s", error_context); + } + + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + pJSONValue->m_handle = handlesys->CreateHandleEx(g_JsonType, pJSONValue, &sec, nullptr, &err); + + if (!pJSONValue->m_handle) { + g_pJsonManager->Release(pJSONValue); + return pContext->ThrowNativeError("Failed to create handle for %s (error code: %d)", error_context, err); + } + + return pJSONValue->m_handle; +} + +/** + * Helper function: Create a SourceMod handle for JsonValue and assign to output parameter + * Used by iterator functions (foreach) that assign handle via reference + * + * @param pContext Plugin context + * @param pJSONValue JSON value to wrap (will be released on failure) + * @param param_index Parameter index for output handle + * @param error_context Descriptive context for error messages + * @return true on success, false on failure (throws native error) + */ +static bool CreateAndAssignHandle(IPluginContext* pContext, JsonValue* pJSONValue, + cell_t param_index, const char* error_context) +{ + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + pJSONValue->m_handle = handlesys->CreateHandleEx(g_JsonType, pJSONValue, &sec, nullptr, &err); + + if (!pJSONValue->m_handle) { + g_pJsonManager->Release(pJSONValue); + pContext->ThrowNativeError("Failed to create handle for %s (error code: %d)", error_context, err); + return false; + } + + cell_t* valHandle; + pContext->LocalToPhysAddr(param_index, &valHandle); + *valHandle = pJSONValue->m_handle; + return true; +} + +/** + * Helper function: Create a SourceMod handle for JsonArrIter and return it directly + */ +static cell_t CreateAndReturnArrIterHandle(IPluginContext* pContext, JsonArrIter* iter, const char* error_context) +{ + if (!iter) { + return pContext->ThrowNativeError("Failed to create %s", error_context); + } + + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + Handle_t handle = handlesys->CreateHandleEx(g_ArrIterType, iter, &sec, nullptr, &err); + + if (!handle) { + g_pJsonManager->ReleaseArrIter(iter); + return pContext->ThrowNativeError("Failed to create handle for %s (error code: %d)", error_context, err); + } + + return handle; +} + +/** + * Helper function: Create a SourceMod handle for JsonObjIter and return it directly + */ +static cell_t CreateAndReturnObjIterHandle(IPluginContext* pContext, JsonObjIter* iter, const char* error_context) +{ + if (!iter) { + return pContext->ThrowNativeError("Failed to create %s", error_context); + } + + HandleError err; + HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity()); + Handle_t handle = handlesys->CreateHandleEx(g_ObjIterType, iter, &sec, nullptr, &err); + + if (!handle) { + g_pJsonManager->ReleaseObjIter(iter); + return pContext->ThrowNativeError("Failed to create handle for %s (error code: %d)", error_context, err); + } + + return handle; +} + +static cell_t json_pack(IPluginContext* pContext, const cell_t* params) +{ + // SourcePawn has a limit of 32 parameters (defined SP_MAX_EXEC_PARAMS) + // including the format string, so we need to check if the number of parameters is less than the limit + if (params[0] > SP_MAX_EXEC_PARAMS - 1) { + return pContext->ThrowNativeError("Too many parameters (max %d)", SP_MAX_EXEC_PARAMS - 1); + } + + char* fmt; + pContext->LocalToString(params[1], &fmt); + + SourceModPackParamProvider provider(pContext, params, 2); + + char error[JSON_PACK_ERROR_SIZE]; + JsonValue* pJSONValue = g_pJsonManager->Pack(fmt, &provider, error, sizeof(error)); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to pack JSON: %s", error); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "packed JSON"); +} + +static cell_t json_doc_parse(IPluginContext* pContext, const cell_t* params) +{ + char* str; + pContext->LocalToString(params[1], &str); + + bool is_file = params[2]; + bool is_mutable_doc = params[3]; + uint32_t read_flg = static_cast(params[4]); + + char error[JSON_ERROR_BUFFER_SIZE]; + JsonValue* pJSONValue = g_pJsonManager->ParseJSON(str, is_file, is_mutable_doc, read_flg, error, sizeof(error)); + + if (!pJSONValue) { + return pContext->ThrowNativeError(error); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "parsed JSON document"); +} + +static cell_t json_doc_equals(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle1 = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* handle2 = g_pJsonManager->GetFromHandle(pContext, params[2]); + + if (!handle1 || !handle2) return 0; + + return g_pJsonManager->Equals(handle1, handle2); +} + +static cell_t json_doc_copy_deep(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* targetDoc = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* sourceValue = g_pJsonManager->GetFromHandle(pContext, params[2]); + + if (!targetDoc || !sourceValue) return 0; + + JsonValue* pJSONValue = g_pJsonManager->DeepCopy(targetDoc, sourceValue); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to copy JSON value"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "copied JSON value"); +} + +static cell_t json_get_type_desc(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + const char* type_desc = g_pJsonManager->GetTypeDesc(handle); + pContext->StringToLocalUTF8(params[2], params[3], type_desc, nullptr); + + return 1; +} + +static cell_t json_obj_parse_str(IPluginContext* pContext, const cell_t* params) +{ + char* str; + pContext->LocalToString(params[1], &str); + uint32_t read_flg = static_cast(params[2]); + + char error[JSON_PACK_ERROR_SIZE]; + JsonValue* pJSONValue = g_pJsonManager->ObjectParseString(str, read_flg, error, sizeof(error)); + + if (!pJSONValue) { + return pContext->ThrowNativeError(error); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON object from string"); +} + +static cell_t json_obj_parse_file(IPluginContext* pContext, const cell_t* params) +{ + char* path; + pContext->LocalToString(params[1], &path); + uint32_t read_flg = static_cast(params[2]); + + char error[JSON_PACK_ERROR_SIZE]; + JsonValue* pJSONValue = g_pJsonManager->ObjectParseFile(path, read_flg, error, sizeof(error)); + + if (!pJSONValue) { + return pContext->ThrowNativeError(error); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON object from file"); +} + +static cell_t json_arr_parse_str(IPluginContext* pContext, const cell_t* params) +{ + char* str; + pContext->LocalToString(params[1], &str); + uint32_t read_flg = static_cast(params[2]); + + char error[JSON_PACK_ERROR_SIZE]; + JsonValue* pJSONValue = g_pJsonManager->ArrayParseString(str, read_flg, error, sizeof(error)); + + if (!pJSONValue) { + return pContext->ThrowNativeError(error); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON array from string"); +} + +static cell_t json_arr_parse_file(IPluginContext* pContext, const cell_t* params) +{ + char* path; + pContext->LocalToString(params[1], &path); + uint32_t read_flg = static_cast(params[2]); + + char error[JSON_PACK_ERROR_SIZE]; + JsonValue* pJSONValue = g_pJsonManager->ArrayParseFile(path, read_flg, error, sizeof(error)); + + if (!pJSONValue) { + return pContext->ThrowNativeError(error); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON array from file"); +} + +static cell_t json_arr_index_of_bool(IPluginContext *pContext, const cell_t *params) +{ + JsonValue *handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + bool searchValue = params[2]; + return g_pJsonManager->ArrayIndexOfBool(handle, searchValue); +} + +static cell_t json_arr_index_of_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* searchStr; + pContext->LocalToString(params[2], &searchStr); + + return g_pJsonManager->ArrayIndexOfString(handle, searchStr); +} + +static cell_t json_arr_index_of_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + int searchValue = params[2]; + return g_pJsonManager->ArrayIndexOfInt(handle, searchValue); +} + +static cell_t json_arr_index_of_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* searchStr; + pContext->LocalToString(params[2], &searchStr); + + std::variant variant_value; + char error[JSON_ERROR_BUFFER_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(searchStr, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return g_pJsonManager->ArrayIndexOfInt64(handle, variant_value); +} + +static cell_t json_arr_index_of_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + float searchValue = sp_ctof(params[2]); + return g_pJsonManager->ArrayIndexOfFloat(handle, searchValue); +} + +static cell_t json_get_type(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->GetType(handle); +} + +static cell_t json_get_subtype(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->GetSubtype(handle); +} + +static cell_t json_is_array(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsArray(handle); +} + +static cell_t json_is_object(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsObject(handle); +} + +static cell_t json_is_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsInt(handle); +} + +static cell_t json_is_uint(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsUint(handle); +} + +static cell_t json_is_sint(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsSint(handle); +} + +static cell_t json_is_num(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsNum(handle); +} + +static cell_t json_is_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsBool(handle); +} + +static cell_t json_is_true(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsTrue(handle); +} + +static cell_t json_is_false(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsFalse(handle); +} + +static cell_t json_is_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsFloat(handle); +} + +static cell_t json_is_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsStr(handle); +} + +static cell_t json_is_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsNull(handle); +} + +static cell_t json_is_ctn(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsCtn(handle); +} + +static cell_t json_is_mutable(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsMutable(handle); +} + +static cell_t json_is_immutable(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->IsImmutable(handle); +} + +static cell_t json_obj_init(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* pJSONValue = g_pJsonManager->ObjectInit(); + return CreateAndReturnHandle(pContext, pJSONValue, "JSON object"); +} + +static cell_t json_obj_init_with_str(IPluginContext* pContext, const cell_t* params) +{ + cell_t* addr; + pContext->LocalToPhysAddr(params[1], &addr); + cell_t array_size = params[2]; + + if (array_size < 2) { + return pContext->ThrowNativeError("Array must contain at least one key-value pair"); + } + if (array_size % 2 != 0) { + return pContext->ThrowNativeError("Array must contain an even number of strings (got %d)", array_size); + } + + std::vector kv_pairs; + kv_pairs.reserve(array_size); + + for (cell_t i = 0; i < array_size; i += 2) { + char* key; + char* value; + + if (pContext->LocalToString(addr[i], &key) != SP_ERROR_NONE) { + return pContext->ThrowNativeError("Failed to read key at index %d", i); + } + if (!key || !key[0]) { + return pContext->ThrowNativeError("Empty key at index %d", i); + } + + if (pContext->LocalToString(addr[i + 1], &value) != SP_ERROR_NONE) { + return pContext->ThrowNativeError("Failed to read value at index %d", i + 1); + } + if (!value) { + return pContext->ThrowNativeError("Invalid value at index %d", i + 1); + } + + kv_pairs.push_back(key); + kv_pairs.push_back(value); + } + + JsonValue* pJSONValue = g_pJsonManager->ObjectInitWithStrings(kv_pairs.data(), array_size / 2); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to create JSON object from key-value pairs"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON object from key-value pairs"); +} + +static cell_t json_create_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* pJSONValue = g_pJsonManager->CreateBool(params[1]); + return CreateAndReturnHandle(pContext, pJSONValue, "JSON boolean value"); +} + +static cell_t json_create_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* pJSONValue = g_pJsonManager->CreateFloat(sp_ctof(params[1])); + return CreateAndReturnHandle(pContext, pJSONValue, "JSON float value"); +} + +static cell_t json_create_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* pJSONValue = g_pJsonManager->CreateInt(params[1]); + return CreateAndReturnHandle(pContext, pJSONValue, "JSON integer value"); +} + +static cell_t json_create_integer64(IPluginContext* pContext, const cell_t* params) +{ + char* value; + pContext->LocalToString(params[1], &value); + + std::variant variant_value; + char error[JSON_ERROR_BUFFER_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + JsonValue* pJSONValue = g_pJsonManager->CreateInt64(variant_value); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to create JSON integer64 value"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON integer64 value"); +} + +static cell_t json_create_str(IPluginContext* pContext, const cell_t* params) +{ + char* str; + pContext->LocalToString(params[1], &str); + + JsonValue* pJSONValue = g_pJsonManager->CreateString(str); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to create JSON string value"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON string value"); +} + +static cell_t json_get_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + bool value; + if (!g_pJsonManager->GetBool(handle, &value)) { + return pContext->ThrowNativeError("Type mismatch: expected boolean value"); + } + + return value; +} + +static cell_t json_get_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + double value; + if (!g_pJsonManager->GetFloat(handle, &value)) { + return pContext->ThrowNativeError("Type mismatch: expected float value"); + } + + return sp_ftoc(static_cast(value)); +} + +static cell_t json_get_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + int value; + if (!g_pJsonManager->GetInt(handle, &value)) { + return pContext->ThrowNativeError("Type mismatch: expected integer value"); + } + + return value; +} + +static cell_t json_get_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + std::variant value; + if (!g_pJsonManager->GetInt64(handle, &value)) { + return pContext->ThrowNativeError("Type mismatch: expected integer64 value"); + } + + char result[JSON_INT64_BUFFER_SIZE]; + if (std::holds_alternative(value)) { + snprintf(result, sizeof(result), "%" PRIu64, std::get(value)); + } else { + snprintf(result, sizeof(result), "%" PRId64, std::get(value)); + } + pContext->StringToLocalUTF8(params[2], params[3], result, nullptr); + + return 1; +} + +static cell_t json_get_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + const char* str; + size_t len; + + if (!g_pJsonManager->GetString(handle, &str, &len)) { + return pContext->ThrowNativeError("Type mismatch: expected string value"); + } + + size_t maxlen = static_cast(params[3]); + + if (len + 1 > maxlen) { + return pContext->ThrowNativeError("Buffer is too small (need %d, have %d)", len + 1, maxlen); + } + + pContext->StringToLocalUTF8(params[2], maxlen, str, nullptr); + + return 1; +} + +static cell_t json_equals_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + char* str; + pContext->LocalToString(params[2], &str); + + return g_pJsonManager->EqualsStr(handle, str); +} + +static cell_t json_get_serialized_size(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + uint32_t write_flg = static_cast(params[2]); + size_t size = g_pJsonManager->GetSerializedSize(handle, write_flg); + + return static_cast(size); +} + +static cell_t json_get_read_size(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + size_t size = g_pJsonManager->GetReadSize(handle); + if (size == 0) return 0; + + return static_cast(size); +} + +static cell_t json_get_ref_count(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + return g_pJsonManager->GetRefCount(handle); +} + +static cell_t json_create_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* pJSONValue = g_pJsonManager->CreateNull(); + return CreateAndReturnHandle(pContext, pJSONValue, "JSON null value"); +} + +static cell_t json_arr_init(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* pJSONValue = g_pJsonManager->ArrayInit(); + return CreateAndReturnHandle(pContext, pJSONValue, "JSON array"); +} + +static cell_t json_arr_init_with_str(IPluginContext* pContext, const cell_t* params) +{ + cell_t* addr; + pContext->LocalToPhysAddr(params[1], &addr); + cell_t array_size = params[2]; + + std::vector strs; + strs.reserve(array_size); + + for (cell_t i = 0; i < array_size; i++) { + char* str; + pContext->LocalToString(addr[i], &str); + strs.push_back(str); + } + + JsonValue* pJSONValue = g_pJsonManager->ArrayInitWithStrings(strs.data(), strs.size()); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to create JSON array from strings"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON array from strings"); +} + +static cell_t json_arr_init_with_int32(IPluginContext* pContext, const cell_t* params) +{ + cell_t* addr; + pContext->LocalToPhysAddr(params[1], &addr); + cell_t array_size = params[2]; + + std::vector values; + values.reserve(array_size); + + for (cell_t i = 0; i < array_size; i++) { + values.push_back(static_cast(addr[i])); + } + + JsonValue* pJSONValue = g_pJsonManager->ArrayInitWithInt32(values.data(), values.size()); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to create JSON array from int32 values"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON array from int32 values"); +} + +static cell_t json_arr_init_with_int64(IPluginContext* pContext, const cell_t* params) +{ + cell_t* addr; + pContext->LocalToPhysAddr(params[1], &addr); + cell_t array_size = params[2]; + + std::vector strs; + strs.reserve(array_size); + + for (cell_t i = 0; i < array_size; i++) { + char* str; + pContext->LocalToString(addr[i], &str); + strs.push_back(str); + } + + char error[JSON_ERROR_BUFFER_SIZE]; + JsonValue* pJSONValue = g_pJsonManager->ArrayInitWithInt64(strs.data(), strs.size(), error, sizeof(error)); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to create JSON array from int64 values: %s", error); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON array from int64 values"); +} + +static cell_t json_arr_init_with_bool(IPluginContext* pContext, const cell_t* params) +{ + cell_t* addr; + pContext->LocalToPhysAddr(params[1], &addr); + cell_t array_size = params[2]; + + // std::vector is specialized and doesn't work with .data() so we use a unique_ptr + auto values = std::make_unique(array_size); + + for (cell_t i = 0; i < array_size; i++) { + values[i] = (addr[i] != 0); + } + + JsonValue* pJSONValue = g_pJsonManager->ArrayInitWithBool(values.get(), array_size); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to create JSON array from bool values"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON array from bool values"); +} + +static cell_t json_arr_init_with_float(IPluginContext* pContext, const cell_t* params) +{ + cell_t* addr; + pContext->LocalToPhysAddr(params[1], &addr); + cell_t array_size = params[2]; + + std::vector values; + values.reserve(array_size); + + for (cell_t i = 0; i < array_size; i++) { + values.push_back(sp_ctof(addr[i])); + } + + JsonValue* pJSONValue = g_pJsonManager->ArrayInitWithFloat(values.data(), values.size()); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to create JSON array from float values"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON array from float values"); +} + +static cell_t json_arr_get_size(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + size_t size = g_pJsonManager->ArrayGetSize(handle); + return static_cast(size); +} + +static cell_t json_arr_get_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + JsonValue* pJSONValue = g_pJsonManager->ArrayGet(handle, index); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Index %d is out of bounds", index); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON array value"); +} + +static cell_t json_arr_get_first(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + JsonValue* pJSONValue = g_pJsonManager->ArrayGetFirst(handle); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Array is empty"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "first JSON array value"); +} + +static cell_t json_arr_get_last(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + JsonValue* pJSONValue = g_pJsonManager->ArrayGetLast(handle); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Array is empty"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "last JSON array value"); +} + +static cell_t json_arr_get_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + bool value; + if (!g_pJsonManager->ArrayGetBool(handle, index, &value)) { + return pContext->ThrowNativeError("Failed to get boolean at index %d", index); + } + + return value; +} + +static cell_t json_arr_get_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + double value; + if (!g_pJsonManager->ArrayGetFloat(handle, index, &value)) { + return pContext->ThrowNativeError("Failed to get float at index %d", index); + } + + return sp_ftoc(static_cast(value)); +} + +static cell_t json_arr_get_integer(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + int value; + if (!g_pJsonManager->ArrayGetInt(handle, index, &value)) { + return pContext->ThrowNativeError("Failed to get integer at index %d", index); + } + + return value; +} + +static cell_t json_arr_get_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + std::variant value; + if (!g_pJsonManager->ArrayGetInt64(handle, index, &value)) { + return pContext->ThrowNativeError("Failed to get integer64 at index %d", index); + } + + char result[JSON_INT64_BUFFER_SIZE]; + if (std::holds_alternative(value)) { + snprintf(result, sizeof(result), "%" PRIu64, std::get(value)); + } else { + snprintf(result, sizeof(result), "%" PRId64, std::get(value)); + } + pContext->StringToLocalUTF8(params[3], params[4], result, nullptr); + + return 1; +} + +static cell_t json_arr_get_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + const char* str; + size_t len; + if (!g_pJsonManager->ArrayGetString(handle, index, &str, &len)) { + return pContext->ThrowNativeError("Failed to get string at index %d", index); + } + + size_t maxlen = static_cast(params[4]); + if (len + 1 > maxlen) { + return pContext->ThrowNativeError("Buffer is too small (need %d, have %d)", len + 1, maxlen); + } + + pContext->StringToLocalUTF8(params[3], maxlen, str, nullptr); + + return 1; +} + +static cell_t json_arr_is_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + return g_pJsonManager->ArrayIsNull(handle, index); +} + +static cell_t json_arr_replace_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle1 = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* handle2 = g_pJsonManager->GetFromHandle(pContext, params[3]); + + if (!handle1 || !handle2) return 0; + + if (!handle1->IsMutable()) { + return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + return g_pJsonManager->ArrayReplace(handle1, index, handle2); +} + +static cell_t json_arr_replace_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + return g_pJsonManager->ArrayReplaceBool(handle, index, params[3]); +} + +static cell_t json_arr_replace_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + return g_pJsonManager->ArrayReplaceFloat(handle, index, sp_ctof(params[3])); +} + +static cell_t json_arr_replace_integer(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + return g_pJsonManager->ArrayReplaceInt(handle, index, params[3]); +} + +static cell_t json_arr_replace_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array"); + } + + char* value; + pContext->LocalToString(params[3], &value); + + std::variant variant_value; + char error[JSON_ERROR_BUFFER_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + return g_pJsonManager->ArrayReplaceInt64(handle, index, variant_value); +} + +static cell_t json_arr_replace_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + return g_pJsonManager->ArrayReplaceNull(handle, index); +} + +static cell_t json_arr_replace_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot replace value in an immutable JSON array"); + } + + char* val; + pContext->LocalToString(params[3], &val); + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + return g_pJsonManager->ArrayReplaceString(handle, index, val); +} + +static cell_t json_arr_append_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle1 = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* handle2 = g_pJsonManager->GetFromHandle(pContext, params[2]); + + if (!handle1 || !handle2) return 0; + + if (!handle1->IsMutable()) { + return pContext->ThrowNativeError("Cannot append value to an immutable JSON array"); + } + + return g_pJsonManager->ArrayAppend(handle1, handle2); +} + +static cell_t json_arr_append_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot append value to an immutable JSON array"); + } + + return g_pJsonManager->ArrayAppendBool(handle, params[2]); +} + +static cell_t json_arr_append_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot append value to an immutable JSON array"); + } + + return g_pJsonManager->ArrayAppendFloat(handle, sp_ctof(params[2])); +} + +static cell_t json_arr_append_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot append value to an immutable JSON array"); + } + + return g_pJsonManager->ArrayAppendInt(handle, params[2]); +} + +static cell_t json_arr_append_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot append value to an immutable JSON array"); + } + + char* value; + pContext->LocalToString(params[2], &value); + + std::variant variant_value; + char error[JSON_ERROR_BUFFER_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return g_pJsonManager->ArrayAppendInt64(handle, variant_value); +} + +static cell_t json_arr_append_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot append value to an immutable JSON array"); + } + + return g_pJsonManager->ArrayAppendNull(handle); +} + +static cell_t json_arr_append_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot append value to an immutable JSON array"); + } + + char* str; + pContext->LocalToString(params[2], &str); + + return g_pJsonManager->ArrayAppendString(handle, str); +} + +static cell_t json_arr_insert(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + size_t arr_size = g_pJsonManager->ArrayGetSize(handle); + if (index > arr_size) { + return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size); + } + + JsonValue* value = g_pJsonManager->GetFromHandle(pContext, params[3]); + if (!value) return 0; + + return g_pJsonManager->ArrayInsert(handle, index, value); +} + +static cell_t json_arr_insert_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + size_t arr_size = g_pJsonManager->ArrayGetSize(handle); + if (index > arr_size) { + return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size); + } + + bool value = params[3] != 0; + + return g_pJsonManager->ArrayInsertBool(handle, index, value); +} + +static cell_t json_arr_insert_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + size_t arr_size = g_pJsonManager->ArrayGetSize(handle); + if (index > arr_size) { + return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size); + } + + int value = params[3]; + + return g_pJsonManager->ArrayInsertInt(handle, index, value); +} + +static cell_t json_arr_insert_int64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + size_t arr_size = g_pJsonManager->ArrayGetSize(handle); + if (index > arr_size) { + return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size); + } + + char* value; + pContext->LocalToString(params[3], &value); + + std::variant variant_value; + char error[JSON_ERROR_BUFFER_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return g_pJsonManager->ArrayInsertInt64(handle, index, variant_value); +} + +static cell_t json_arr_insert_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + size_t arr_size = g_pJsonManager->ArrayGetSize(handle); + if (index > arr_size) { + return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size); + } + + float value = sp_ctof(params[3]); + + return g_pJsonManager->ArrayInsertFloat(handle, index, value); +} + +static cell_t json_arr_insert_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + size_t arr_size = g_pJsonManager->ArrayGetSize(handle); + if (index > arr_size) { + return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size); + } + + char* str; + pContext->LocalToString(params[3], &str); + + return g_pJsonManager->ArrayInsertString(handle, index, str); +} + +static cell_t json_arr_insert_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot insert value into an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + size_t arr_size = g_pJsonManager->ArrayGetSize(handle); + if (index > arr_size) { + return pContext->ThrowNativeError("Index is out of bounds (got %d, max %zu)", index, arr_size); + } + + + return g_pJsonManager->ArrayInsertNull(handle, index); +} + +static cell_t json_arr_prepend(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot prepend value to an immutable JSON array"); + } + + JsonValue* value = g_pJsonManager->GetFromHandle(pContext, params[2]); + if (!value) return 0; + + return g_pJsonManager->ArrayPrepend(handle, value); +} + +static cell_t json_arr_prepend_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot prepend value to an immutable JSON array"); + } + + bool value = params[2] != 0; + + return g_pJsonManager->ArrayPrependBool(handle, value); +} + +static cell_t json_arr_prepend_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot prepend value to an immutable JSON array"); + } + + int value = params[2]; + + return g_pJsonManager->ArrayPrependInt(handle, value); +} + +static cell_t json_arr_prepend_int64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot prepend value to an immutable JSON array"); + } + + char* value; + pContext->LocalToString(params[2], &value); + + std::variant variant_value; + char error[JSON_ERROR_BUFFER_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return g_pJsonManager->ArrayPrependInt64(handle, variant_value); +} + +static cell_t json_arr_prepend_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot prepend value to an immutable JSON array"); + } + + float value = sp_ctof(params[2]); + + return g_pJsonManager->ArrayPrependFloat(handle, value); +} + +static cell_t json_arr_prepend_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot prepend value to an immutable JSON array"); + } + + char* str; + pContext->LocalToString(params[2], &str); + + return g_pJsonManager->ArrayPrependString(handle, str); +} + +static cell_t json_arr_prepend_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot prepend value to an immutable JSON array"); + } + + return g_pJsonManager->ArrayPrependNull(handle); +} + +static cell_t json_arr_remove(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot remove value from an immutable JSON array"); + } + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + return g_pJsonManager->ArrayRemove(handle, index); +} + +static cell_t json_arr_remove_first(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot remove value from an immutable JSON array"); + } + + return g_pJsonManager->ArrayRemoveFirst(handle); +} + +static cell_t json_arr_remove_last(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot remove value from an immutable JSON array"); + } + + return g_pJsonManager->ArrayRemoveLast(handle); +} + +static cell_t json_arr_remove_range(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot remove value from an immutable JSON array"); + } + + cell_t start_index_param = params[2]; + cell_t count_param = params[3]; + if (start_index_param < 0) { + return pContext->ThrowNativeError("Start index must be >= 0 (got %d)", start_index_param); + } + if (count_param < 0) { + return pContext->ThrowNativeError("Count must be >= 0 (got %d)", count_param); + } + + size_t start_index = static_cast(start_index_param); + size_t count = static_cast(count_param); + return g_pJsonManager->ArrayRemoveRange(handle, start_index, count); +} + +static cell_t json_arr_clear(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot clear an immutable JSON array"); + } + + return g_pJsonManager->ArrayClear(handle); +} + +static cell_t json_doc_write_to_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + size_t buffer_size = static_cast(params[3]); + uint32_t write_flg = static_cast(params[4]); + + size_t json_size; + char* json_str = g_pJsonManager->WriteToStringPtr(handle, write_flg, &json_size); + + if (!json_str) { + return pContext->ThrowNativeError("Failed to serialize JSON"); + } + + if (json_size > buffer_size) { + free(json_str); + return pContext->ThrowNativeError("Buffer too small (need %d, have %d)", json_size, buffer_size); + } + + pContext->StringToLocalUTF8(params[2], buffer_size, json_str, nullptr); + free(json_str); + + return static_cast(json_size); +} + +static cell_t json_doc_write_to_file(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + uint32_t write_flg = static_cast(params[3]); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->WriteToFile(handle, path, write_flg, error, sizeof(error))) { + return pContext->ThrowNativeError(error); + } + + return true; +} + +static cell_t json_obj_get_size(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + size_t size = g_pJsonManager->ObjectGetSize(handle); + return static_cast(size); +} + +static cell_t json_obj_get_key(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + const char* key; + + if (!g_pJsonManager->ObjectGetKey(handle, index, &key)) { + return pContext->ThrowNativeError("Index %d is out of bounds", index); + } + + pContext->StringToLocalUTF8(params[3], params[4], key, nullptr); + return 1; +} + +static cell_t json_obj_get_val_at(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + cell_t index_param = params[2]; + if (index_param < 0) { + return pContext->ThrowNativeError("Index must be >= 0 (got %d)", index_param); + } + size_t index = static_cast(index_param); + + JsonValue* pJSONValue = g_pJsonManager->ObjectGetValueAt(handle, index); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Index %d is out of bounds", index); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON object value"); +} + +static cell_t json_obj_get_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* key; + pContext->LocalToString(params[2], &key); + + JsonValue* pJSONValue = g_pJsonManager->ObjectGet(handle, key); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Key not found: %s", key); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON object value"); +} + +static cell_t json_obj_get_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* key; + pContext->LocalToString(params[2], &key); + + bool value; + if (!g_pJsonManager->ObjectGetBool(handle, key, &value)) { + return pContext->ThrowNativeError("Failed to get boolean for key '%s'", key); + } + + return value; +} + +static cell_t json_obj_get_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* key; + pContext->LocalToString(params[2], &key); + + double value; + if (!g_pJsonManager->ObjectGetFloat(handle, key, &value)) { + return pContext->ThrowNativeError("Failed to get float for key '%s'", key); + } + + return sp_ftoc(static_cast(value)); +} + +static cell_t json_obj_get_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* key; + pContext->LocalToString(params[2], &key); + + int value; + if (!g_pJsonManager->ObjectGetInt(handle, key, &value)) { + return pContext->ThrowNativeError("Failed to get integer for key '%s'", key); + } + + return value; +} + +static cell_t json_obj_get_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* key; + pContext->LocalToString(params[2], &key); + + std::variant value; + if (!g_pJsonManager->ObjectGetInt64(handle, key, &value)) { + return pContext->ThrowNativeError("Failed to get integer64 for key '%s'", key); + } + + char result[JSON_INT64_BUFFER_SIZE]; + if (std::holds_alternative(value)) { + snprintf(result, sizeof(result), "%" PRIu64, std::get(value)); + } else { + snprintf(result, sizeof(result), "%" PRId64, std::get(value)); + } + pContext->StringToLocalUTF8(params[3], params[4], result, nullptr); + + return 1; +} + +static cell_t json_obj_get_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* key; + pContext->LocalToString(params[2], &key); + + const char* str; + size_t len; + if (!g_pJsonManager->ObjectGetString(handle, key, &str, &len)) { + return pContext->ThrowNativeError("Failed to get string for key '%s'", key); + } + + size_t maxlen = static_cast(params[4]); + if (len + 1 > maxlen) { + return pContext->ThrowNativeError("Buffer is too small (need %d, have %d)", len + 1, maxlen); + } + + pContext->StringToLocalUTF8(params[3], maxlen, str, nullptr); + + return 1; +} + +static cell_t json_obj_clear(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot clear an immutable JSON object"); + } + + return g_pJsonManager->ObjectClear(handle); +} + +static cell_t json_obj_is_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* key; + pContext->LocalToString(params[2], &key); + + bool is_null; + if (!g_pJsonManager->ObjectIsNull(handle, key, &is_null)) { + return pContext->ThrowNativeError("Key not found: %s", key); + } + + return is_null; +} + +static cell_t json_obj_has_key(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* key; + pContext->LocalToString(params[2], &key); + + bool ptr_use = params[3]; + + return g_pJsonManager->ObjectHasKey(handle, key, ptr_use); +} + +static cell_t json_obj_rename_key(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot rename key in an immutable JSON object"); + } + + char* old_key; + pContext->LocalToString(params[2], &old_key); + + char* new_key; + pContext->LocalToString(params[3], &new_key); + + bool allow_duplicate = params[4]; + + if (!g_pJsonManager->ObjectRenameKey(handle, old_key, new_key, allow_duplicate)) { + return pContext->ThrowNativeError("Failed to rename key from '%s' to '%s'", old_key, new_key); + } + + return true; +} + +static cell_t json_obj_set_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle1 = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* handle2 = g_pJsonManager->GetFromHandle(pContext, params[3]); + + if (!handle1 || !handle2) return 0; + + if (!handle1->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON object"); + } + + char* key; + pContext->LocalToString(params[2], &key); + + return g_pJsonManager->ObjectSet(handle1, key, handle2); +} + +static cell_t json_obj_set_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON object"); + } + + char* key; + pContext->LocalToString(params[2], &key); + + return g_pJsonManager->ObjectSetBool(handle, key, params[3]); +} + +static cell_t json_obj_set_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON object"); + } + + char* key; + pContext->LocalToString(params[2], &key); + + return g_pJsonManager->ObjectSetFloat(handle, key, sp_ctof(params[3])); +} + +static cell_t json_obj_set_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON object"); + } + + char* key; + pContext->LocalToString(params[2], &key); + + return g_pJsonManager->ObjectSetInt(handle, key, params[3]); +} + +static cell_t json_obj_set_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON object"); + } + + char* key, * value; + pContext->LocalToString(params[2], &key); + pContext->LocalToString(params[3], &value); + + std::variant variant_value; + char error[JSON_ERROR_BUFFER_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return g_pJsonManager->ObjectSetInt64(handle, key, variant_value); +} + +static cell_t json_obj_set_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON object"); + } + + char* key; + pContext->LocalToString(params[2], &key); + + return g_pJsonManager->ObjectSetNull(handle, key); +} + +static cell_t json_obj_set_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON object"); + } + + char* key, * value; + pContext->LocalToString(params[2], &key); + pContext->LocalToString(params[3], &value); + + return g_pJsonManager->ObjectSetString(handle, key, value); +} + +static cell_t json_obj_remove(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot remove value from an immutable JSON object"); + } + + char* key; + pContext->LocalToString(params[2], &key); + + return g_pJsonManager->ObjectRemove(handle, key); +} + +static cell_t json_ptr_get_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + JsonValue* pJSONValue = g_pJsonManager->PtrGet(handle, path, error, sizeof(error)); + + if (!pJSONValue) { + return pContext->ThrowNativeError("%s", error); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "JSON pointer value"); +} + +static cell_t json_ptr_get_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + bool value; + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrGetBool(handle, path, &value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return value; +} + +static cell_t json_ptr_get_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + double value; + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrGetFloat(handle, path, &value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return sp_ftoc(static_cast(value)); +} + +static cell_t json_ptr_get_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + int value; + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrGetInt(handle, path, &value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return value; +} + +static cell_t json_ptr_get_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + std::variant value; + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrGetInt64(handle, path, &value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + char result[JSON_INT64_BUFFER_SIZE]; + if (std::holds_alternative(value)) { + snprintf(result, sizeof(result), "%" PRIu64, std::get(value)); + } else { + snprintf(result, sizeof(result), "%" PRId64, std::get(value)); + } + pContext->StringToLocalUTF8(params[3], params[4], result, nullptr); + + return 1; +} + +static cell_t json_ptr_get_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + const char* str; + size_t len; + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrGetString(handle, path, &str, &len, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + size_t maxlen = static_cast(params[4]); + if (len + 1 > maxlen) { + return pContext->ThrowNativeError("Buffer is too small (need %d, have %d)", len + 1, maxlen); + } + + pContext->StringToLocalUTF8(params[3], maxlen, str, nullptr); + + return 1; +} + +static cell_t json_ptr_get_is_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + bool is_null; + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrGetIsNull(handle, path, &is_null, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return is_null; +} + +static cell_t json_ptr_get_length(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + size_t len; + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrGetLength(handle, path, &len, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return static_cast(len); +} + +static cell_t json_ptr_set_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle1 = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* handle2 = g_pJsonManager->GetFromHandle(pContext, params[3]); + + if (!handle1 || !handle2) return 0; + + if (!handle1->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrSet(handle1, path, handle2, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_set_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrSetBool(handle, path, params[3], error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_set_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrSetFloat(handle, path, sp_ctof(params[3]), error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_set_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrSetInt(handle, path, params[3], error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_set_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON document using pointer"); + } + + char* path, * value; + pContext->LocalToString(params[2], &path); + pContext->LocalToString(params[3], &value); + + std::variant variant_value; + char error[JSON_PACK_ERROR_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + if (!g_pJsonManager->PtrSetInt64(handle, path, variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_set_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON document using pointer"); + } + + char* path, * str; + pContext->LocalToString(params[2], &path); + pContext->LocalToString(params[3], &str); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrSetString(handle, path, str, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_set_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot set value in an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrSetNull(handle, path, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_add_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle1 = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* handle2 = g_pJsonManager->GetFromHandle(pContext, params[3]); + + if (!handle1 || !handle2) return 0; + + if (!handle1->IsMutable()) { + return pContext->ThrowNativeError("Cannot add value to an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrAdd(handle1, path, handle2, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_add_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot add value to an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrAddBool(handle, path, params[3], error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_add_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot add value to an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrAddFloat(handle, path, sp_ctof(params[3]), error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_add_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot add value to an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrAddInt(handle, path, params[3], error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_add_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot add value to an immutable JSON document using pointer"); + } + + char* path, * value; + pContext->LocalToString(params[2], &path); + pContext->LocalToString(params[3], &value); + + std::variant variant_value; + char error[JSON_PACK_ERROR_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(value, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + if (!g_pJsonManager->PtrAddInt64(handle, path, variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_add_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot add value to an immutable JSON document using pointer"); + } + + char* path, * str; + pContext->LocalToString(params[2], &path); + pContext->LocalToString(params[3], &str); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrAddString(handle, path, str, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_add_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot add value to an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrAddNull(handle, path, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_remove_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot remove value from an immutable JSON document using pointer"); + } + + char* path; + pContext->LocalToString(params[2], &path); + + char error[JSON_PACK_ERROR_SIZE]; + if (!g_pJsonManager->PtrRemove(handle, path, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + return true; +} + +static cell_t json_ptr_try_get_val(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + JsonValue* pJSONValue = g_pJsonManager->PtrTryGet(handle, path); + + if (!pJSONValue) { + return 0; + } + + return CreateAndAssignHandle(pContext, pJSONValue, params[3], "JSON pointer value"); +} + +static cell_t json_ptr_try_get_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + bool value; + if (!g_pJsonManager->PtrTryGetBool(handle, path, &value)) { + return 0; + } + + cell_t* addr; + pContext->LocalToPhysAddr(params[3], &addr); + *addr = value; + + return 1; +} + +static cell_t json_ptr_try_get_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + double value; + if (!g_pJsonManager->PtrTryGetFloat(handle, path, &value)) { + return 0; + } + + cell_t* addr; + pContext->LocalToPhysAddr(params[3], &addr); + *addr = sp_ftoc(static_cast(value)); + + return 1; +} + +static cell_t json_ptr_try_get_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + int value; + if (!g_pJsonManager->PtrTryGetInt(handle, path, &value)) { + return 0; + } + + cell_t* addr; + pContext->LocalToPhysAddr(params[3], &addr); + *addr = value; + + return 1; +} + +static cell_t json_ptr_try_get_integer64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + std::variant value; + if (!g_pJsonManager->PtrTryGetInt64(handle, path, &value)) { + return 0; + } + + size_t maxlen = static_cast(params[4]); + char result[JSON_INT64_BUFFER_SIZE]; + if (std::holds_alternative(value)) { + snprintf(result, sizeof(result), "%" PRIu64, std::get(value)); + } else { + snprintf(result, sizeof(result), "%" PRId64, std::get(value)); + } + pContext->StringToLocalUTF8(params[3], maxlen, result, nullptr); + return 1; +} + +static cell_t json_ptr_try_get_str(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + char* path; + pContext->LocalToString(params[2], &path); + + const char* str; + size_t len; + + if (!g_pJsonManager->PtrTryGetString(handle, path, &str, &len)) { + return 0; + } + + size_t maxlen = static_cast(params[4]); + if (len + 1 > maxlen) { + return 0; + } + + pContext->StringToLocalUTF8(params[3], maxlen, str, nullptr); + + return 1; +} + +static cell_t json_obj_foreach(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + const char* key; + JsonValue* pJSONValue; + + if (!g_pJsonManager->ObjectForeachNext(handle, &key, nullptr, &pJSONValue)) { + return false; + } + + pContext->StringToLocalUTF8(params[2], params[3], key, nullptr); + + return CreateAndAssignHandle(pContext, pJSONValue, params[4], "JSON object value"); +} + +static cell_t json_arr_foreach(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + size_t index; + JsonValue* pJSONValue; + + if (!g_pJsonManager->ArrayForeachNext(handle, &index, &pJSONValue)) { + return false; + } + + cell_t* indexPtr; + pContext->LocalToPhysAddr(params[2], &indexPtr); + *indexPtr = static_cast(index); + + return CreateAndAssignHandle(pContext, pJSONValue, params[3], "JSON array value"); +} + +static cell_t json_obj_foreach_key(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + const char* key; + + if (!g_pJsonManager->ObjectForeachKeyNext(handle, &key, nullptr)) { + return false; + } + + pContext->StringToLocalUTF8(params[2], params[3], key, nullptr); + + return true; +} + +static cell_t json_arr_foreach_index(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + size_t index; + + if (!g_pJsonManager->ArrayForeachIndexNext(handle, &index)) { + return false; + } + + cell_t* indexPtr; + pContext->LocalToPhysAddr(params[2], &indexPtr); + *indexPtr = static_cast(index); + + return true; +} + +static cell_t json_arr_sort(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot sort an immutable JSON array"); + } + + JSON_SORT_ORDER sort_mode = static_cast(params[2]); + if (sort_mode < JSON_SORT_ASC || sort_mode > JSON_SORT_RANDOM) { + return pContext->ThrowNativeError("Invalid sort mode: %d (expected 0=ascending, 1=descending, 2=random)", sort_mode); + } + + return g_pJsonManager->ArraySort(handle, sort_mode); +} + +static cell_t json_obj_sort(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Cannot sort an immutable JSON object"); + } + + JSON_SORT_ORDER sort_mode = static_cast(params[2]); + if (sort_mode < JSON_SORT_ASC || sort_mode > JSON_SORT_RANDOM) { + return pContext->ThrowNativeError("Invalid sort mode: %d (expected 0=ascending, 1=descending, 2=random)", sort_mode); + } + + return g_pJsonManager->ObjectSort(handle, sort_mode); +} + +static cell_t json_doc_to_mutable(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (handle->IsMutable()) { + return pContext->ThrowNativeError("Document is already mutable"); + } + + JsonValue* pJSONValue = g_pJsonManager->ToMutable(handle); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to convert to mutable document"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "mutable JSON document"); +} + +static cell_t json_doc_to_immutable(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + + if (!handle) return 0; + + if (!handle->IsMutable()) { + return pContext->ThrowNativeError("Document is already immutable"); + } + + JsonValue* pJSONValue = g_pJsonManager->ToImmutable(handle); + + if (!pJSONValue) { + return pContext->ThrowNativeError("Failed to convert to immutable document"); + } + + return CreateAndReturnHandle(pContext, pJSONValue, "immutable JSON document"); +} + +static cell_t json_apply_json_patch(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* target = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* patch = g_pJsonManager->GetFromHandle(pContext, params[2]); + + if (!target || !patch) return 0; + + bool resultMutable = params[3] != 0; + char error[JSON_ERROR_BUFFER_SIZE] = {0}; + + JsonValue* result = g_pJsonManager->ApplyJsonPatch(target, patch, resultMutable, error, sizeof(error)); + if (!result) { + if (error[0] != '\0') { + return pContext->ThrowNativeError("%s", error); + } + return pContext->ThrowNativeError("Failed to apply JSON Patch"); + } + + return CreateAndReturnHandle(pContext, result, "JSON patch result"); +} + +static cell_t json_json_patch_in_place(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* target = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* patch = g_pJsonManager->GetFromHandle(pContext, params[2]); + + if (!target || !patch) return 0; + + char error[JSON_ERROR_BUFFER_SIZE] = {0}; + if (!g_pJsonManager->JsonPatchInPlace(target, patch, error, sizeof(error))) { + if (error[0] != '\0') { + return pContext->ThrowNativeError("%s", error); + } + return pContext->ThrowNativeError("Failed to apply JSON Patch in place"); + } + + return 1; +} + +static cell_t json_apply_merge_patch(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* target = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* patch = g_pJsonManager->GetFromHandle(pContext, params[2]); + + if (!target || !patch) return 0; + + bool resultMutable = params[3] != 0; + char error[JSON_ERROR_BUFFER_SIZE] = {0}; + + JsonValue* result = g_pJsonManager->ApplyMergePatch(target, patch, resultMutable, error, sizeof(error)); + if (!result) { + if (error[0] != '\0') { + return pContext->ThrowNativeError("%s", error); + } + return pContext->ThrowNativeError("Failed to apply JSON Merge Patch"); + } + + return CreateAndReturnHandle(pContext, result, "JSON merge patch result"); +} + +static cell_t json_merge_patch_in_place(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* target = g_pJsonManager->GetFromHandle(pContext, params[1]); + JsonValue* patch = g_pJsonManager->GetFromHandle(pContext, params[2]); + + if (!target || !patch) return 0; + + char error[JSON_ERROR_BUFFER_SIZE] = {0}; + if (!g_pJsonManager->MergePatchInPlace(target, patch, error, sizeof(error))) { + if (error[0] != '\0') { + return pContext->ThrowNativeError("%s", error); + } + return pContext->ThrowNativeError("Failed to apply JSON Merge Patch in place"); + } + + return 1; +} + +static cell_t json_arr_iter_init(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + JsonArrIter* iter = g_pJsonManager->ArrIterWith(handle); + return CreateAndReturnArrIterHandle(pContext, iter, "array iterator"); +} + +static cell_t json_arr_iter_next(IPluginContext* pContext, const cell_t* params) +{ + JsonArrIter* iter = g_pJsonManager->GetArrIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + JsonValue* val = g_pJsonManager->ArrIterNext(iter); + if (!val) return 0; + + return CreateAndReturnHandle(pContext, val, "array iterator value"); +} + +static cell_t json_arr_iter_has_next(IPluginContext* pContext, const cell_t* params) +{ + JsonArrIter* iter = g_pJsonManager->GetArrIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + return g_pJsonManager->ArrIterHasNext(iter); +} + +static cell_t json_arr_iter_get_index(IPluginContext* pContext, const cell_t* params) +{ + JsonArrIter* iter = g_pJsonManager->GetArrIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + size_t index = g_pJsonManager->ArrIterGetIndex(iter); + if (index == SIZE_MAX) { + return -1; + } + + return static_cast(index); +} + +static cell_t json_arr_iter_remove(IPluginContext* pContext, const cell_t* params) +{ + JsonArrIter* iter = g_pJsonManager->GetArrIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + if (!iter->IsMutable()) { + return pContext->ThrowNativeError("Cannot remove from immutable array iterator"); + } + + void* removed = g_pJsonManager->ArrIterRemove(iter); + return removed != nullptr; +} + +static cell_t json_arr_iter_reset(IPluginContext* pContext, const cell_t* params) +{ + JsonArrIter* iter = g_pJsonManager->GetArrIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + return g_pJsonManager->ArrIterReset(iter); +} + +static cell_t json_obj_iter_init(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + JsonObjIter* iter = g_pJsonManager->ObjIterWith(handle); + return CreateAndReturnObjIterHandle(pContext, iter, "object iterator"); +} + +static cell_t json_obj_iter_next(IPluginContext* pContext, const cell_t* params) +{ + JsonObjIter* iter = g_pJsonManager->GetObjIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + void* key = g_pJsonManager->ObjIterNext(iter); + if (!key) return 0; + + const char* key_str; + size_t key_len; + g_pJsonManager->GetString(reinterpret_cast(key), &key_str, &key_len); + pContext->StringToLocalUTF8(params[2], params[3], key_str, nullptr); + return 1; +} + +static cell_t json_obj_iter_has_next(IPluginContext* pContext, const cell_t* params) +{ + JsonObjIter* iter = g_pJsonManager->GetObjIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + return g_pJsonManager->ObjIterHasNext(iter); +} + +static cell_t json_obj_iter_get_val(IPluginContext* pContext, const cell_t* params) +{ + JsonObjIter* iter = g_pJsonManager->GetObjIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + void* key = iter->m_currentKey; + if (!key) { + return pContext->ThrowNativeError("Iterator not positioned at a valid key (call Next() first)"); + } + + JsonValue* val = g_pJsonManager->ObjIterGetVal(iter, key); + if (!val) { + return pContext->ThrowNativeError("Failed to get value from iterator"); + } + + return CreateAndReturnHandle(pContext, val, "object iterator value"); +} + +static cell_t json_obj_iter_get(IPluginContext* pContext, const cell_t* params) +{ + JsonObjIter* iter = g_pJsonManager->GetObjIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + char* key; + pContext->LocalToString(params[2], &key); + + JsonValue* val = g_pJsonManager->ObjIterGet(iter, key); + if (!val) { + return 0; + } + + return CreateAndReturnHandle(pContext, val, "object iterator value"); +} + +static cell_t json_obj_iter_get_index(IPluginContext* pContext, const cell_t* params) +{ + JsonObjIter* iter = g_pJsonManager->GetObjIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + size_t index = g_pJsonManager->ObjIterGetIndex(iter); + if (index == SIZE_MAX) { + return -1; + } + + return static_cast(index); +} + +static cell_t json_obj_iter_remove(IPluginContext* pContext, const cell_t* params) +{ + JsonObjIter* iter = g_pJsonManager->GetObjIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + if (!iter->IsMutable()) { + return pContext->ThrowNativeError("Cannot remove from immutable object iterator"); + } + + void* removed = g_pJsonManager->ObjIterRemove(iter); + return removed != nullptr; +} + +static cell_t json_obj_iter_reset(IPluginContext* pContext, const cell_t* params) +{ + JsonObjIter* iter = g_pJsonManager->GetObjIterFromHandle(pContext, params[1]); + if (!iter) return 0; + + return g_pJsonManager->ObjIterReset(iter); +} + +static cell_t json_read_number(IPluginContext* pContext, const cell_t* params) +{ + char* dat; + pContext->LocalToString(params[1], &dat); + uint32_t read_flg = static_cast(params[2]); + + char error[JSON_ERROR_BUFFER_SIZE]; + size_t consumed; + JsonValue* pJSONValue = g_pJsonManager->ReadNumber(dat, read_flg, error, sizeof(error), &consumed); + + if (!pJSONValue) { + return pContext->ThrowNativeError("%s", error); + } + + cell_t* consumedPtr = nullptr; + if (params[4] != 0) { + pContext->LocalToPhysAddr(params[4], &consumedPtr); + if (consumedPtr) { + *consumedPtr = static_cast(consumed); + } + } + + return CreateAndReturnHandle(pContext, pJSONValue, "read number"); +} + +static cell_t json_write_number(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + cell_t buffer_size_param = params[3]; + if (buffer_size_param <= 0) { + return pContext->ThrowNativeError("Buffer size must be > 0 (got %d)", buffer_size_param); + } + + size_t buffer_size = static_cast(buffer_size_param); + char* temp_buffer = (char*)malloc(buffer_size); + if (!temp_buffer) { + return pContext->ThrowNativeError("Failed to allocate buffer"); + } + + size_t written = 0; + if (!g_pJsonManager->WriteNumber(handle, temp_buffer, buffer_size, &written)) { + free(temp_buffer); + return pContext->ThrowNativeError("Failed to write number or buffer too small"); + } + + pContext->StringToLocalUTF8(params[2], buffer_size, temp_buffer, nullptr); + free(temp_buffer); + + cell_t* writtenPtr = nullptr; + if (params[4] != 0) { + pContext->LocalToPhysAddr(params[4], &writtenPtr); + if (writtenPtr) { + *writtenPtr = static_cast(written); + } + } + + return 1; +} + +static cell_t json_set_fp_to_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + bool flt = params[2] != 0; + if (!g_pJsonManager->SetFpToFloat(handle, flt)) { + return pContext->ThrowNativeError("Failed to set floating-point format to float (value is not a floating-point number)"); + } + + return 1; +} + +static cell_t json_set_fp_to_fixed(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + int prec = params[2]; + if (prec < 1 || prec > 7) { + return pContext->ThrowNativeError("Precision out of range (1-7)"); + } + + if (!g_pJsonManager->SetFpToFixed(handle, prec)) { + return pContext->ThrowNativeError("Failed to set floating-point format to fixed (value is not a floating-point number or precision out of range 1-7)"); + } + + return 1; +} + +static cell_t json_set_bool(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + bool value = params[2] != 0; + if (!g_pJsonManager->SetBool(handle, value)) { + return pContext->ThrowNativeError("Failed to set value to boolean (value is object or array)"); + } + + return 1; +} + +static cell_t json_set_int(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + int value = params[2]; + if (!g_pJsonManager->SetInt(handle, value)) { + return pContext->ThrowNativeError("Failed to set value to integer (value is object or array)"); + } + + return 1; +} + +static cell_t json_set_int64(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + char* str; + pContext->LocalToString(params[2], &str); + + std::variant variant_value; + char error[JSON_ERROR_BUFFER_SIZE]; + + if (!g_pJsonManager->ParseInt64Variant(str, &variant_value, error, sizeof(error))) { + return pContext->ThrowNativeError("%s", error); + } + + if (!g_pJsonManager->SetInt64(handle, variant_value)) { + return pContext->ThrowNativeError("Failed to set value to int64 (value is object or array)"); + } + + return 1; +} + +static cell_t json_set_float(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + float value = sp_ctof(params[2]); + if (!g_pJsonManager->SetFloat(handle, value)) { + return pContext->ThrowNativeError("Failed to set value to float (value is object or array)"); + } + + return 1; +} + +static cell_t json_set_string(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + char* value; + pContext->LocalToString(params[2], &value); + + if (!g_pJsonManager->SetString(handle, value)) { + return pContext->ThrowNativeError("Failed to set value to string (value is object or array)"); + } + + return 1; +} + +static cell_t json_set_null(IPluginContext* pContext, const cell_t* params) +{ + JsonValue* handle = g_pJsonManager->GetFromHandle(pContext, params[1]); + if (!handle) return 0; + + if (!g_pJsonManager->SetNull(handle)) { + return pContext->ThrowNativeError("Failed to set value to null (value is object or array)"); + } + + return 1; +} + +const sp_nativeinfo_t g_JsonNatives[] = +{ + // JSONObject + {"JSONObject.JSONObject", json_obj_init}, + {"JSONObject.FromStrings", json_obj_init_with_str}, + {"JSONObject.Size.get", json_obj_get_size}, + {"JSONObject.Get", json_obj_get_val}, + {"JSONObject.GetBool", json_obj_get_bool}, + {"JSONObject.GetFloat", json_obj_get_float}, + {"JSONObject.GetInt", json_obj_get_int}, + {"JSONObject.GetInt64", json_obj_get_integer64}, + {"JSONObject.GetString", json_obj_get_str}, + {"JSONObject.IsNull", json_obj_is_null}, + {"JSONObject.GetKey", json_obj_get_key}, + {"JSONObject.GetValueAt", json_obj_get_val_at}, + {"JSONObject.HasKey", json_obj_has_key}, + {"JSONObject.RenameKey", json_obj_rename_key}, + {"JSONObject.Set", json_obj_set_val}, + {"JSONObject.SetBool", json_obj_set_bool}, + {"JSONObject.SetFloat", json_obj_set_float}, + {"JSONObject.SetInt", json_obj_set_int}, + {"JSONObject.SetInt64", json_obj_set_integer64}, + {"JSONObject.SetNull", json_obj_set_null}, + {"JSONObject.SetString", json_obj_set_str}, + {"JSONObject.Remove", json_obj_remove}, + {"JSONObject.Clear", json_obj_clear}, + {"JSONObject.FromString", json_obj_parse_str}, + {"JSONObject.FromFile", json_obj_parse_file}, + {"JSONObject.Sort", json_obj_sort}, + + // JSONArray + {"JSONArray.JSONArray", json_arr_init}, + {"JSONArray.FromStrings", json_arr_init_with_str}, + {"JSONArray.FromInt", json_arr_init_with_int32}, + {"JSONArray.FromInt64", json_arr_init_with_int64}, + {"JSONArray.FromBool", json_arr_init_with_bool}, + {"JSONArray.FromFloat", json_arr_init_with_float}, + {"JSONArray.Length.get", json_arr_get_size}, + {"JSONArray.Get", json_arr_get_val}, + {"JSONArray.First.get", json_arr_get_first}, + {"JSONArray.Last.get", json_arr_get_last}, + {"JSONArray.GetBool", json_arr_get_bool}, + {"JSONArray.GetFloat", json_arr_get_float}, + {"JSONArray.GetInt", json_arr_get_integer}, + {"JSONArray.GetInt64", json_arr_get_integer64}, + {"JSONArray.GetString", json_arr_get_str}, + {"JSONArray.IsNull", json_arr_is_null}, + {"JSONArray.Set", json_arr_replace_val}, + {"JSONArray.SetBool", json_arr_replace_bool}, + {"JSONArray.SetFloat", json_arr_replace_float}, + {"JSONArray.SetInt", json_arr_replace_integer}, + {"JSONArray.SetInt64", json_arr_replace_integer64}, + {"JSONArray.SetNull", json_arr_replace_null}, + {"JSONArray.SetString", json_arr_replace_str}, + {"JSONArray.Push", json_arr_append_val}, + {"JSONArray.PushBool", json_arr_append_bool}, + {"JSONArray.PushFloat", json_arr_append_float}, + {"JSONArray.PushInt", json_arr_append_int}, + {"JSONArray.PushInt64", json_arr_append_integer64}, + {"JSONArray.PushNull", json_arr_append_null}, + {"JSONArray.PushString", json_arr_append_str}, + {"JSONArray.Insert", json_arr_insert}, + {"JSONArray.InsertBool", json_arr_insert_bool}, + {"JSONArray.InsertInt", json_arr_insert_int}, + {"JSONArray.InsertInt64", json_arr_insert_int64}, + {"JSONArray.InsertFloat", json_arr_insert_float}, + {"JSONArray.InsertString", json_arr_insert_str}, + {"JSONArray.InsertNull", json_arr_insert_null}, + {"JSONArray.Prepend", json_arr_prepend}, + {"JSONArray.PrependBool", json_arr_prepend_bool}, + {"JSONArray.PrependInt", json_arr_prepend_int}, + {"JSONArray.PrependInt64", json_arr_prepend_int64}, + {"JSONArray.PrependFloat", json_arr_prepend_float}, + {"JSONArray.PrependString", json_arr_prepend_str}, + {"JSONArray.PrependNull", json_arr_prepend_null}, + {"JSONArray.Remove", json_arr_remove}, + {"JSONArray.RemoveFirst", json_arr_remove_first}, + {"JSONArray.RemoveLast", json_arr_remove_last}, + {"JSONArray.RemoveRange", json_arr_remove_range}, + {"JSONArray.Clear", json_arr_clear}, + {"JSONArray.FromString", json_arr_parse_str}, + {"JSONArray.FromFile", json_arr_parse_file}, + {"JSONArray.IndexOfBool", json_arr_index_of_bool}, + {"JSONArray.IndexOfString", json_arr_index_of_str}, + {"JSONArray.IndexOfInt", json_arr_index_of_int}, + {"JSONArray.IndexOfInt64", json_arr_index_of_integer64}, + {"JSONArray.IndexOfFloat", json_arr_index_of_float}, + {"JSONArray.Sort", json_arr_sort}, + + // JSON UTILITY + {"JSON.ToString", json_doc_write_to_str}, + {"JSON.ToFile", json_doc_write_to_file}, + {"JSON.Parse", json_doc_parse}, + {"JSON.Equals", json_doc_equals}, + {"JSON.EqualsStr", json_equals_str}, + {"JSON.DeepCopy", json_doc_copy_deep}, + {"JSON.GetTypeDesc", json_get_type_desc}, + {"JSON.GetSerializedSize", json_get_serialized_size}, + {"JSON.ReadSize.get", json_get_read_size}, + {"JSON.RefCount.get", json_get_ref_count}, + {"JSON.Type.get", json_get_type}, + {"JSON.SubType.get", json_get_subtype}, + {"JSON.IsArray.get", json_is_array}, + {"JSON.IsObject.get", json_is_object}, + {"JSON.IsInt.get", json_is_int}, + {"JSON.IsUint.get", json_is_uint}, + {"JSON.IsSint.get", json_is_sint}, + {"JSON.IsNum.get", json_is_num}, + {"JSON.IsBool.get", json_is_bool}, + {"JSON.IsTrue.get", json_is_true}, + {"JSON.IsFalse.get", json_is_false}, + {"JSON.IsFloat.get", json_is_float}, + {"JSON.IsStr.get", json_is_str}, + {"JSON.IsNull.get", json_is_null}, + {"JSON.IsCtn.get", json_is_ctn}, + {"JSON.IsMutable.get", json_is_mutable}, + {"JSON.IsImmutable.get", json_is_immutable}, + {"JSON.ForeachObject", json_obj_foreach}, + {"JSON.ForeachArray", json_arr_foreach}, + {"JSON.ForeachKey", json_obj_foreach_key}, + {"JSON.ForeachIndex", json_arr_foreach_index}, + {"JSON.ToMutable", json_doc_to_mutable}, + {"JSON.ToImmutable", json_doc_to_immutable}, + {"JSON.ApplyJsonPatch", json_apply_json_patch}, + {"JSON.JsonPatchInPlace", json_json_patch_in_place}, + {"JSON.ApplyMergePatch", json_apply_merge_patch}, + {"JSON.MergePatchInPlace", json_merge_patch_in_place}, + {"JSON.ReadNumber", json_read_number}, + {"JSON.WriteNumber", json_write_number}, + {"JSON.SetFpToFloat", json_set_fp_to_float}, + {"JSON.SetFpToFixed", json_set_fp_to_fixed}, + + // JSON CREATE/GET/SET + {"JSON.Pack", json_pack}, + {"JSON.CreateBool", json_create_bool}, + {"JSON.CreateFloat", json_create_float}, + {"JSON.CreateInt", json_create_int}, + {"JSON.CreateInt64", json_create_integer64}, + {"JSON.CreateNull", json_create_null}, + {"JSON.CreateString", json_create_str}, + {"JSON.GetBool", json_get_bool}, + {"JSON.GetFloat", json_get_float}, + {"JSON.GetInt", json_get_int}, + {"JSON.GetInt64", json_get_integer64}, + {"JSON.GetString", json_get_str}, + {"JSON.SetBool", json_set_bool}, + {"JSON.SetInt", json_set_int}, + {"JSON.SetInt64", json_set_int64}, + {"JSON.SetFloat", json_set_float}, + {"JSON.SetString", json_set_string}, + {"JSON.SetNull", json_set_null}, + + // JSON POINTER + {"JSON.PtrGet", json_ptr_get_val}, + {"JSON.PtrGetBool", json_ptr_get_bool}, + {"JSON.PtrGetFloat", json_ptr_get_float}, + {"JSON.PtrGetInt", json_ptr_get_int}, + {"JSON.PtrGetInt64", json_ptr_get_integer64}, + {"JSON.PtrGetString", json_ptr_get_str}, + {"JSON.PtrGetIsNull", json_ptr_get_is_null}, + {"JSON.PtrGetLength", json_ptr_get_length}, + {"JSON.PtrSet", json_ptr_set_val}, + {"JSON.PtrSetBool", json_ptr_set_bool}, + {"JSON.PtrSetFloat", json_ptr_set_float}, + {"JSON.PtrSetInt", json_ptr_set_int}, + {"JSON.PtrSetInt64", json_ptr_set_integer64}, + {"JSON.PtrSetString", json_ptr_set_str}, + {"JSON.PtrSetNull", json_ptr_set_null}, + {"JSON.PtrAdd", json_ptr_add_val}, + {"JSON.PtrAddBool", json_ptr_add_bool}, + {"JSON.PtrAddFloat", json_ptr_add_float}, + {"JSON.PtrAddInt", json_ptr_add_int}, + {"JSON.PtrAddInt64", json_ptr_add_integer64}, + {"JSON.PtrAddString", json_ptr_add_str}, + {"JSON.PtrAddNull", json_ptr_add_null}, + {"JSON.PtrRemove", json_ptr_remove_val}, + {"JSON.PtrTryGetVal", json_ptr_try_get_val}, + {"JSON.PtrTryGetBool", json_ptr_try_get_bool}, + {"JSON.PtrTryGetFloat", json_ptr_try_get_float}, + {"JSON.PtrTryGetInt", json_ptr_try_get_int}, + {"JSON.PtrTryGetInt64", json_ptr_try_get_integer64}, + {"JSON.PtrTryGetString", json_ptr_try_get_str}, + + // JSONArrIter + {"JSONArrIter.JSONArrIter", json_arr_iter_init}, + {"JSONArrIter.Next.get", json_arr_iter_next}, + {"JSONArrIter.HasNext.get", json_arr_iter_has_next}, + {"JSONArrIter.Index.get", json_arr_iter_get_index}, + {"JSONArrIter.Remove", json_arr_iter_remove}, + {"JSONArrIter.Reset", json_arr_iter_reset}, + + // JSONObjIter + {"JSONObjIter.JSONObjIter", json_obj_iter_init}, + {"JSONObjIter.Next", json_obj_iter_next}, + {"JSONObjIter.HasNext.get", json_obj_iter_has_next}, + {"JSONObjIter.Value.get", json_obj_iter_get_val}, + {"JSONObjIter.Get", json_obj_iter_get}, + {"JSONObjIter.Index.get", json_obj_iter_get_index}, + {"JSONObjIter.Remove", json_obj_iter_remove}, + {"JSONObjIter.Reset", json_obj_iter_reset}, + + {nullptr, nullptr} +}; \ No newline at end of file diff --git a/extensions/json/extension.cpp b/extensions/json/extension.cpp new file mode 100755 index 0000000000..2c624acf0f --- /dev/null +++ b/extensions/json/extension.cpp @@ -0,0 +1,84 @@ +#include "extension.h" +#include "JsonManager.h" + +JsonExtension g_JsonExt; +SMEXT_LINK(&g_JsonExt); + +HandleType_t g_JsonType; +HandleType_t g_ArrIterType; +HandleType_t g_ObjIterType; +JsonHandler g_JsonHandler; +ArrIterHandler g_ArrIterHandler; +ObjIterHandler g_ObjIterHandler; +IJsonManager* g_pJsonManager; + +bool JsonExtension::SDK_OnLoad(char* error, size_t maxlen, bool late) +{ + sharesys->AddNatives(myself, g_JsonNatives); + sharesys->RegisterLibrary(myself, "json"); + + HandleAccess haJSON; + handlesys->InitAccessDefaults(nullptr, &haJSON); + haJSON.access[HandleAccess_Read] = 0; + haJSON.access[HandleAccess_Delete] = 0; + + HandleError err; + g_JsonType = handlesys->CreateType("JSON", &g_JsonHandler, 0, nullptr, &haJSON, myself->GetIdentity(), &err); + + if (!g_JsonType) { + snprintf(error, maxlen, "Failed to create JSON handle type (err: %d)", err); + return false; + } + + g_ArrIterType = handlesys->CreateType("JSONArrIter", &g_ArrIterHandler, 0, nullptr, &haJSON, myself->GetIdentity(), &err); + if (!g_ArrIterType) { + snprintf(error, maxlen, "Failed to create JSONArrIter handle type (err: %d)", err); + return false; + } + + g_ObjIterType = handlesys->CreateType("JSONObjIter", &g_ObjIterHandler, 0, nullptr, &haJSON, myself->GetIdentity(), &err); + if (!g_ObjIterType) { + snprintf(error, maxlen, "Failed to create JSONObjIter handle type (err: %d)", err); + return false; + } + + if (g_pJsonManager) { + delete g_pJsonManager; + g_pJsonManager = nullptr; + } + + g_pJsonManager = new JsonManager(); + if (!g_pJsonManager) { + snprintf(error, maxlen, "Failed to create JSON manager instance"); + return false; + } + + return sharesys->AddInterface(myself, g_pJsonManager); +} + +void JsonExtension::SDK_OnUnload() +{ + handlesys->RemoveType(g_JsonType, myself->GetIdentity()); + handlesys->RemoveType(g_ArrIterType, myself->GetIdentity()); + handlesys->RemoveType(g_ObjIterType, myself->GetIdentity()); + + if (g_pJsonManager) { + delete g_pJsonManager; + g_pJsonManager = nullptr; + } +} + +void JsonHandler::OnHandleDestroy(HandleType_t type, void* object) +{ + delete (JsonValue*)object; +} + +void ArrIterHandler::OnHandleDestroy(HandleType_t type, void* object) +{ + delete (JsonArrIter*)object; +} + +void ObjIterHandler::OnHandleDestroy(HandleType_t type, void* object) +{ + delete (JsonObjIter*)object; +} \ No newline at end of file diff --git a/extensions/json/extension.h b/extensions/json/extension.h new file mode 100755 index 0000000000..3114905419 --- /dev/null +++ b/extensions/json/extension.h @@ -0,0 +1,42 @@ +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ + +#include "smsdk_ext.h" +#include "IJsonManager.h" + +class JsonExtension : public SDKExtension +{ +public: + virtual bool SDK_OnLoad(char *error, size_t maxlength, bool late); + virtual void SDK_OnUnload(); +}; + +class JsonHandler : public IHandleTypeDispatch +{ +public: + void OnHandleDestroy(HandleType_t type, void *object); +}; + +class ArrIterHandler : public IHandleTypeDispatch +{ +public: + void OnHandleDestroy(HandleType_t type, void *object); +}; + +class ObjIterHandler : public IHandleTypeDispatch +{ +public: + void OnHandleDestroy(HandleType_t type, void *object); +}; + +extern JsonExtension g_JsonExt; +extern HandleType_t g_JsonType; +extern HandleType_t g_ArrIterType; +extern HandleType_t g_ObjIterType; +extern JsonHandler g_JsonHandler; +extern ArrIterHandler g_ArrIterHandler; +extern ObjIterHandler g_ObjIterHandler; +extern const sp_nativeinfo_t g_JsonNatives[]; +extern IJsonManager* g_pJsonManager; + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ \ No newline at end of file diff --git a/extensions/json/smsdk_config.h b/extensions/json/smsdk_config.h new file mode 100755 index 0000000000..7abb641b7d --- /dev/null +++ b/extensions/json/smsdk_config.h @@ -0,0 +1,17 @@ +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ + +#define SMEXT_CONF_NAME "SourceMod JSON Extension" +#define SMEXT_CONF_DESCRIPTION "Provide JSON Native" +#define SMEXT_CONF_VERSION "1.1.5e" +#define SMEXT_CONF_AUTHOR "ProjectSky" +#define SMEXT_CONF_URL "https://github.com/ProjectSky/sm-ext-yyjson" +#define SMEXT_CONF_LOGTAG "json" +#define SMEXT_CONF_LICENSE "GPL" +#define SMEXT_CONF_DATESTRING __DATE__ + +#define SMEXT_LINK(name) SDKExtension *g_pExtensionIface = name; + +#define SMEXT_ENABLE_HANDLESYS + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ \ No newline at end of file diff --git a/extensions/json/version.rc b/extensions/json/version.rc new file mode 100755 index 0000000000..dd8d069a3d --- /dev/null +++ b/extensions/json/version.rc @@ -0,0 +1,104 @@ +// Microsoft Visual C++ generated resource script. +// +//#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +#include + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION SM_VERSION_FILE + PRODUCTVERSION SM_VERSION_FILE + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "Comments", "JSON Extension" + VALUE "FileDescription", "SourceMod JSON Extension" + VALUE "FileVersion", SM_VERSION_FILE + VALUE "InternalName", "SourceMod JSON Extension" + VALUE "LegalCopyright", "Copyright (c) 2004-2009, AlliedModders LLC" + VALUE "OriginalFilename", BINARY_NAME + VALUE "ProductName", "SourceMod JSON Extension" + VALUE "ProductVersion", SM_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/extensions/json/yyjson/LICENSE b/extensions/json/yyjson/LICENSE new file mode 100755 index 0000000000..c4904d1f11 --- /dev/null +++ b/extensions/json/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. \ No newline at end of file diff --git a/extensions/json/yyjson/yyjson.c b/extensions/json/yyjson/yyjson.c new file mode 100755 index 0000000000..9805ecd856 --- /dev/null +++ b/extensions/json/yyjson/yyjson.c @@ -0,0 +1,11195 @@ +/*============================================================================== + 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 YYJSON_IS_REAL_GCC +# 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; +} + +yyjson_api bool yyjson_equals_fp(double a, double b) { + // handle NaN cases first since NaN != NaN + if (isnan(a) || isnan(b)) return false; + + // fast path for exact equality + // this also handles the case where both a and b are +0 or -0 + // IEEE 754 guarantees that +0 == -0 is true + if (a == b) return true; + + // handle infinity cases + if (isinf(a) || isinf(b)) { + return isinf(a) && isinf(b) && (a > 0) == (b > 0); + } + + double diff = fabs(a - b); + if (diff <= 1e-15) return true; + + double larger = fmax(fabs(a), fabs(b)); + return diff <= larger * 1e-6; +} + +yyjson_api bool yyjson_equals_fp_custom(double a, double b, double rel_epsilon, double abs_epsilon) { + // handle NaN cases first since NaN != NaN + if (isnan(a) || isnan(b)) return false; + + // fast path for exact equality + // this also handles the case where both a and b are +0 or -0 + // IEEE 754 guarantees that +0 == -0 is true + if (a == b) return true; + + // handle infinity cases + if (isinf(a) || isinf(b)) { + return isinf(a) && isinf(b) && (a > 0) == (b > 0); + } + + double diff = fabs(a - b); + if (diff <= abs_epsilon) return true; + + double larger = fmax(fabs(a), fabs(b)); + return diff <= larger * rel_epsilon; +} + +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 either is a float, use areClose to compare + if (lt == YYJSON_SUBTYPE_REAL || rt == YYJSON_SUBTYPE_REAL) { + return yyjson_equals_fp( + lt == YYJSON_SUBTYPE_REAL ? luni->f64 : (double)luni->i64, + rt == YYJSON_SUBTYPE_REAL ? runi->f64 : (double)runi->i64 + ); + } + + 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 *write_root_single(yyjson_val *val, + yyjson_write_flag flg, + yyjson_alc alc, + char *buf, 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 { \ + if (buf) hdr = *dat_len >= _len ? (u8 *)buf : (u8 *)NULL; \ + else 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 *write_root_minify(const yyjson_val *root, + const yyjson_write_flag flg, + const yyjson_alc alc, + char *buf, 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; + + if (buf) { + hdr = (u8 *)buf; + alc_len = *dat_len; + alc_len = size_align_down(alc_len, sizeof(yyjson_write_ctx)); + if (alc_len <= sizeof(yyjson_write_ctx)) goto fail_alloc; + } else { + 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(2 * sizeof(*ctx)); + 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 *write_root_pretty(const yyjson_val *root, + const yyjson_write_flag flg, + const yyjson_alc alc, + char *buf, 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; + + if (buf) { + hdr = (u8 *)buf; + alc_len = *dat_len; + alc_len = size_align_down(alc_len, sizeof(yyjson_write_ctx)); + if (alc_len <= sizeof(yyjson_write_ctx)) goto fail_alloc; + } else { + 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); + incr_len(2 * sizeof(*ctx) + (no_indent ? 0 : level * 4)); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + 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 */ + 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 +} + +static char *write_root(const yyjson_val *val, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + char *buf, 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 *)write_root_single(root, flg, alc, buf, dat_len, err); + } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { + return (char *)write_root_pretty(root, flg, alc, buf, dat_len, err); + } else { + return (char *)write_root_minify(root, flg, alc, buf, dat_len, err); + } +} + + + +/*============================================================================== + * 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) { + return write_root(val, flg, alc_ptr, NULL, 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 write_root(root, flg, alc_ptr, NULL, 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 *)write_root(root, flg, &alc, NULL, &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 *)write_root(root, flg, &alc, NULL, &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; +} + +size_t yyjson_val_write_buf(char *buf, size_t buf_len, + const yyjson_val *val, + yyjson_write_flag flg, + yyjson_write_err *err) { + if (unlikely(!buf || !buf_len)) { + if (err) err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + if (err) err->msg = "input buf or buf_len is invalid"; + return 0; + } else { + write_root(val, flg, &YYJSON_NULL_ALC, buf, &buf_len, err); + return buf_len; + } +} + +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); +} + +size_t yyjson_write_buf(char *buf, size_t buf_len, + const yyjson_doc *doc, + yyjson_write_flag flg, + yyjson_write_err *err) { + yyjson_val *root = doc ? doc->root : NULL; + return yyjson_val_write_buf(buf, buf_len, root, flg, 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 *mut_write_root_single(yyjson_mut_val *val, + yyjson_write_flag flg, + yyjson_alc alc, + char *buf, usize *dat_len, + yyjson_write_err *err) { + return write_root_single((yyjson_val *)val, flg, alc, buf, dat_len, err); +} + +/** Write JSON document minify. + The root of this document should be a non-empty container. */ +static_inline u8 *mut_write_root_minify(const yyjson_mut_val *root, + usize estimated_val_num, + yyjson_write_flag flg, + yyjson_alc alc, + char *buf, 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; + + if (buf) { + hdr = (u8 *)buf; + alc_len = *dat_len; + alc_len = size_align_down(alc_len, sizeof(yyjson_mut_write_ctx)); + if (alc_len <= sizeof(yyjson_mut_write_ctx)) goto fail_alloc; + } else { + 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(2 * sizeof(*ctx)); + 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 *mut_write_root_pretty(const yyjson_mut_val *root, + usize estimated_val_num, + yyjson_write_flag flg, + yyjson_alc alc, + char *buf, 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; + + if (buf) { + hdr = (u8 *)buf; + alc_len = *dat_len; + alc_len = size_align_down(alc_len, sizeof(yyjson_mut_write_ctx)); + if (alc_len <= sizeof(yyjson_mut_write_ctx)) goto fail_alloc; + } else { + 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); + incr_len(2 * sizeof(*ctx) + (no_indent ? 0 : level * 4)); + if (unlikely(ctn_len_tmp == 0)) { + /* write empty container */ + 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 */ + 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 *mut_write_root(const yyjson_mut_val *val, + usize estimated_val_num, + yyjson_write_flag flg, + const yyjson_alc *alc_ptr, + char *buf, 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 *)mut_write_root_single(root, flg, alc, buf, dat_len, err); + } else if (flg & (YYJSON_WRITE_PRETTY | YYJSON_WRITE_PRETTY_TWO_SPACES)) { + return (char *)mut_write_root_pretty(root, estimated_val_num, + flg, alc, buf, dat_len, err); + } else { + return (char *)mut_write_root_minify(root, estimated_val_num, + flg, alc, buf, 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 mut_write_root(val, 0, flg, alc_ptr, NULL, 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 mut_write_root(root, estimated_val_num, + flg, alc_ptr, NULL, 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; +} + +size_t yyjson_mut_val_write_buf(char *buf, size_t buf_len, + const yyjson_mut_val *val, + yyjson_write_flag flg, + yyjson_write_err *err) { + if (unlikely(!buf || !buf_len)) { + if (err) err->code = YYJSON_WRITE_ERROR_INVALID_PARAMETER; + if (err) err->msg = "input buf or buf_len is invalid"; + return 0; + } else { + mut_write_root(val, 0, flg, &YYJSON_NULL_ALC, buf, &buf_len, err); + return buf_len; + } +} + +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); +} + +size_t yyjson_mut_write_buf(char *buf, size_t buf_len, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + yyjson_write_err *err) { + yyjson_mut_val *root = doc ? doc->root : NULL; + return yyjson_mut_val_write_buf(buf, buf_len, root, flg, 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/extensions/json/yyjson/yyjson.h b/extensions/json/yyjson/yyjson.h new file mode 100755 index 0000000000..6189389a12 --- /dev/null +++ b/extensions/json/yyjson/yyjson.h @@ -0,0 +1,8346 @@ +/*============================================================================== + 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_MSC_VER >= 1800 || YYJSON_STDC_VER >= 199901L || \ + (yyjson_has_include() && !defined(__STRICT_ANSI__)) +# 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 YYJSON_IS_REAL_GCC +# 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 into a buffer. + + This function does not allocate memory, but the buffer must be larger than the + final JSON size to allow temporary space. See `API.md` for details. + + @param buf The output buffer. + If the buffer is NULL, the function will fail and return 0. + @param buf_len The buffer length. + If the buf_len is too small, the function will fail and return 0. + @param doc doc The JSON document. + If this doc is NULL or has no root, the function will fail and return 0. + @param flg flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param err err A pointer to receive error information. + Pass NULL if you don't need error information. + @return The number of bytes written (excluding the null terminator), + or 0 on failure. + */ +yyjson_api size_t yyjson_write_buf(char *buf, size_t buf_len, + const yyjson_doc *doc, + yyjson_write_flag flg, + 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 into a buffer. + + This function does not allocate memory, but the buffer must be larger than the + final JSON size to allow temporary space. See `API.md` for details. + + @param buf The output buffer. + If the buffer is NULL, the function will fail and return 0. + @param buf_len The buffer length. + If the buf_len is too small, the function will fail and return 0. + @param doc doc The JSON document. + If this doc is NULL or has no root, the function will fail and return 0. + @param flg flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param err err A pointer to receive error information. + Pass NULL if you don't need error information. + @return The number of bytes written (excluding the null terminator), + or 0 on failure. + */ +yyjson_api size_t yyjson_mut_write_buf(char *buf, size_t buf_len, + const yyjson_mut_doc *doc, + yyjson_write_flag flg, + 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 into a buffer. + + This function does not allocate memory, but the buffer must be larger than the + final JSON size to allow temporary space. See `API.md` for details. + + @param buf The output buffer. + If the buffer is NULL, the function will fail and return 0. + @param buf_len The buffer length. + If the buf_len is too small, the function will fail and return 0. + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param err err A pointer to receive error information. + Pass NULL if you don't need error information. + @return The number of bytes written (excluding the null terminator), + or 0 on failure. + */ +yyjson_api size_t yyjson_val_write_buf(char *buf, size_t buf_len, + const yyjson_val *val, + yyjson_write_flag flg, + 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 into a buffer. + + This function does not allocate memory, but the buffer must be larger than the + final JSON size to allow temporary space. See `API.md` for details. + + @param buf The output buffer. + If the buffer is NULL, the function will fail and return 0. + @param buf_len The buffer length. + If the buf_len is too small, the function will fail and return 0. + @param val The JSON root value. + If this parameter is NULL, the function will fail and return NULL. + @param flg flg The JSON write options. + Multiple options can be combined with `|` operator. 0 means no options. + @param err err A pointer to receive error information. + Pass NULL if you don't need error information. + @return The number of bytes written (excluding the null terminator), + or 0 on failure. + */ +yyjson_api size_t yyjson_mut_val_write_buf(char *buf, size_t buf_len, + const yyjson_mut_val *val, + yyjson_write_flag flg, + 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); + +/** Compare two floating-point numbers with default epsilon values. + This function uses both relative and absolute epsilon to handle edge cases. + @param a First floating-point number to compare. + @param b Second floating-point number to compare. + @return true if the numbers are considered equal, false otherwise. + */ +yyjson_api bool yyjson_equals_fp(double a, double b); + +/** Compare two floating-point numbers with custom epsilon values. + This function uses both relative and absolute epsilon to handle edge cases. + @param a First floating-point number to compare. + @param b Second floating-point number to compare. + @param rel_epsilon Relative epsilon for comparison (e.g., 1e-6). + @param abs_epsilon Absolute epsilon for comparison (e.g., 1e-15). + @return true if the numbers are considered equal, false otherwise. + */ +yyjson_api bool yyjson_equals_fp_custom(double a, double b, + double rel_epsilon, + double abs_epsilon); + +/** 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 */ diff --git a/plugins/include/json.inc b/plugins/include/json.inc new file mode 100755 index 0000000000..10a298ae94 --- /dev/null +++ b/plugins/include/json.inc @@ -0,0 +1,2339 @@ +#if defined _json_included + #endinput +#endif +#define _json_included + +// JSON value types +enum JSON_TYPE +{ + JSON_TYPE_NONE = 0, // Invalid type + JSON_TYPE_RAW = 1, // Raw string (stored as is, used for number/literal) + JSON_TYPE_NULL = 2, // null + JSON_TYPE_BOOL = 3, // true/false + JSON_TYPE_NUM = 4, // Number (integer/real) + JSON_TYPE_STR = 5, // String + JSON_TYPE_ARR = 6, // Array + JSON_TYPE_OBJ = 7 // Object +} + +// JSON value subtypes +enum JSON_SUBTYPE +{ + JSON_SUBTYPE_NONE = 0 << 3, // Invalid subtype + JSON_SUBTYPE_FALSE = 0 << 3, // Boolean false + JSON_SUBTYPE_TRUE = 1 << 3, // Boolean true + JSON_SUBTYPE_UINT = 0 << 3, // Unsigned integer + JSON_SUBTYPE_SINT = 1 << 3, // Signed integer + JSON_SUBTYPE_REAL = 2 << 3, // Real number (float/double) + JSON_SUBTYPE_NOESC = 1 << 3 // String without escape character +} + +// JSON reader flags for parsing behavior +enum JSON_READ_FLAG +{ + JSON_READ_NOFLAG = 0 << 0, // Default behavior + JSON_READ_INSITU = 1 << 0, // Read JSON data in-situ (modify input string) + JSON_READ_STOP_WHEN_DONE = 1 << 1, // Stop when done instead of issuing an error if there's additional content + JSON_READ_ALLOW_TRAILING_COMMAS = 1 << 2, // Allow trailing commas at the end of arrays/objects + JSON_READ_ALLOW_COMMENTS = 1 << 3, // Allow C-style comments (/* */) and line comments (//) + JSON_READ_ALLOW_INF_AND_NAN = 1 << 4, // Allow nan/inf number (non-standard JSON) + JSON_READ_NUMBER_AS_RAW = 1 << 5, // Read numbers as raw strings + JSON_READ_ALLOW_INVALID_UNICODE = 1 << 6, // Allow invalid unicode when parsing string + JSON_READ_BIGNUM_AS_RAW = 1 << 7, // Read big numbers as raw strings + JSON_READ_ALLOW_BOM = 1 << 8, // Allow BOM (Byte Order Mark) at the beginning of the JSON string + JSON_READ_ALLOW_EXT_NUMBER = 1 << 9, // Allow extended number (non-standard JSON) + JSON_READ_ALLOW_EXT_ESCAPE = 1 << 10, // Allow extended escape sequences in strings (non-standard) + JSON_READ_ALLOW_EXT_WHITESPACE = 1 << 11, // Allow extended whitespace characters (non-standard) + JSON_READ_ALLOW_SINGLE_QUOTED_STR = 1 << 12, // Allow strings enclosed in single quotes (non-standard) + JSON_READ_ALLOW_UNQUOTED_KEY = 1 << 13, // Allow object keys without quotes (non-standard) + JSON_READ_JSON5 = JSON_READ_ALLOW_TRAILING_COMMAS | + JSON_READ_ALLOW_COMMENTS | + JSON_READ_ALLOW_INF_AND_NAN | + JSON_READ_ALLOW_EXT_NUMBER | + JSON_READ_ALLOW_EXT_ESCAPE | + JSON_READ_ALLOW_EXT_WHITESPACE | + JSON_READ_ALLOW_SINGLE_QUOTED_STR | + JSON_READ_ALLOW_UNQUOTED_KEY // Allow JSON5 format, see: [https://json5.org] +} + +// JSON writer flags for serialization behavior +enum JSON_WRITE_FLAG +{ + JSON_WRITE_NOFLAG = 0 << 0, // Default behavior + JSON_WRITE_PRETTY = 1 << 0, // Pretty print with indent and newline + JSON_WRITE_ESCAPE_UNICODE = 1 << 1, // Escape unicode as \uXXXX + JSON_WRITE_ESCAPE_SLASHES = 1 << 2, // Escape '/' as '\/' + JSON_WRITE_ALLOW_INF_AND_NAN = 1 << 3, // Write inf/nan number (non-standard JSON) + JSON_WRITE_INF_AND_NAN_AS_NULL = 1 << 4, // Write inf/nan as null + JSON_WRITE_ALLOW_INVALID_UNICODE = 1 << 5, // Allow invalid unicode when encoding string + JSON_WRITE_PRETTY_TWO_SPACES = 1 << 6, // Use 2 spaces for indent when pretty print + JSON_WRITE_NEWLINE_AT_END = 1 << 7, // Add newline at the end of output + JSON_WRITE_FP_TO_FLOAT = 1 << 27 // Write floating-point numbers using single-precision (float) +} + +/** + * Write floating-point number using fixed-point notation + * This is similar to ECMAScript Number.prototype.toFixed(prec) but with trailing zeros removed + * + * @param n Precision digits (1-7) + * @return JSON write flag with precision setting + * @note This will produce shorter output but may lose some precision + */ +stock JSON_WRITE_FLAG JSON_WRITE_FP_TO_FIXED(int n) +{ + n = (n < 1) ? 1 : (n > 7 ? 7 : n); + return view_as(n << 28); +} + +// Sort order for arrays and objects +enum JSON_SORT_ORDER +{ + JSON_SORT_ASC = 0, // Ascending order (default) + JSON_SORT_DESC = 1, // Descending order + JSON_SORT_RANDOM = 2 // Random order +} + +methodmap JSON < Handle +{ + /** + * Creates a JSON value using a format string and arguments + * Format specifiers: + * - s: string + * - i: integer + * - f: float + * - b: boolean + * - n: null + * - {: start object + * - }: end object + * - [: start array + * - ]: end array + * + * @note Needs to be freed using delete or CloseHandle() + * @note There is a limit of 32 parameters (defined in SourcePawn) + * including the format string, so the number of arguments must be less than or equal to 31 + * + * @param format Format string + * @param ... Arguments based on format string + * + * @return JSON handle + * @error If format string is invalid or arguments don't match + */ + public static native any Pack(const char[] format, any ...); + + /** + * Iterates over the object's key-value pairs + * + * @deprecated Use JSONObjIter instead + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param buffer Buffer to copy key name to + * @param maxlength Maximum length of the string buffer + * @param value JSON handle to store the current value + * + * @return True if there are more elements, false when iteration is complete + * @error Invalid handle or handle is not an object + */ + #pragma deprecated Use JSONObjIter instead. + public native bool ForeachObject(char[] buffer, int maxlength, JSON &value); + + /** + * Iterates over the array's values + * + * @deprecated Use JSONArrIter instead + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param index Variable to store current array index (starting from 0) + * @param value JSON handle to store the current value + * + * @return True if there are more elements, false when iteration is complete + * @error Invalid handle or handle is not an array + */ + #pragma deprecated Use JSONArrIter instead. + public native bool ForeachArray(int &index, JSON &value); + + /** + * Same as ForeachObject, but only iterates over the object's keys + * + * @deprecated Use JSONObjIter instead + * + * @note Use this when you only need keys (faster than ForeachObject since it doesn't create value handles) + * + * @param buffer Buffer to copy key name to + * @param maxlength Maximum length of the string buffer + * + * @return True if there are more elements, false when iteration is complete + * @error Invalid handle or handle is not an object + */ + #pragma deprecated Use JSONObjIter instead. + public native bool ForeachKey(char[] buffer, int maxlength); + + /** + * Same as ForeachArray, but only iterates over the array's indexes + * + * @deprecated Use JSONArrIter instead + * + * @note Use this when you only need indexes (faster than ForeachArray since it doesn't create value handles) + * + * @param index Variable to store current array index (starting from 0) + * + * @return True if there are more elements, false when iteration is complete + * @error Invalid handle or handle is not an array + */ + #pragma deprecated Use JSONArrIter instead. + public native bool ForeachIndex(int &index); + + /** + * Converts an immutable JSON document to a mutable one + * + * @note Needs to be freed using delete or CloseHandle() + * + * @return Handle to the new mutable JSON document, INVALID_HANDLE on failure + * @error If the document is already mutable + */ + public native any ToMutable(); + + /** + * Converts a mutable JSON document to an immutable one + * + * @note Needs to be freed using delete or CloseHandle() + * + * @return Handle to the new immutable JSON document, INVALID_HANDLE on failure + * @error If the document is already immutable + */ + public native any ToImmutable(); + + /** + * Apply a JSON Patch (RFC 6902) to this value and return a new JSON handle + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param patch JSON Patch document + * @param resultMutable True to return a mutable result, false for immutable + * + * @return New JSON handle on success + * @error Throws if the patch cannot be applied + */ + public native any ApplyJsonPatch(const JSON patch, bool resultMutable = false); + + /** + * Apply a JSON Patch (RFC 6902) to this value in place + * + * @param patch JSON Patch document + * + * @return True on success, false otherwise + * @error Throws if this value is immutable or patch failed + */ + public native bool JsonPatchInPlace(const JSON patch); + + /** + * Apply a JSON Merge Patch (RFC 7396) to this value and return a new JSON handle + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param patch JSON Merge Patch document + * @param resultMutable True to return a mutable result, false for immutable + * + * @return New JSON handle on success + * @error Throws if the merge patch cannot be applied + */ + public native any ApplyMergePatch(const JSON patch, bool resultMutable = false); + + /** + * Apply a JSON Merge Patch (RFC 7396) to this value in place + * + * @param patch JSON Merge Patch document + * + * @return True on success, false otherwise + * @error Throws if this value is immutable or merge patch failed + */ + public native bool MergePatchInPlace(const JSON patch); + + /** + * Write a document to JSON file with options + * + * @note On 32-bit operating system, files larger than 2GB may fail to write + * + * @param file The JSON file's path. 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 flag The JSON write options + * + * @return True on success, false on failure + */ + public native bool ToFile(const char[] file, JSON_WRITE_FLAG flag = JSON_WRITE_NOFLAG); + + /** + * Write a value to JSON string + * + * @param buffer String buffer to write to + * @param maxlength Maximum length of the string buffer + * @param flag The JSON write options + * + * @return Number of characters written to the buffer (including null terminator) or 0 on failure + */ + public native int ToString(char[] buffer, int maxlength, JSON_WRITE_FLAG flag = JSON_WRITE_NOFLAG); + + /** + * Write a JSON number value to string buffer + * + * @note The buffer must be large enough to hold the number string (at least 40 bytes for floating-point, 21 bytes for integer) + * + * @param buffer String buffer to write to + * @param maxlength Maximum length of the string buffer + * @param written Variable to store number of characters written (excluding null terminator) (optional) + * + * @return True on success, false on failure + * @error Invalid handle, handle is not a number, or buffer too small + */ + public native bool WriteNumber(char[] buffer, int maxlength, int &written = 0); + + /** + * Set floating-point number's output format to single-precision + * + * @note Only works on floating-point numbers (not integers) + * @note This affects how the number is serialized in all write operations + * + * @param flt True to use single-precision (float), false to use double-precision (double) + * + * @return True on success, false if handle is not a floating-point number + * @error Invalid handle or handle is not a floating-point number + */ + public native bool SetFpToFloat(bool flt); + + /** + * Set floating-point number's output format to fixed-point notation + * + * @note Only works on floating-point numbers (not integers) + * @note This is similar to ECMAScript Number.prototype.toFixed(prec) but with trailing zeros removed + * @note This will produce shorter output but may lose some precision + * @note This affects how the number is serialized in all write operations + * + * @param prec Precision (1-7) + * + * @return True on success, false if handle is not a floating-point number or prec is out of range + * @error Invalid handle, handle is not a floating-point number, or precision out of range (1-7) + */ + public native bool SetFpToFixed(int prec); + + /** + * Directly modify a JSON value to boolean type + * + * @note This modifies the value in-place without creating a new value + * @note For immutable documents, this breaks immutability. Use with caution + * + * @param value Boolean value + * + * @return True on success, false if value is object or array + * @error Invalid handle or handle is object or array + */ + public native bool SetBool(bool value); + + /** + * Directly modify a JSON value to integer type + * + * @note This modifies the value in-place without creating a new value + * @note For immutable documents, this breaks immutability. Use with caution + * + * @param value Integer value + * + * @return True on success, false if value is object or array + * @error Invalid handle or handle is object or array + */ + public native bool SetInt(int value); + + /** + * Directly modify a JSON value to 64-bit integer type + * + * @note This modifies the value in-place without creating a new value + * @note For immutable documents, this breaks immutability. Use with caution + * @note This function auto-detects whether the value is signed or unsigned + * + * @param value 64-bit integer value (as string, can be signed or unsigned) + * + * @return True on success, false if value is object or array + * @error Invalid handle, handle is object or array, or invalid int64 string format + */ + public native bool SetInt64(const char[] value); + + /** + * Directly modify a JSON value to floating-point type + * + * @note This modifies the value in-place without creating a new value + * @note For immutable documents, this breaks immutability. Use with caution + * + * @param value Float value + * + * @return True on success, false if value is object or array + * @error Invalid handle or handle is object or array + */ + public native bool SetFloat(float value); + + /** + * Directly modify a JSON value to string type + * + * @note This modifies the value in-place without creating a new value + * @note For immutable documents, this breaks immutability and does NOT copy the string. Use with caution + * + * @param value String value + * + * @return True on success, false if value is object or array or value is null + * @error Invalid handle or handle is object or array + */ + public native bool SetString(const char[] value); + + /** + * Directly modify a JSON value to null type + * + * @note This modifies the value in-place without creating a new value + * @note For immutable documents, this breaks immutability. Use with caution + * + * @return True on success, false if value is object or array + * @error Invalid handle or handle is object or array + */ + public native bool SetNull(); + + /** + * Parses JSON string or a file that contains JSON + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param string String or file to parse + * @param is_file True to treat string param as file, false otherwise + * @param is_mutable_doc True to create a mutable document, false to create an immutable one + * @param flag The JSON read options + * + * @return JSON handle, false on failure + */ + public static native any Parse(const char[] string, bool is_file = false, bool is_mutable_doc = false, JSON_READ_FLAG flag = JSON_READ_NOFLAG); + + /** + * Read a JSON number from string + * + * @note Needs to be freed using delete or CloseHandle() + * @note The input string must be null-terminated UTF-8 without BOM + * @note The returned value is a mutable number value + * + * @param dat The JSON data (UTF-8 without BOM), null-terminator is required + * @param flag Read flags (JSON_READ_FLAG values, default: JSON_READ_NOFLAG) + * @param consumed Variable to store number of characters consumed (optional) + * + * @return New JSON number value or null on error + * @error If input data is invalid or number parsing fails + */ + public static native JSON ReadNumber(const char[] dat, JSON_READ_FLAG flag = JSON_READ_NOFLAG, int &consumed = 0); + + /** + * Creates a deep copy of a JSON value and returns it as a new JSON handle + * + * @note Needs to be freed using delete or CloseHandle() + * @note This function is recursive and may cause a stack overflow if the object level is too deep + * @note The mutability of the returned copy depends on the targetDoc parameter + * + * @param targetDoc The target document that determines whether the copy will be mutable or immutable + * @param sourceValue The source JSON value to be copied + * + * @return New JSON handle on success, false on failure + */ + public static native any DeepCopy(const JSON targetDoc, const JSON sourceValue); + + /** + * Returns the JSON value's type description + * + * @param buffer String buffer to write to + * @param maxlength Maximum length of the string buffer + * + * @return The return value should be one of these strings: "raw", "null", "string", + * "array", "object", "true", "false", "uint", "sint", "real", "unknown" + */ + public native void GetTypeDesc(char[] buffer, int maxlength); + + /** + * Returns whether two JSON values are equal (deep compare) + * + * @note This function is recursive and may cause a stack overflow if the object level is too deep + * @note the result may be inaccurate if object has duplicate keys + * + * @param value1 First JSON value to compare + * @param value2 Second JSON value to compare + * + * @return True if they are the same, false otherwise + */ + public static native bool Equals(const JSON value1, const JSON value2); + + /** + * Check if JSON value equals a string + * + * @param value JSON handle + * @param str String to compare with + * + * @return True if value is a string and equals the given string, false otherwise + */ + public static native bool EqualsStr(const JSON value, const char[] str); + + /** + * Creates and returns a boolean value + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param value The boolean value to be set + * + * @return JSON handle, null on error + */ + public static native JSON CreateBool(bool value); + + /** + * Creates and returns a float value + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param value The float value to be set + * + * @return JSON handle, null on error + */ + public static native JSON CreateFloat(float value); + + /** + * Creates and returns an int value + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param value The int value to be set + * + * @return JSON handle, null on error + */ + public static native JSON CreateInt(int value); + + /** + * Creates and returns an integer64 value + * + * @note Needs to be freed using delete or CloseHandle() + * @note This function auto-detects whether the value is signed or unsigned + * + * @param value The integer64 value to be set (can be signed or unsigned) + * + * @return JSON handle, null on error + */ + public static native JSON CreateInt64(const char[] value); + + /** + * Creates and returns a string value + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param value The string value to be set + * + * @return JSON handle, null on error + */ + public static native JSON CreateString(const char[] value); + + /** + * Creates and returns a null value + * + * @note Needs to be freed using delete or CloseHandle() + * + * @return JSON handle, null on error + */ + public static native JSON CreateNull(); + + /** + * Get boolean value + * + * @return Boolean value + */ + public native bool GetBool(); + + /** + * Get float value + * + * @return float value + */ + public native float GetFloat(); + + /** + * Get int value + * + * @return int value + */ + public native int GetInt(); + + /** + * Get integer64 value (auto-detects signed/unsigned) + * + * @param buffer Buffer to copy to + * @param maxlength Maximum size of the buffer + * + * @return True on success, false on failure + */ + public native bool GetInt64(char[] buffer, int maxlength); + + /** + * Get string value + * + * @param buffer Buffer to copy to + * @param maxlength Maximum size of the buffer + * + * @return True on success, false on failure + */ + public native bool GetString(char[] buffer, int maxlength); + + /** + * Get JSON Handle serialized size in bytes (including null-terminator) + * + * @param flag The JSON write options + * + * @return Size in bytes (including null terminator) + * + * @note The returned size depends on the flag parameter. + * You MUST use the same flags when calling both GetSerializedSize() + * and ToString(). Using different flags will return different sizes + * and may cause buffer overflow + */ + public native int GetSerializedSize(JSON_WRITE_FLAG flag = JSON_WRITE_NOFLAG); + + /** + * Get value by a JSON Pointer + * + * @note Needs to be freed using delete or CloseHandle() + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * + * @return The value referenced by the JSON pointer + */ + public native any PtrGet(const char[] path); + + /** + * Get boolean value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * + * @return boolean value referenced by the JSON pointer + */ + public native bool PtrGetBool(const char[] path); + + /** + * Get float value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * + * @return float value referenced by the JSON pointer + */ + public native float PtrGetFloat(const char[] path); + + /** + * Get integer value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * + * @return integer value referenced by the JSON pointer + */ + public native int PtrGetInt(const char[] path); + + /** + * Get integer64 value by a JSON Pointer (auto-detects signed/unsigned) + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param buffer Buffer to copy to + * @param maxlength Maximum size of the buffer + * + * @return True on success, false on failure + */ + public native bool PtrGetInt64(const char[] path, char[] buffer, int maxlength); + + /** + * Get string value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param buffer Buffer to copy to + * @param maxlength Maximum size of the buffer + * + * @return True on success, false on failure + */ + public native bool PtrGetString(const char[] path, char[] buffer, int maxlength); + + /** + * Get value is null by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * + * @return True if the value is null, false otherwise + */ + public native bool PtrGetIsNull(const char[] path); + + /** + * Get JSON content length (string length, array size, object size) + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * @note For strings: returns string length including null-terminator + * @note For arrays/objects: returns number of elements + * @note Returns 0 if value is null or type is not string/array/object + * + * @param path The JSON pointer string + * + * @return JSON content length + */ + public native int PtrGetLength(const char[] path); + + /** + * Set value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * @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 + * + * @param path The JSON pointer string + * @param value The value to be set + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrSet(const char[] path, JSON value); + + /** + * Set boolean value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * @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 + * + * @param path The JSON pointer string + * @param value The boolean value to be set + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrSetBool(const char[] path, bool value); + + /** + * Set float value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * @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 + * + * @param path The JSON pointer string + * @param value The float value to be set + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrSetFloat(const char[] path, float value); + + /** + * Set integer value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * @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 + * + * @param path The JSON pointer string + * @param value The integer value to be set + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrSetInt(const char[] path, int value); + + /** + * Set integer64 value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * @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 + * @note This function auto-detects whether the value is signed or unsigned + * + * @param path The JSON pointer string + * @param value The integer64 value to be set (can be signed or unsigned) + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrSetInt64(const char[] path, const char[] value); + + /** + * Set string value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * @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 + * + * @param path The JSON pointer string + * @param value The string value to be set + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrSetString(const char[] path, const char[] value); + + /** + * Set null value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * @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 + * + * @param path The JSON pointer string + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrSetNull(const char[] path); + + /** + * Add (insert) value by a JSON pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value The value to be added + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrAdd(const char[] path, JSON value); + + /** + * Add (insert) boolean value by a JSON pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value The boolean value to be added + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrAddBool(const char[] path, bool value); + + /** + * Add (insert) float value by a JSON pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value The float value to be added + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrAddFloat(const char[] path, float value); + + /** + * Add (insert) integer value by a JSON pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value The int value to be added + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrAddInt(const char[] path, int value); + + /** + * Add (insert) integer64 value by a JSON pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value The integer64 value to be added + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrAddInt64(const char[] path, const char[] value); + + /** + * Add (insert) string value by a JSON pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value The str value to be added + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrAddString(const char[] path, const char[] value); + + /** + * Add (insert) null value by a JSON pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * + * @return true if JSON pointer is valid and new value is set, false otherwise + */ + public native bool PtrAddNull(const char[] path); + + /** + * Remove value by a JSON pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * + * @return true if removed value, false otherwise + */ + public native bool PtrRemove(const char[] path); + + /** + * Try to get value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value Handle to store value + * + * @return true on success, false otherwise + */ + public native bool PtrTryGetVal(const char[] path, JSON &value); + + /** + * Try to get boolean value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value Store the boolean value + * + * @return true on success, false otherwise + */ + public native bool PtrTryGetBool(const char[] path, bool &value); + + /** + * Try to get float value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value Store the float value + * + * @return true on success, false otherwise + */ + public native bool PtrTryGetFloat(const char[] path, float &value); + + /** + * Try to get integer value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param value Store the integer value + * + * @return true on success, false otherwise + */ + public native bool PtrTryGetInt(const char[] path, int &value); + + /** + * Try to get integer64 value by a JSON Pointer (automatically detects signed/unsigned) + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param buffer Buffer to store the integer64 value + * @param maxlength Maximum length of the buffer + * + * @return true on success, false otherwise + */ + public native bool PtrTryGetInt64(const char[] path, char[] buffer, int maxlength); + + /** + * Try to get string value by a JSON Pointer + * + * @note JSON Pointer paths are always resolved from the document root, not from the current value + * + * @param path The JSON pointer string + * @param buffer Buffer to store the string value + * @param maxlength Maximum length of the buffer + * + * @return true on success, false otherwise + */ + public native bool PtrTryGetString(const char[] path, char[] buffer, int maxlength); + + /** + * Retrieves json type + */ + property JSON_TYPE Type { + public native get(); + } + + /** + * Retrieves json subtype + */ + property JSON_SUBTYPE SubType { + public native get(); + } + + /** + * Retrieves json is array + */ + property bool IsArray { + public native get(); + } + + /** + * Retrieves json is object + */ + property bool IsObject { + public native get(); + } + + /** + * Retrieves json is integer (uint64_t/int64_t) + */ + property bool IsInt { + public native get(); + } + + /** + * Retrieves json is unsigned integer (uint64_t) + */ + property bool IsUint { + public native get(); + } + + /** + * Retrieves json is signed integer (int64_t) + */ + property bool IsSint { + public native get(); + } + + /** + * Retrieves json is number (uint64_t/int64_t/double) + */ + property bool IsNum { + public native get(); + } + + /** + * Retrieves json is boolean + */ + property bool IsBool { + public native get(); + } + + /** + * Retrieves json is true + */ + property bool IsTrue { + public native get(); + } + + /** + * Retrieves json is false + */ + property bool IsFalse { + public native get(); + } + + /** + * Retrieves json is float + */ + property bool IsFloat { + public native get(); + } + + /** + * Retrieves json is string + */ + property bool IsStr { + public native get(); + } + + /** + * Retrieves json is null + */ + property bool IsNull { + public native get(); + } + + /** + * Retrieves json is container (array/object) + */ + property bool IsCtn { + public native get(); + } + + /** + * Retrieves json is mutable doc + */ + property bool IsMutable { + public native get(); + } + + /** + * Retrieves json is immutable doc + */ + property bool IsImmutable { + public native get(); + } + + /** + * Retrieves the size of the JSON data as it was originally read from parsing + * + * @return Size in bytes (including null terminator) or 0 if not from parsing + * + * @note This value only applies to documents created from parsing (Parse, FromString, FromFile) + * @note Manually created documents (new JSONObject(), JSON.CreateBool(), etc.) will return 0 + * @note This value does not auto-update if the document is modified + * @note For modified documents, use GetSerializedSize() to obtain the current size + */ + property int ReadSize { + public native get(); + } + + /** + * Get the reference count of the document (debugging purpose) + * + * @return Reference count (number of JSON objects sharing this document) + * + * @note Returns 0 if the handle is invalid + */ + property int RefCount { + public native get(); + } +}; + +methodmap JSONObject < JSON +{ + /** + * Creates a JSON object A JSON object maps strings (called "keys") to values Keys in a + * JSON object are unique That is, there is at most one entry in the map for a given key + * + * @note Needs to be freed using delete or CloseHandle() + */ + public native JSONObject(); + + /** + * Creates a new JSON object from an array of strings representing key-value pairs. + * The array must contain an even number of strings, where even indices are keys + * and odd indices are values. Keys cannot be empty strings + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param pairs Array of strings containing alternating keys and values + * @param size Total size of the array (must be even) + * + * @return New JSON object handle + * @error If array size is invalid, any key is empty, or if creation fails + * + */ + public static native JSONObject FromStrings(const char[][] pairs, int size); + + /** + * Loads a JSON object from a file + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param file File to read from + * @param flag The JSON read options + * + * @return Object handle, or null on failure + */ + public static native JSONObject FromFile(const char[] file, JSON_READ_FLAG flag = JSON_READ_NOFLAG); + + /** + * Loads a JSON object from a string + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param buffer String buffer to load into the JSON object + * @param flag The JSON read options + * + * @return Object handle, or null on failure + */ + public static native JSONObject FromString(const char[] buffer, JSON_READ_FLAG flag = JSON_READ_NOFLAG); + + /** + * Gets a value from the object + * + * @note Needs to be freed using delete or CloseHandle() + * @note This function takes a linear search time + * + * @param key Key name + * + * @return Returns the value to which the specified key is mapped, or null if this object contains no mapping for the key + */ + public native any Get(const char[] key); + + /** + * Sets a value in the object + * + * @param key Key name + * @param value JSON handle to set + * + * @return True if succeed, false otherwise + */ + public native bool Set(const char[] key, const JSON value); + + /** + * Gets a boolean value from the object + * + * @param key Key name + * + * @return Boolean value + */ + public native bool GetBool(const char[] key); + + /** + * Gets a float value from the object + * + * @param key Key name + * + * @return Float value + */ + public native float GetFloat(const char[] key); + + /** + * Gets a integer value from the object + * + * @param key Key name + * + * @return Integer value + */ + public native int GetInt(const char[] key); + + /** + * Retrieves a 64-bit integer value from the object (automatically detects signed/unsigned) + * + * @param key Key string + * @param buffer String buffer to store value + * @param maxlength Maximum length of the string buffer + * + * @return True on success, false if the key was not found + */ + public native bool GetInt64(const char[] key, char[] buffer, int maxlength); + + /** + * Gets name of the object's key + * + * @param index Position from which get key name + * @param buffer Buffer to copy string to + * @param maxlength Maximum size of the buffer + * + * @return True on success, false on failure + */ + public native bool GetKey(int index, char[] buffer, int maxlength); + + /** + * Gets a value at the specified position from the object + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param index Position from which get key name + * + * @return Returns the value to which index + */ + public native any GetValueAt(int index); + + /** + * Returns whether or not a key exists in the object + * + * @param key Key string + * @param ptr_use Use JSON Pointer + * + * @return True if the key exists, false otherwise + */ + public native bool HasKey(const char[] key, bool ptr_use = false); + + /** + * Replaces all matching keys with the new key + * The old_key and new_key should be a null-terminated UTF-8 string + * The new_key is copied and held by doc + * + * @note This function takes a linear search time + * + * @param old_key The key to rename + * @param new_key The new key name + * @param allow_duplicate Whether to allow renaming even if new key exists + * + * @return True if at least one key was renamed, false otherwise + */ + public native bool RenameKey(const char[] old_key, const char[] new_key, bool allow_duplicate = false); + + /** + * Gets string data from the object + * + * @param key Key name + * @param buffer Buffer to copy string to + * @param maxlength Maximum size of the buffer + * + * @return True on success, false on failure + */ + public native bool GetString(const char[] key, char[] buffer, int maxlength); + + /** + * Returns whether or not a value in the object is null + * + * @param key Key string + * + * @return True if the value is null, false otherwise + */ + public native bool IsNull(const char[] key); + + /** + * Sets a boolean value in the object + * + * @param key Key name + * @param value Boolean value to set + * + * @return True if succeed, false otherwise + */ + public native bool SetBool(const char[] key, bool value); + + /** + * Sets a float value in the object + * + * @param key Key name + * @param value float to set + * + * @return True if succeed, false otherwise + */ + public native bool SetFloat(const char[] key, float value); + + /** + * Sets a integer value in the object + * + * @param key Key name + * @param value integer to set + * + * @return True if succeed, false otherwise + */ + public native bool SetInt(const char[] key, int value); + + /** + * Sets a 64-bit integer value in the object, either inserting a new entry or replacing an old one + * + * @note This function automatically detects whether the value is signed or unsigned + * + * @param key Key string + * @param value Value to store at this key (can be signed or unsigned) + * + * @return True on success, false on failure + */ + public native bool SetInt64(const char[] key, const char[] value); + + /** + * Sets a null in the object + * + * @param key Key name + * + * @return True if succeed, false otherwise + */ + public native bool SetNull(const char[] key); + + /** + * Sets string data in the object + * + * @param key Key name + * @param value String to copy + * + * @return True if succeed, false otherwise + */ + public native bool SetString(const char[] key, const char[] value); + + /** + * Removes a key and its value in the object + * + * @note This function takes a linear search time + * + * @param key Key name + * + * @return True if succeed, false otherwise + */ + public native bool Remove(const char[] key); + + /** + * Removes all keys and their values in the object + * + * @return True if succeed, false otherwise + */ + public native bool Clear(); + + /** + * Sorts the object's keys + * + * @note This function performs a lexicographical sort on the object's keys + * - The values maintain their association with their respective keys + * + * @param order Sort order, see JSON_SORT_ORDER enums + * + * @return True on success, false on failure + * @error Invalid handle or handle is not an object + */ + public native bool Sort(JSON_SORT_ORDER order = JSON_SORT_ASC); + + /** + * Retrieves the size of the object + */ + property int Size { + public native get(); + } +}; + +methodmap JSONArray < JSON +{ + /** + * Creates a JSON array + * + * @note Needs to be freed using delete or CloseHandle() + */ + public native JSONArray(); + + /** + * Creates a new JSON array from an array of strings + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param strings Array of strings to create array from + * @param size Size of the array + * @return New JSON array handle, or null if creation failed + */ + public static native JSONArray FromStrings(const char[][] strings, int size); + + /** + * Creates a new JSON array from an array of integer values + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param values Array of int values to create array from + * @param size Size of the array + * @return New JSON array handle, or null if creation failed + */ + public static native JSONArray FromInt(const int[] values, int size); + + /** + * Creates a new JSON array from an array of 64-bit integer strings (auto-detects signed/unsigned) + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param values Array of int64 string values (can be signed or unsigned) + * @param size Size of the array + * @return New JSON array handle, or null if creation failed + * @error If any value cannot be parsed as int64 + */ + public static native JSONArray FromInt64(const char[][] values, int size); + + /** + * Creates a new JSON array from an array of boolean values + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param values Array of bool values to create array from + * @param size Size of the array + * @return New JSON array handle, or null if creation failed + */ + public static native JSONArray FromBool(const bool[] values, int size); + + /** + * Creates a new JSON array from an array of float values + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param values Array of float values to create array from + * @param size Size of the array + * @return New JSON array handle, or null if creation failed + */ + public static native JSONArray FromFloat(const float[] values, int size); + + /** + * Loads a JSON array from a file + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param file File to read from + * @param flag Read flag + * @return Array handle, or null on failure + */ + public static native JSONArray FromFile(const char[] file, JSON_READ_FLAG flag = JSON_READ_NOFLAG); + + /** + * Loads a JSON array from a string + * + * @note Needs to be freed using delete or CloseHandle() + * + * @param buffer String buffer to load into the JSON array + * @param flag Read flag + * @return Array handle, or null on failure + */ + public static native JSONArray FromString(const char[] buffer, JSON_READ_FLAG flag = JSON_READ_NOFLAG); + + /** + * Gets a value from the array + * + * @note Needs to be freed using delete or CloseHandle() + * @note This function takes a linear search time + * + * @param index Position in the array (starting from 0) + * + * @return Value handle on success, null if array is empty or index out of bounds + */ + public native any Get(int index); + + /** + * Gets a boolean value from the array + * + * @param index Position in the array (starting from 0) + * + * @return Boolean value + */ + public native bool GetBool(int index); + + /** + * Gets a float value from the array + * + * @param index Position in the array (starting from 0) + * + * @return The number as float + */ + public native float GetFloat(int index); + + /** + * Gets a integer value from the array + * + * @param index Position in the array (starting from 0) + * + * @return integer value + */ + public native int GetInt(int index); + + /** + * Gets a 64-bit integer from the array (automatically detects signed/unsigned) + * + * @param index Position in the array (starting from 0) + * @param buffer Buffer to copy to + * @param maxlength Maximum size of the buffer + * + * @return True on success, false on failure + */ + public native bool GetInt64(int index, char[] buffer, int maxlength); + + /** + * Gets string data from the array + * + * @param index Position in the array (starting from 0) + * @param buffer Buffer to copy string to + * @param maxlength Maximum size of the buffer + * + * @return True on success, false on failure + */ + public native bool GetString(int index, char[] buffer, int maxlength); + + /** + * Returns whether or not a value in the array is null + * + * @param index Position in the array (starting from 0) + * + * @return True if the value is null, false otherwise + */ + public native bool IsNull(int index); + + /** + * Replaces a value at index + * + * @note This function takes a linear search time + * + * @param index The index to which to replace the value + * @param value The new value to replace + * + * @return True if succeed, false otherwise + */ + public native bool Set(int index, const JSON value); + + /** + * Replaces a boolean value at index + * + * @param index The index to which to replace the value + * @param value The new boolean value to replace + * + * @return True if succeed, false otherwise + */ + public native bool SetBool(int index, bool value); + + /** + * Replaces a float value at index + * + * @param index The index to which to replace the value + * @param value The new float value to replace + * + * @return True if succeed, false otherwise + */ + public native bool SetFloat(int index, float value); + + /** + * Replaces an integer value at index + * + * @param index The index to which to replace the value + * @param value The new int value to replace + * + * @return True if succeed, false otherwise + */ + public native bool SetInt(int index, int value); + + /** + * Replaces an integer64 value at index + * + * @note This function automatically detects whether the value is signed or unsigned + * + * @param index The index to which to replace the value + * @param value The new integer64 value to replace (can be signed or unsigned) + * + * @return True if succeed, false otherwise + */ + public native bool SetInt64(int index, const char[] value); + + /** + * Replaces a string value at index + * + * @param index The index to which to replace the value + * @param value The new string value to replace + * + * @return True if succeed, false otherwise + */ + public native bool SetString(int index, const char[] value); + + /** + * Replaces a null value at index + * + * @param index The index to which to replace the value + * + * @return True if succeed, false otherwise + */ + public native bool SetNull(int index); + + /** + * Inserts a value at the end of the array + * + * @param value JSON handle to set + * + * @return True if succeed, false otherwise + */ + public native bool Push(const JSON value); + + /** + * Inserts a boolean value at the end of the array + * + * @param value Boolean value to set + * + * @return True if succeed, false otherwise + */ + public native bool PushBool(bool value); + + /** + * Inserts a float value at the end of the array + * + * @param value float to set + * + * @return True if succeed, false otherwise + */ + public native bool PushFloat(float value); + + /** + * Inserts an integer value at the end of the array + * + * @param value integer to set + * + * @return True if succeed, false otherwise + */ + public native bool PushInt(int value); + + /** + * Inserts an integer64 value at the end of the array + * + * @param value integer64 value + * + * @return True if succeed, false otherwise + */ + public native bool PushInt64(const char[] value); + + /** + * Inserts a string value at the end of the array + * + * @param value String to copy + * + * @return True if succeed, false otherwise + */ + public native bool PushString(const char[] value); + + /** + * Inserts a null value at the end of the array + * + * @return True if succeed, false otherwise + */ + public native bool PushNull(); + + /** + * Inserts a JSON value at specific index + * + * @note This function takes a linear search time + * + * @param index Position to insert (0 to size, size means append) + * @param value JSON handle to insert + * + * @return True if succeed, false otherwise + */ + public native bool Insert(int index, const JSON value); + + /** + * Inserts a boolean value at specific index + * + * @note This function takes a linear search time + * + * @param index Position to insert + * @param value Boolean value + * + * @return True if succeed, false otherwise + */ + public native bool InsertBool(int index, bool value); + + /** + * Inserts an integer value at specific index + * + * @note This function takes a linear search time + * + * @param index Position to insert + * @param value Integer value + * + * @return True if succeed, false otherwise + */ + public native bool InsertInt(int index, int value); + + /** + * Inserts an integer64 value at specific index + * + * @note This function takes a linear search time + * + * @param index Position to insert + * @param value Integer64 value (as string) + * + * @return True if succeed, false otherwise + */ + public native bool InsertInt64(int index, const char[] value); + + /** + * Inserts a float value at specific index + * + * @note This function takes a linear search time + * + * @param index Position to insert + * @param value Float value + * + * @return True if succeed, false otherwise + */ + public native bool InsertFloat(int index, float value); + + /** + * Inserts a string value at specific index + * + * @note This function takes a linear search time + * + * @param index Position to insert + * @param value String value + * + * @return True if succeed, false otherwise + */ + public native bool InsertString(int index, const char[] value); + + /** + * Inserts a null value at specific index + * + * @note This function takes a linear search time + * + * @param index Position to insert + * + * @return True if succeed, false otherwise + */ + public native bool InsertNull(int index); + + /** + * Prepends a JSON value to the beginning of the array + * + * @param value JSON handle to prepend + * + * @return True if succeed, false otherwise + */ + public native bool Prepend(const JSON value); + + /** + * Prepends a boolean value to the beginning of the array + * + * @param value Boolean value + * + * @return True if succeed, false otherwise + */ + public native bool PrependBool(bool value); + + /** + * Prepends an integer value to the beginning of the array + * + * @param value Integer value + * + * @return True if succeed, false otherwise + */ + public native bool PrependInt(int value); + + /** + * Prepends an integer64 value to the beginning of the array + * + * @param value Integer64 value (as string) + * + * @return True if succeed, false otherwise + */ + public native bool PrependInt64(const char[] value); + + /** + * Prepends a float value to the beginning of the array + * + * @param value Float value + * + * @return True if succeed, false otherwise + */ + public native bool PrependFloat(float value); + + /** + * Prepends a string value to the beginning of the array + * + * @param value String value + * + * @return True if succeed, false otherwise + */ + public native bool PrependString(const char[] value); + + /** + * Prepends a null value to the beginning of the array + * + * @return True if succeed, false otherwise + */ + public native bool PrependNull(); + + /** + * Removes an element from the array + * + * @note This function takes a linear search time + * + * @param index Position in the array (starting from 0) + * + * @return True if succeed, false otherwise + */ + public native bool Remove(int index); + + /** + * Removes the first value in this array + * + * @return True if succeed, false otherwise + */ + public native bool RemoveFirst(); + + /** + * Removes the last value in this array + * + * @return True if succeed, false otherwise + */ + public native bool RemoveLast(); + + /** + * Removes all values within a specified range in the array + * + * @note This function takes a linear search time + * + * @param start_index The start index of the range (0 is the first) + * @param count Number of items to remove (can be 0, in which case nothing happens) + * + * @return True if succeed, false otherwise + */ + public native bool RemoveRange(int start_index, int count); + + /** + * Searches for a boolean value in the array and returns its index + * + * @param value The boolean value to search for + * + * @return The index of the first matching element, or -1 if not found + * @error Invalid handle or handle is not an array + */ + public native int IndexOfBool(bool value); + + /** + * Searches for a string value in the array and returns its index + * + * @param value The string value to search for + * + * @return The index of the first matching element, or -1 if not found + * @error Invalid handle or handle is not an array + */ + public native int IndexOfString(const char[] value); + + /** + * Searches for an integer value in the array and returns its index + * + * @param value The integer value to search for + * + * @return The index of the first matching element, or -1 if not found + * @error Invalid handle or handle is not an array + */ + public native int IndexOfInt(int value); + + /** + * Searches for an integer64 value in the array and returns its index + * + * @param value The integer64 value to search for as string + * + * @return The index of the first matching element, or -1 if not found + * @error Invalid handle, handle is not an array, or invalid integer64 string format + */ + public native int IndexOfInt64(const char[] value); + + /** + * Searches for a float value in the array and returns its index + * + * @param value The float value to search for + * + * @return The index of the first matching element, or -1 if not found + * @error Invalid handle or handle is not an array + */ + public native int IndexOfFloat(float value); + + /** + * Removes all elements from the array + * + * @return True if succeed, false otherwise + */ + public native bool Clear(); + + /** + * Sorts the array elements + * + * @note Sorting rules: + * - Different types are sorted by their type ID + * - Strings are sorted lexicographically + * - Numbers are sorted by their numeric value + * - Booleans are sorted with false before true + * - Other types (null, object, array) are sorted by type only + * + * @param order Sort order, see JSON_SORT_ORDER enums + * + * @return True on success, false on failure + * @error Invalid handle or handle is not an array + */ + public native bool Sort(JSON_SORT_ORDER order = JSON_SORT_ASC); + + /** + * Retrieves the size of the array + */ + property int Length { + public native get(); + } + + /** + * @note This function takes a linear search time + * Returns the first element of this array + * Returns null if arr is null/empty or type is not array + * Needs to be freed using delete or CloseHandle() + */ + property JSON First { + public native get(); + } + + /** + * @note This function takes a linear search time + * Returns the last element of this array + * Returns null if arr is null/empty or type is not array + * Needs to be freed using delete or CloseHandle() + */ + property JSON Last { + public native get(); + } +}; + +methodmap JSONArrIter < Handle +{ + /** + * Creates an array iterator from a JSON array value + * + * @note Needs to be freed using delete or CloseHandle() + * @note Iterators are single-pass. Once Next returns null, call Reset() or create + * a new iterator to traverse the array again + * + * @param array JSON array value to iterate + * + * @return Array iterator handle, or null on failure + * @error Invalid handle or handle is not an array + */ + public native JSONArrIter(JSON array); + + /** + * Resets the iterator to the beginning of the array + * + * @note Only resets the iterator, does not free the iterator handle + * + * @return True on success, false on failure + * @error Invalid iterator handle + */ + public native bool Reset(); + + /** + * Moves the iterator to the next element + * + * @note Needs to be freed using delete or CloseHandle() + * + * @return JSON value handle for the next element, or null if iteration is complete + * @error Invalid iterator handle + */ + property JSON Next { + public native get(); + } + + /** + * Checks if there are more elements to iterate + * + * @return True if there are more elements, false otherwise + * @error Invalid iterator handle + */ + property bool HasNext { + public native get(); + } + + /** + * Gets the current index in the array iteration + * + * @return Current index (0-based), or -1 if iterator is not positioned + * @error Invalid iterator handle + */ + property int Index { + public native get(); + } + + /** + * Removes the current element from the array (mutable iterators only) + * + * @note This function can only be called on mutable iterators + * @note After calling Remove(), the iterator should not be used to continue iteration + * + * @return True on success, false on failure + * @error Invalid iterator handle or iterator is not mutable + */ + public native bool Remove(); +}; + +methodmap JSONObjIter < Handle +{ + /** + * Creates an object iterator from a JSON object value + * + * @note Needs to be freed using delete or CloseHandle() + * @note Iterators are single-pass. Once Next returns null, call Reset() or create + * a new iterator to traverse the object again + * + * @param obj JSON object value to iterate + * + * @return Object iterator handle, or null on failure + * @error Invalid handle or handle is not an object + */ + public native JSONObjIter(JSON obj); + + /** + * Resets the iterator to the beginning of the object + * + * @note Only resets the iterator, does not free the iterator handle + * + * @return True on success, false on failure + * @error Invalid iterator handle + */ + public native bool Reset(); + + /** + * Moves the iterator to the next key + * + * @param buffer Buffer to copy key name to + * @param maxlength Maximum length of the string buffer + * + * @return True if there are more elements, false when iteration is complete + * @error Invalid iterator handle + */ + public native bool Next(char[] buffer, int maxlength); + + /** + * Checks if there are more elements to iterate + * + * @return True if there are more elements, false otherwise + * @error Invalid iterator handle + */ + property bool HasNext { + public native get(); + } + + /** + * Gets the value associated with the current key + * + * @note Needs to be freed using delete or CloseHandle() + * @note This should be called after Next() to get the value for the current key + * + * @return JSON value handle for the current key's value, or null on failure + * @error Invalid iterator handle + */ + property JSON Value { + public native get(); + } + + /** + * Iterates to a specified key and returns the value + * + * @note Needs to be freed using delete or CloseHandle() + * @note This function does the same thing as JSONObject.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 + * @note 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 + * @note This function takes a linear search time if the key is not nearby + * + * @param key Key name to search for (should be a UTF-8 string with null-terminator) + * + * @return JSON value handle for the key's value, or null if key not found or input is invalid + * @error Invalid iterator handle + */ + public native JSON Get(const char[] key); + + /** + * Gets the current index in the object iteration + * + * @return Current index (0-based), or -1 if iterator is not positioned + * @error Invalid iterator handle + */ + property int Index { + public native get(); + } + + /** + * Removes the current key-value pair from the object (mutable iterators only) + * + * @note This function can only be called on mutable iterators + * @note After calling Remove(), the iterator should not be used to continue iteration + * + * @return True on success, false on failure + * @error Invalid iterator handle or iterator is not mutable + */ + public native bool Remove(); +}; + +public Extension __ext_json = { + name = "json", + file = "json.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public void __pl_json_SetNTVOptional() +{ + // JSONObject + MarkNativeAsOptional("JSONObject.JSONObject"); + MarkNativeAsOptional("JSONObject.FromStrings"); + MarkNativeAsOptional("JSONObject.Size.get"); + MarkNativeAsOptional("JSONObject.Get"); + MarkNativeAsOptional("JSONObject.GetBool"); + MarkNativeAsOptional("JSONObject.GetFloat"); + MarkNativeAsOptional("JSONObject.GetInt"); + MarkNativeAsOptional("JSONObject.GetInt64"); + MarkNativeAsOptional("JSONObject.GetString"); + MarkNativeAsOptional("JSONObject.IsNull"); + MarkNativeAsOptional("JSONObject.GetKey"); + MarkNativeAsOptional("JSONObject.GetValueAt"); + MarkNativeAsOptional("JSONObject.HasKey"); + MarkNativeAsOptional("JSONObject.RenameKey"); + MarkNativeAsOptional("JSONObject.Set"); + MarkNativeAsOptional("JSONObject.SetBool"); + MarkNativeAsOptional("JSONObject.SetFloat"); + MarkNativeAsOptional("JSONObject.SetInt"); + MarkNativeAsOptional("JSONObject.SetInt64"); + MarkNativeAsOptional("JSONObject.SetNull"); + MarkNativeAsOptional("JSONObject.SetString"); + MarkNativeAsOptional("JSONObject.Remove"); + MarkNativeAsOptional("JSONObject.Clear"); + MarkNativeAsOptional("JSONObject.FromString"); + MarkNativeAsOptional("JSONObject.FromFile"); + MarkNativeAsOptional("JSONObject.Sort"); + + // JSONArray + MarkNativeAsOptional("JSONArray.JSONArray"); + MarkNativeAsOptional("JSONArray.FromStrings"); + MarkNativeAsOptional("JSONArray.FromInt"); + MarkNativeAsOptional("JSONArray.FromInt64"); + MarkNativeAsOptional("JSONArray.FromBool"); + MarkNativeAsOptional("JSONArray.FromFloat"); + MarkNativeAsOptional("JSONArray.Length.get"); + MarkNativeAsOptional("JSONArray.Get"); + MarkNativeAsOptional("JSONArray.First.get"); + MarkNativeAsOptional("JSONArray.Last.get"); + MarkNativeAsOptional("JSONArray.GetBool"); + MarkNativeAsOptional("JSONArray.GetFloat"); + MarkNativeAsOptional("JSONArray.GetInt"); + MarkNativeAsOptional("JSONArray.GetInt64"); + MarkNativeAsOptional("JSONArray.GetString"); + MarkNativeAsOptional("JSONArray.IsNull"); + MarkNativeAsOptional("JSONArray.Set"); + MarkNativeAsOptional("JSONArray.SetBool"); + MarkNativeAsOptional("JSONArray.SetFloat"); + MarkNativeAsOptional("JSONArray.SetInt"); + MarkNativeAsOptional("JSONArray.SetInt64"); + MarkNativeAsOptional("JSONArray.SetNull"); + MarkNativeAsOptional("JSONArray.SetString"); + MarkNativeAsOptional("JSONArray.Push"); + MarkNativeAsOptional("JSONArray.PushBool"); + MarkNativeAsOptional("JSONArray.PushFloat"); + MarkNativeAsOptional("JSONArray.PushInt"); + MarkNativeAsOptional("JSONArray.PushInt64"); + MarkNativeAsOptional("JSONArray.PushNull"); + MarkNativeAsOptional("JSONArray.PushString"); + MarkNativeAsOptional("JSONArray.Insert"); + MarkNativeAsOptional("JSONArray.InsertBool"); + MarkNativeAsOptional("JSONArray.InsertInt"); + MarkNativeAsOptional("JSONArray.InsertInt64"); + MarkNativeAsOptional("JSONArray.InsertFloat"); + MarkNativeAsOptional("JSONArray.InsertString"); + MarkNativeAsOptional("JSONArray.InsertNull"); + MarkNativeAsOptional("JSONArray.Prepend"); + MarkNativeAsOptional("JSONArray.PrependBool"); + MarkNativeAsOptional("JSONArray.PrependInt"); + MarkNativeAsOptional("JSONArray.PrependInt64"); + MarkNativeAsOptional("JSONArray.PrependFloat"); + MarkNativeAsOptional("JSONArray.PrependString"); + MarkNativeAsOptional("JSONArray.PrependNull"); + MarkNativeAsOptional("JSONArray.Remove"); + MarkNativeAsOptional("JSONArray.RemoveFirst"); + MarkNativeAsOptional("JSONArray.RemoveLast"); + MarkNativeAsOptional("JSONArray.RemoveRange"); + MarkNativeAsOptional("JSONArray.Clear"); + MarkNativeAsOptional("JSONArray.FromString"); + MarkNativeAsOptional("JSONArray.FromFile"); + MarkNativeAsOptional("JSONArray.IndexOfBool"); + MarkNativeAsOptional("JSONArray.IndexOfString"); + MarkNativeAsOptional("JSONArray.IndexOfInt"); + MarkNativeAsOptional("JSONArray.IndexOfInt64"); + MarkNativeAsOptional("JSONArray.IndexOfFloat"); + MarkNativeAsOptional("JSONArray.Sort"); + + // JSON + MarkNativeAsOptional("JSON.ToString"); + MarkNativeAsOptional("JSON.ToFile"); + MarkNativeAsOptional("JSON.Parse"); + MarkNativeAsOptional("JSON.Equals"); + MarkNativeAsOptional("JSON.EqualsStr"); + MarkNativeAsOptional("JSON.DeepCopy"); + MarkNativeAsOptional("JSON.GetTypeDesc"); + MarkNativeAsOptional("JSON.GetSerializedSize"); + MarkNativeAsOptional("JSON.ReadSize.get"); + MarkNativeAsOptional("JSON.RefCount.get"); + MarkNativeAsOptional("JSON.Type.get"); + MarkNativeAsOptional("JSON.SubType.get"); + MarkNativeAsOptional("JSON.IsArray.get"); + MarkNativeAsOptional("JSON.IsObject.get"); + MarkNativeAsOptional("JSON.IsInt.get"); + MarkNativeAsOptional("JSON.IsUint.get"); + MarkNativeAsOptional("JSON.IsSint.get"); + MarkNativeAsOptional("JSON.IsNum.get"); + MarkNativeAsOptional("JSON.IsBool.get"); + MarkNativeAsOptional("JSON.IsTrue.get"); + MarkNativeAsOptional("JSON.IsFalse.get"); + MarkNativeAsOptional("JSON.IsFloat.get"); + MarkNativeAsOptional("JSON.IsStr.get"); + MarkNativeAsOptional("JSON.IsNull.get"); + MarkNativeAsOptional("JSON.IsCtn.get"); + MarkNativeAsOptional("JSON.IsMutable.get"); + MarkNativeAsOptional("JSON.IsImmutable.get"); + MarkNativeAsOptional("JSON.ForeachObject"); + MarkNativeAsOptional("JSON.ForeachArray"); + MarkNativeAsOptional("JSON.ForeachKey"); + MarkNativeAsOptional("JSON.ForeachIndex"); + MarkNativeAsOptional("JSON.ToMutable"); + MarkNativeAsOptional("JSON.ToImmutable"); + MarkNativeAsOptional("JSON.ApplyJsonPatch"); + MarkNativeAsOptional("JSON.JsonPatchInPlace"); + MarkNativeAsOptional("JSON.ApplyMergePatch"); + MarkNativeAsOptional("JSON.MergePatchInPlace"); + + // JSON CREATE & GET + MarkNativeAsOptional("JSON.Pack"); + MarkNativeAsOptional("JSON.CreateBool"); + MarkNativeAsOptional("JSON.CreateFloat"); + MarkNativeAsOptional("JSON.CreateInt"); + MarkNativeAsOptional("JSON.CreateInt64"); + MarkNativeAsOptional("JSON.CreateNull"); + MarkNativeAsOptional("JSON.CreateString"); + MarkNativeAsOptional("JSON.GetBool"); + MarkNativeAsOptional("JSON.GetFloat"); + MarkNativeAsOptional("JSON.GetInt"); + MarkNativeAsOptional("JSON.GetInt64"); + MarkNativeAsOptional("JSON.GetString"); + MarkNativeAsOptional("JSON.ReadNumber"); + MarkNativeAsOptional("JSON.WriteNumber"); + MarkNativeAsOptional("JSON.SetFpToFloat"); + MarkNativeAsOptional("JSON.SetFpToFixed"); + MarkNativeAsOptional("JSON.SetBool"); + MarkNativeAsOptional("JSON.SetInt"); + MarkNativeAsOptional("JSON.SetInt64"); + MarkNativeAsOptional("JSON.SetFloat"); + MarkNativeAsOptional("JSON.SetString"); + MarkNativeAsOptional("JSON.SetNull"); + + // JSON POINTER + MarkNativeAsOptional("JSON.PtrGet"); + MarkNativeAsOptional("JSON.PtrGetBool"); + MarkNativeAsOptional("JSON.PtrGetFloat"); + MarkNativeAsOptional("JSON.PtrGetInt"); + MarkNativeAsOptional("JSON.PtrGetInt64"); + MarkNativeAsOptional("JSON.PtrGetString"); + MarkNativeAsOptional("JSON.PtrGetIsNull"); + MarkNativeAsOptional("JSON.PtrGetLength"); + MarkNativeAsOptional("JSON.PtrSet"); + MarkNativeAsOptional("JSON.PtrSetBool"); + MarkNativeAsOptional("JSON.PtrSetFloat"); + MarkNativeAsOptional("JSON.PtrSetInt"); + MarkNativeAsOptional("JSON.PtrSetInt64"); + MarkNativeAsOptional("JSON.PtrSetString"); + MarkNativeAsOptional("JSON.PtrSetNull"); + MarkNativeAsOptional("JSON.PtrAdd"); + MarkNativeAsOptional("JSON.PtrAddBool"); + MarkNativeAsOptional("JSON.PtrAddFloat"); + MarkNativeAsOptional("JSON.PtrAddInt"); + MarkNativeAsOptional("JSON.PtrAddInt64"); + MarkNativeAsOptional("JSON.PtrAddString"); + MarkNativeAsOptional("JSON.PtrAddNull"); + MarkNativeAsOptional("JSON.PtrRemove"); + MarkNativeAsOptional("JSON.PtrTryGetVal"); + MarkNativeAsOptional("JSON.PtrTryGetBool"); + MarkNativeAsOptional("JSON.PtrTryGetFloat"); + MarkNativeAsOptional("JSON.PtrTryGetInt"); + MarkNativeAsOptional("JSON.PtrTryGetInt64"); + MarkNativeAsOptional("JSON.PtrTryGetString"); + + // JSONArrIter + MarkNativeAsOptional("JSONArrIter.JSONArrIter"); + MarkNativeAsOptional("JSONArrIter.Next.get"); + MarkNativeAsOptional("JSONArrIter.HasNext.get"); + MarkNativeAsOptional("JSONArrIter.Index.get"); + MarkNativeAsOptional("JSONArrIter.Remove"); + MarkNativeAsOptional("JSONArrIter.Reset"); + + // JSONObjIter + MarkNativeAsOptional("JSONObjIter.JSONObjIter"); + MarkNativeAsOptional("JSONObjIter.Next"); + MarkNativeAsOptional("JSONObjIter.HasNext.get"); + MarkNativeAsOptional("JSONObjIter.Value.get"); + MarkNativeAsOptional("JSONObjIter.Get"); + MarkNativeAsOptional("JSONObjIter.Index.get"); + MarkNativeAsOptional("JSONObjIter.Remove"); + MarkNativeAsOptional("JSONObjIter.Reset"); +} +#endif \ No newline at end of file diff --git a/plugins/testsuite/test_json.sp b/plugins/testsuite/test_json.sp new file mode 100755 index 0000000000..2c0b149206 --- /dev/null +++ b/plugins/testsuite/test_json.sp @@ -0,0 +1,3183 @@ +#include +#include + +#pragma newdecls required +#pragma semicolon 1 + +public Plugin myinfo = +{ + name = "JSON Test Suite", + author = "ProjectSky", + description = "test suite for JSON functions", + version = "1.0.0", + url = "https://github.com/ProjectSky/sm-ext-yyjson" +}; + +// Test statistics +int g_iTotalTests = 0; +int g_iPassedTests = 0; +int g_iFailedTests = 0; +char g_sCurrentTest[128]; +bool g_bCurrentTestFailed = false; + +public void OnPluginStart() +{ + RegServerCmd("test_json", Command_RunTests, "Run JSON test suite"); +} + +// ============================================================================ +// Test Framework - Assertion Functions +// ============================================================================ + +/** + * Start a new test case + */ +void TestStart(const char[] name) +{ + g_iTotalTests++; + strcopy(g_sCurrentTest, sizeof(g_sCurrentTest), name); + g_bCurrentTestFailed = false; +} + +/** + * End current test case + */ +void TestEnd() +{ + if (!g_bCurrentTestFailed) + { + g_iPassedTests++; + PrintToServer("[PASS] %s", g_sCurrentTest); + } + else + { + g_iFailedTests++; + } +} + +/** + * Assert a condition is true + */ +void Assert(bool condition, const char[] message = "") +{ + if (!condition) + { + g_bCurrentTestFailed = true; + PrintToServer("[FAIL] %s - %s", g_sCurrentTest, message); + } +} + +/** + * Assert two integers are equal + */ +void AssertEq(int a, int b, const char[] message = "") +{ + if (a != b) + { + g_bCurrentTestFailed = true; + char buffer[256]; + if (message[0] != '\0') + { + FormatEx(buffer, sizeof(buffer), "%s (expected %d, got %d)", message, b, a); + } + else + { + FormatEx(buffer, sizeof(buffer), "Expected %d, got %d", b, a); + } + PrintToServer("[FAIL] %s - %s", g_sCurrentTest, buffer); + } +} + +/** + * Assert two integers are not equal + */ +stock void AssertNeq(int a, int b, const char[] message = "") +{ + if (a == b) + { + g_bCurrentTestFailed = true; + char buffer[256]; + if (message[0] != '\0') + { + FormatEx(buffer, sizeof(buffer), "%s (values should not be equal: %d)", message, a); + } + else + { + FormatEx(buffer, sizeof(buffer), "Values should not be equal: %d", a); + } + PrintToServer("[FAIL] %s - %s", g_sCurrentTest, buffer); + } +} + +/** + * Assert condition is true + */ +void AssertTrue(bool condition, const char[] message = "") +{ + Assert(condition, message[0] != '\0' ? message : "Expected true, got false"); +} + +/** + * Assert condition is false + */ +void AssertFalse(bool condition, const char[] message = "") +{ + Assert(!condition, message[0] != '\0' ? message : "Expected false, got true"); +} + +/** + * Assert two strings are equal + */ +void AssertStrEq(const char[] a, const char[] b, const char[] message = "") +{ + if (strcmp(a, b) != 0) + { + g_bCurrentTestFailed = true; + char buffer[512]; + if (message[0] != '\0') + { + FormatEx(buffer, sizeof(buffer), "%s (expected \"%s\", got \"%s\")", message, b, a); + } + else + { + FormatEx(buffer, sizeof(buffer), "Expected \"%s\", got \"%s\"", b, a); + } + PrintToServer("[FAIL] %s - %s", g_sCurrentTest, buffer); + } +} + +/** + * Assert two floats are equal within epsilon + */ +void AssertFloatEq(float a, float b, const char[] message = "", float epsilon = 0.0001) +{ + if (FloatAbs(a - b) > epsilon) + { + g_bCurrentTestFailed = true; + char buffer[256]; + if (message[0] != '\0') + { + FormatEx(buffer, sizeof(buffer), "%s (expected %.4f, got %.4f)", message, b, a); + } + else + { + FormatEx(buffer, sizeof(buffer), "Expected %.4f, got %.4f", b, a); + } + PrintToServer("[FAIL] %s - %s", g_sCurrentTest, buffer); + } +} + +/** + * Assert handle is valid + */ +void AssertValidHandle(Handle handle, const char[] message = "") +{ + Assert(handle != null, message[0] != '\0' ? message : "Handle should not be null"); +} + +/** + * Assert handle is null + */ +void AssertNullHandle(Handle handle, const char[] message = "") +{ + Assert(handle == null, message[0] != '\0' ? message : "Handle should be null"); +} + +// ============================================================================ +// Test Command +// ============================================================================ + +public Action Command_RunTests(int args) +{ + PrintToServer("========================================"); + PrintToServer("JSON Test Suite"); + PrintToServer("========================================"); + + // Reset statistics + g_iTotalTests = 0; + g_iPassedTests = 0; + g_iFailedTests = 0; + + // Run all test categories + Test_BasicValues(); + Test_ObjectOperations(); + Test_ArrayOperations(); + Test_ParseAndSerialize(); + Test_Iterators(); + Test_JSONPointer(); + Test_AdvancedFeatures(); + Test_Int64Operations(); + Test_EdgeCases(); + + // Print results + PrintToServer("========================================"); + PrintToServer("JSON Test Suite Results"); + PrintToServer("========================================"); + PrintToServer("Total Tests: %d", g_iTotalTests); + PrintToServer("Passed: %d", g_iPassedTests); + PrintToServer("Failed: %d", g_iFailedTests); + + if (g_iTotalTests > 0) + { + float successRate = (float(g_iPassedTests) / float(g_iTotalTests)) * 100.0; + PrintToServer("Success Rate: %.2f%%", successRate); + } + + PrintToServer("========================================"); + + return Plugin_Handled; +} + +// ============================================================================ +// 2.1 Basic Value Tests +// ============================================================================ + +void Test_BasicValues() +{ + PrintToServer("\n[Category] Basic Value Tests"); + + // Test CreateBool & GetBool + TestStart("BasicValues_CreateBool_True"); + { + JSON val = JSON.CreateBool(true); + AssertValidHandle(val); + AssertTrue(val.GetBool()); + AssertEq(val.Type, JSON_TYPE_BOOL); + AssertTrue(val.IsBool); + AssertTrue(val.IsTrue); + AssertFalse(val.IsFalse); + delete val; + } + TestEnd(); + + TestStart("BasicValues_CreateBool_False"); + { + JSON val = JSON.CreateBool(false); + AssertValidHandle(val); + AssertFalse(val.GetBool()); + AssertTrue(val.IsBool); + AssertFalse(val.IsTrue); + AssertTrue(val.IsFalse); + delete val; + } + TestEnd(); + + // Test CreateInt & GetInt + TestStart("BasicValues_CreateInt_Positive"); + { + JSON val = JSON.CreateInt(42); + AssertValidHandle(val); + AssertEq(val.GetInt(), 42); + AssertEq(val.Type, JSON_TYPE_NUM); + AssertTrue(val.IsInt); + AssertTrue(val.IsNum); + delete val; + } + TestEnd(); + + TestStart("BasicValues_CreateInt_Negative"); + { + JSON val = JSON.CreateInt(-123); + AssertValidHandle(val); + AssertEq(val.GetInt(), -123); + AssertTrue(val.IsSint); + delete val; + } + TestEnd(); + + TestStart("BasicValues_CreateInt_Zero"); + { + JSON val = JSON.CreateInt(0); + AssertValidHandle(val); + AssertEq(val.GetInt(), 0); + delete val; + } + TestEnd(); + + // Test CreateFloat & GetFloat + TestStart("BasicValues_CreateFloat_Positive"); + { + JSON val = JSON.CreateFloat(3.14159); + AssertValidHandle(val); + AssertFloatEq(val.GetFloat(), 3.14159); + AssertTrue(val.IsFloat); + AssertTrue(val.IsNum); + delete val; + } + TestEnd(); + + TestStart("BasicValues_CreateFloat_Negative"); + { + JSON val = JSON.CreateFloat(-2.71828); + AssertValidHandle(val); + AssertFloatEq(val.GetFloat(), -2.71828); + delete val; + } + TestEnd(); + + TestStart("BasicValues_CreateFloat_Zero"); + { + JSON val = JSON.CreateFloat(0.0); + AssertValidHandle(val); + AssertFloatEq(val.GetFloat(), 0.0); + delete val; + } + TestEnd(); + + // Test CreateString & GetString + TestStart("BasicValues_CreateString_Regular"); + { + JSON val = JSON.CreateString("Hello, World!"); + AssertValidHandle(val); + char buffer[64]; + AssertTrue(val.GetString(buffer, sizeof(buffer))); + AssertStrEq(buffer, "Hello, World!"); + AssertEq(val.Type, JSON_TYPE_STR); + AssertTrue(val.IsStr); + delete val; + } + TestEnd(); + + TestStart("BasicValues_CreateString_Empty"); + { + JSON val = JSON.CreateString(""); + AssertValidHandle(val); + char buffer[64]; + AssertTrue(val.GetString(buffer, sizeof(buffer))); + AssertStrEq(buffer, ""); + delete val; + } + TestEnd(); + + TestStart("BasicValues_CreateString_Unicode"); + { + JSON val = JSON.CreateString("测试字符串"); + AssertValidHandle(val); + char buffer[64]; + AssertTrue(val.GetString(buffer, sizeof(buffer))); + AssertStrEq(buffer, "测试字符串"); + delete val; + } + TestEnd(); + + // Test CreateInt64 & GetInt64 + TestStart("BasicValues_CreateInt64_Large"); + { + JSON val = JSON.CreateInt64("9223372036854775807"); + AssertValidHandle(val); + char buffer[32]; + AssertTrue(val.GetInt64(buffer, sizeof(buffer))); + AssertStrEq(buffer, "9223372036854775807"); + delete val; + } + TestEnd(); + + TestStart("BasicValues_CreateInt64_Negative"); + { + JSON val = JSON.CreateInt64("-9223372036854775808"); + AssertValidHandle(val); + char buffer[32]; + AssertTrue(val.GetInt64(buffer, sizeof(buffer))); + AssertStrEq(buffer, "-9223372036854775808"); + delete val; + } + TestEnd(); + + // Test CreateNull + TestStart("BasicValues_CreateNull"); + { + JSON val = JSON.CreateNull(); + AssertValidHandle(val); + AssertEq(val.Type, JSON_TYPE_NULL); + AssertTrue(val.IsNull); + delete val; + } + TestEnd(); + + // Test GetTypeDesc + TestStart("BasicValues_GetTypeDesc"); + { + JSON boolVal = JSON.CreateBool(true); + JSON intVal = JSON.CreateInt(42); + JSON floatVal = JSON.CreateFloat(3.14); + JSON strVal = JSON.CreateString("test"); + JSON nullVal = JSON.CreateNull(); + + char buffer[32]; + + boolVal.GetTypeDesc(buffer, sizeof(buffer)); + AssertStrEq(buffer, "true"); + + intVal.GetTypeDesc(buffer, sizeof(buffer)); + AssertTrue(strcmp(buffer, "uint") == 0 || strcmp(buffer, "sint") == 0); + + floatVal.GetTypeDesc(buffer, sizeof(buffer)); + AssertStrEq(buffer, "real"); + + strVal.GetTypeDesc(buffer, sizeof(buffer)); + AssertStrEq(buffer, "string"); + + nullVal.GetTypeDesc(buffer, sizeof(buffer)); + AssertStrEq(buffer, "null"); + + delete boolVal; + delete intVal; + delete floatVal; + delete strVal; + delete nullVal; + } + TestEnd(); +} + +// ============================================================================ +// 2.2 Object Operations Tests +// ============================================================================ + +void Test_ObjectOperations() +{ + PrintToServer("\n[Category] Object Operations Tests"); + + // Test object creation + TestStart("Object_Constructor"); + { + JSONObject obj = new JSONObject(); + AssertValidHandle(obj); + AssertTrue(obj.IsObject); + AssertEq(obj.Size, 0); + delete obj; + } + TestEnd(); + + // Test Set/Get methods + TestStart("Object_SetGetInt"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.SetInt("number", 42)); + AssertEq(obj.GetInt("number"), 42); + AssertEq(obj.Size, 1); + delete obj; + } + TestEnd(); + + TestStart("Object_SetGetFloat"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.SetFloat("pi", 3.14159)); + AssertFloatEq(obj.GetFloat("pi"), 3.14159); + delete obj; + } + TestEnd(); + + TestStart("Object_SetGetBool"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.SetBool("flag", true)); + AssertTrue(obj.GetBool("flag")); + delete obj; + } + TestEnd(); + + TestStart("Object_SetGetString"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.SetString("name", "test")); + char buffer[64]; + AssertTrue(obj.GetString("name", buffer, sizeof(buffer))); + AssertStrEq(buffer, "test"); + delete obj; + } + TestEnd(); + + TestStart("Object_SetGetInt64"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.SetInt64("bignum", "9223372036854775807")); + char buffer[32]; + AssertTrue(obj.GetInt64("bignum", buffer, sizeof(buffer))); + AssertStrEq(buffer, "9223372036854775807"); + delete obj; + } + TestEnd(); + + TestStart("Object_SetGetNull"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.SetNull("nullable")); + AssertTrue(obj.IsNull("nullable")); + delete obj; + } + TestEnd(); + + // Test HasKey + TestStart("Object_HasKey"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("exists", 1); + AssertTrue(obj.HasKey("exists")); + AssertFalse(obj.HasKey("notexists")); + delete obj; + } + TestEnd(); + + // Test GetKey and GetValueAt + TestStart("Object_GetKeyAndValueAt"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("first", 1); + obj.SetInt("second", 2); + + char key[32]; + AssertTrue(obj.GetKey(0, key, sizeof(key))); + AssertStrEq(key, "first"); + + JSON val = obj.GetValueAt(0); + AssertValidHandle(val); + AssertEq(val.GetInt(), 1); + + delete val; + delete obj; + } + TestEnd(); + + // Test Remove + TestStart("Object_Remove"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("remove_me", 1); + obj.SetInt("keep_me", 2); + AssertEq(obj.Size, 2); + + AssertTrue(obj.Remove("remove_me")); + AssertEq(obj.Size, 1); + AssertFalse(obj.HasKey("remove_me")); + AssertTrue(obj.HasKey("keep_me")); + + delete obj; + } + TestEnd(); + + // Test Clear + TestStart("Object_Clear"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("a", 1); + obj.SetInt("b", 2); + obj.SetInt("c", 3); + AssertEq(obj.Size, 3); + + AssertTrue(obj.Clear()); + AssertEq(obj.Size, 0); + + delete obj; + } + TestEnd(); + + // Test RenameKey + TestStart("Object_RenameKey"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("oldname", 42); + + AssertTrue(obj.RenameKey("oldname", "newname")); + AssertFalse(obj.HasKey("oldname")); + AssertTrue(obj.HasKey("newname")); + AssertEq(obj.GetInt("newname"), 42); + + delete obj; + } + TestEnd(); + + // Test FromString + TestStart("Object_FromString"); + { + JSONObject obj = JSONObject.FromString("{\"key\":\"value\",\"num\":123}"); + AssertValidHandle(obj); + + char buffer[64]; + AssertTrue(obj.GetString("key", buffer, sizeof(buffer))); + AssertStrEq(buffer, "value"); + AssertEq(obj.GetInt("num"), 123); + + delete obj; + } + TestEnd(); + + // Test FromStrings + TestStart("Object_FromStrings"); + { + char pairs[][] = {"name", "test", "type", "demo", "version", "1.0"}; + JSONObject obj = JSONObject.FromStrings(pairs, sizeof(pairs)); + AssertValidHandle(obj); + AssertEq(obj.Size, 3); + + char buffer[64]; + AssertTrue(obj.GetString("name", buffer, sizeof(buffer))); + AssertStrEq(buffer, "test"); + + delete obj; + } + TestEnd(); + + // Test Sort + TestStart("Object_Sort_Ascending"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("zebra", 1); + obj.SetInt("alpha", 2); + obj.SetInt("beta", 3); + + AssertTrue(obj.Sort(JSON_SORT_ASC)); + + char key[32]; + obj.GetKey(0, key, sizeof(key)); + AssertStrEq(key, "alpha"); + + delete obj; + } + TestEnd(); + + TestStart("Object_Sort_Descending"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("alpha", 1); + obj.SetInt("beta", 2); + obj.SetInt("gamma", 3); + + AssertTrue(obj.Sort(JSON_SORT_DESC)); + + char key[32]; + obj.GetKey(0, key, sizeof(key)); + AssertStrEq(key, "gamma"); + + delete obj; + } + TestEnd(); + + // Test Set with handle + TestStart("Object_SetWithHandle"); + { + JSONObject obj = new JSONObject(); + JSON val = JSON.CreateInt(999); + + AssertTrue(obj.Set("nested", val)); + AssertEq(obj.GetInt("nested"), 999); + + delete val; + delete obj; + } + TestEnd(); + + // Test Get with handle + TestStart("Object_GetHandle"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("value", 42); + + JSON val = obj.Get("value"); + AssertValidHandle(val); + AssertEq(val.GetInt(), 42); + + delete val; + delete obj; + } + TestEnd(); +} + +// ============================================================================ +// 2.3 Array Operations Tests +// ============================================================================ + +void Test_ArrayOperations() +{ + PrintToServer("\n[Category] Array Operations Tests"); + + // Test array creation + TestStart("Array_Constructor"); + { + JSONArray arr = new JSONArray(); + AssertValidHandle(arr); + AssertTrue(arr.IsArray); + AssertEq(arr.Length, 0); + delete arr; + } + TestEnd(); + + // Test Push methods + TestStart("Array_PushInt"); + { + JSONArray arr = new JSONArray(); + AssertTrue(arr.PushInt(42)); + AssertEq(arr.Length, 1); + AssertEq(arr.GetInt(0), 42); + delete arr; + } + TestEnd(); + + TestStart("Array_PushFloat"); + { + JSONArray arr = new JSONArray(); + AssertTrue(arr.PushFloat(3.14)); + AssertFloatEq(arr.GetFloat(0), 3.14); + delete arr; + } + TestEnd(); + + TestStart("Array_PushBool"); + { + JSONArray arr = new JSONArray(); + AssertTrue(arr.PushBool(true)); + AssertTrue(arr.GetBool(0)); + delete arr; + } + TestEnd(); + + TestStart("Array_PushString"); + { + JSONArray arr = new JSONArray(); + AssertTrue(arr.PushString("test")); + char buffer[64]; + AssertTrue(arr.GetString(0, buffer, sizeof(buffer))); + AssertStrEq(buffer, "test"); + delete arr; + } + TestEnd(); + + TestStart("Array_PushInt64"); + { + JSONArray arr = new JSONArray(); + AssertTrue(arr.PushInt64("9223372036854775807")); + char buffer[32]; + arr.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "9223372036854775807"); + delete arr; + } + TestEnd(); + + TestStart("Array_PushNull"); + { + JSONArray arr = new JSONArray(); + AssertTrue(arr.PushNull()); + AssertTrue(arr.IsNull(0)); + delete arr; + } + TestEnd(); + + // Test Set/Get methods + TestStart("Array_SetGetInt"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(0); + AssertTrue(arr.SetInt(0, 100)); + AssertEq(arr.GetInt(0), 100); + delete arr; + } + TestEnd(); + + TestStart("Array_SetGetFloat"); + { + JSONArray arr = new JSONArray(); + arr.PushFloat(0.0); + AssertTrue(arr.SetFloat(0, 2.718)); + AssertFloatEq(arr.GetFloat(0), 2.718); + delete arr; + } + TestEnd(); + + // Test Remove methods + TestStart("Array_Remove"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(2); + arr.PushInt(3); + AssertEq(arr.Length, 3); + + AssertTrue(arr.Remove(1)); + AssertEq(arr.Length, 2); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(1), 3); + + delete arr; + } + TestEnd(); + + TestStart("Array_RemoveFirst"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(2); + arr.PushInt(3); + + AssertTrue(arr.RemoveFirst()); + AssertEq(arr.Length, 2); + AssertEq(arr.GetInt(0), 2); + + delete arr; + } + TestEnd(); + + TestStart("Array_RemoveLast"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(2); + arr.PushInt(3); + + AssertTrue(arr.RemoveLast()); + AssertEq(arr.Length, 2); + AssertEq(arr.GetInt(1), 2); + + delete arr; + } + TestEnd(); + + TestStart("Array_RemoveRange"); + { + JSONArray arr = new JSONArray(); + for (int i = 0; i < 10; i++) + { + arr.PushInt(i); + } + + AssertTrue(arr.RemoveRange(2, 5)); + AssertEq(arr.Length, 5); + + delete arr; + } + TestEnd(); + + TestStart("Array_Clear"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(2); + arr.PushInt(3); + + AssertTrue(arr.Clear()); + AssertEq(arr.Length, 0); + + delete arr; + } + TestEnd(); + + // Test First/Last properties + TestStart("Array_FirstLast"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(10); + arr.PushInt(20); + arr.PushInt(30); + + JSON first = arr.First; + JSON last = arr.Last; + + AssertValidHandle(first); + AssertValidHandle(last); + AssertEq(first.GetInt(), 10); + AssertEq(last.GetInt(), 30); + + delete first; + delete last; + delete arr; + } + TestEnd(); + + // Test IndexOf methods + TestStart("Array_IndexOfInt"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(10); + arr.PushInt(20); + arr.PushInt(30); + + AssertEq(arr.IndexOfInt(20), 1); + AssertEq(arr.IndexOfInt(999), -1); + + delete arr; + } + TestEnd(); + + TestStart("Array_IndexOfBool"); + { + JSONArray arr = new JSONArray(); + arr.PushBool(false); + arr.PushBool(true); + arr.PushBool(false); + + AssertEq(arr.IndexOfBool(true), 1); + + delete arr; + } + TestEnd(); + + TestStart("Array_IndexOfString"); + { + JSONArray arr = new JSONArray(); + arr.PushString("apple"); + arr.PushString("banana"); + arr.PushString("cherry"); + + AssertEq(arr.IndexOfString("banana"), 1); + AssertEq(arr.IndexOfString("orange"), -1); + + delete arr; + } + TestEnd(); + + TestStart("Array_IndexOfFloat"); + { + JSONArray arr = new JSONArray(); + arr.PushFloat(1.1); + arr.PushFloat(2.2); + arr.PushFloat(3.3); + + AssertEq(arr.IndexOfFloat(2.2), 1); + + delete arr; + } + TestEnd(); + + TestStart("Array_IndexOfInt64"); + { + JSONArray arr = new JSONArray(); + arr.PushInt64("1000000000000"); + arr.PushInt64("2000000000000"); + + AssertEq(arr.IndexOfInt64("2000000000000"), 1); + AssertEq(arr.IndexOfInt64("3000000000000"), -1); + + delete arr; + } + TestEnd(); + + // Test FromString + TestStart("Array_FromString"); + { + JSONArray arr = JSONArray.FromString("[1,2,3,4,5]"); + AssertValidHandle(arr); + AssertEq(arr.Length, 5); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(4), 5); + delete arr; + } + TestEnd(); + + // Test FromStrings + TestStart("Array_FromStrings"); + { + char strings[][] = {"apple", "banana", "cherry"}; + JSONArray arr = JSONArray.FromStrings(strings, sizeof(strings)); + AssertValidHandle(arr); + AssertEq(arr.Length, 3); + + char buffer[64]; + arr.GetString(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "banana"); + + delete arr; + } + TestEnd(); + + // Test Sort + TestStart("Array_Sort_Ascending"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(3); + arr.PushInt(1); + arr.PushInt(4); + arr.PushInt(1); + arr.PushInt(5); + + AssertTrue(arr.Sort(JSON_SORT_ASC)); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(4), 5); + + delete arr; + } + TestEnd(); + + TestStart("Array_Sort_Descending"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(2); + arr.PushInt(3); + arr.PushInt(4); + arr.PushInt(5); + + AssertTrue(arr.Sort(JSON_SORT_DESC)); + AssertEq(arr.GetInt(0), 5); + AssertEq(arr.GetInt(4), 1); + + delete arr; + } + TestEnd(); + + // Test Push with handle + TestStart("Array_PushHandle"); + { + JSONArray arr = new JSONArray(); + JSON val = JSON.CreateInt(999); + + AssertTrue(arr.Push(val)); + AssertEq(arr.GetInt(0), 999); + + delete val; + delete arr; + } + TestEnd(); + + // Test Get with handle + TestStart("Array_GetHandle"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(42); + + JSON val = arr.Get(0); + AssertValidHandle(val); + AssertEq(val.GetInt(), 42); + + delete val; + delete arr; + } + TestEnd(); + + // Test FromInt + TestStart("Array_FromInt"); + { + int values[] = {10, 20, 30, 40, 50}; + JSONArray arr = JSONArray.FromInt(values, sizeof(values)); + + AssertValidHandle(arr); + AssertEq(arr.Length, 5); + AssertEq(arr.GetInt(0), 10); + AssertEq(arr.GetInt(2), 30); + AssertEq(arr.GetInt(4), 50); + + delete arr; + } + TestEnd(); + + TestStart("Array_FromInt_Negative"); + { + int values[] = {-100, -50, 0, 50, 100}; + JSONArray arr = JSONArray.FromInt(values, sizeof(values)); + + AssertValidHandle(arr); + AssertEq(arr.Length, 5); + AssertEq(arr.GetInt(0), -100); + AssertEq(arr.GetInt(2), 0); + AssertEq(arr.GetInt(4), 100); + + delete arr; + } + TestEnd(); + + // Test FromInt64 + TestStart("Array_FromInt64_Mixed"); + { + char values[][] = {"9223372036854775807", "-9223372036854775808", "0", "18446744073709551615"}; + JSONArray arr = JSONArray.FromInt64(values, sizeof(values)); + + AssertValidHandle(arr); + AssertEq(arr.Length, 4); + + char buffer[32]; + arr.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "9223372036854775807"); + + arr.GetInt64(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "-9223372036854775808"); + + arr.GetInt64(2, buffer, sizeof(buffer)); + AssertStrEq(buffer, "0"); + + arr.GetInt64(3, buffer, sizeof(buffer)); + AssertStrEq(buffer, "18446744073709551615"); + + delete arr; + } + TestEnd(); + + TestStart("Array_FromInt64_LargeValues"); + { + char values[][] = {"1234567890123456", "9876543210987654", "5555555555555555"}; + JSONArray arr = JSONArray.FromInt64(values, sizeof(values)); + + AssertValidHandle(arr); + AssertEq(arr.Length, 3); + + char buffer[32]; + arr.GetInt64(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "9876543210987654"); + + delete arr; + } + TestEnd(); + + // Test FromBool + TestStart("Array_FromBool"); + { + bool values[] = {true, false, true, true, false}; + JSONArray arr = JSONArray.FromBool(values, sizeof(values)); + + AssertValidHandle(arr); + AssertEq(arr.Length, 5); + AssertTrue(arr.GetBool(0)); + AssertFalse(arr.GetBool(1)); + AssertTrue(arr.GetBool(2)); + AssertTrue(arr.GetBool(3)); + AssertFalse(arr.GetBool(4)); + + delete arr; + } + TestEnd(); + + TestStart("Array_FromBool_AllTrue"); + { + bool values[] = {true, true, true}; + JSONArray arr = JSONArray.FromBool(values, sizeof(values)); + + AssertValidHandle(arr); + AssertEq(arr.Length, 3); + AssertTrue(arr.GetBool(0)); + AssertTrue(arr.GetBool(1)); + AssertTrue(arr.GetBool(2)); + + delete arr; + } + TestEnd(); + + // Test FromFloat + TestStart("Array_FromFloat"); + { + float values[] = {1.1, 2.2, 3.3, 4.4, 5.5}; + JSONArray arr = JSONArray.FromFloat(values, sizeof(values)); + + AssertValidHandle(arr); + AssertEq(arr.Length, 5); + AssertFloatEq(arr.GetFloat(0), 1.1); + AssertFloatEq(arr.GetFloat(2), 3.3, "", 0.01); + AssertFloatEq(arr.GetFloat(4), 5.5); + + delete arr; + } + TestEnd(); + + TestStart("Array_FromFloat_Negative"); + { + float values[] = {-3.14, 0.0, 2.718, -1.414}; + JSONArray arr = JSONArray.FromFloat(values, sizeof(values)); + + AssertValidHandle(arr); + AssertEq(arr.Length, 4); + AssertFloatEq(arr.GetFloat(0), -3.14); + AssertFloatEq(arr.GetFloat(1), 0.0); + AssertFloatEq(arr.GetFloat(2), 2.718); + + delete arr; + } + TestEnd(); + + // Test Insert methods + TestStart("Array_InsertInt"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(3); + arr.PushInt(4); + + AssertTrue(arr.InsertInt(1, 2)); + AssertEq(arr.Length, 4); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(1), 2); + AssertEq(arr.GetInt(2), 3); + AssertEq(arr.GetInt(3), 4); + + delete arr; + } + TestEnd(); + + TestStart("Array_InsertBool"); + { + JSONArray arr = new JSONArray(); + arr.PushBool(true); + arr.PushBool(true); + + AssertTrue(arr.InsertBool(1, false)); + AssertEq(arr.Length, 3); + AssertTrue(arr.GetBool(0)); + AssertFalse(arr.GetBool(1)); + AssertTrue(arr.GetBool(2)); + + delete arr; + } + TestEnd(); + + TestStart("Array_InsertFloat"); + { + JSONArray arr = new JSONArray(); + arr.PushFloat(1.1); + arr.PushFloat(3.3); + + AssertTrue(arr.InsertFloat(1, 2.2)); + AssertEq(arr.Length, 3); + AssertFloatEq(arr.GetFloat(0), 1.1); + AssertFloatEq(arr.GetFloat(1), 2.2); + AssertFloatEq(arr.GetFloat(2), 3.3, "", 0.01); + + delete arr; + } + TestEnd(); + + TestStart("Array_InsertString"); + { + JSONArray arr = new JSONArray(); + arr.PushString("first"); + arr.PushString("third"); + + AssertTrue(arr.InsertString(1, "second")); + AssertEq(arr.Length, 3); + + char buffer[64]; + arr.GetString(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "first"); + + arr.GetString(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "second"); + + arr.GetString(2, buffer, sizeof(buffer)); + AssertStrEq(buffer, "third"); + + delete arr; + } + TestEnd(); + + TestStart("Array_InsertInt64"); + { + JSONArray arr = new JSONArray(); + arr.PushInt64("1111111111111111"); + arr.PushInt64("3333333333333333"); + + AssertTrue(arr.InsertInt64(1, "2222222222222222")); + AssertEq(arr.Length, 3); + + char buffer[32]; + arr.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "1111111111111111"); + + arr.GetInt64(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "2222222222222222"); + + arr.GetInt64(2, buffer, sizeof(buffer)); + AssertStrEq(buffer, "3333333333333333"); + + delete arr; + } + TestEnd(); + + TestStart("Array_InsertNull"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(2); + + AssertTrue(arr.InsertNull(1)); + AssertEq(arr.Length, 3); + AssertEq(arr.GetInt(0), 1); + AssertTrue(arr.IsNull(1)); + AssertEq(arr.GetInt(2), 2); + + delete arr; + } + TestEnd(); + + TestStart("Array_InsertAtBeginning"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(2); + arr.PushInt(3); + + AssertTrue(arr.InsertInt(0, 1)); + AssertEq(arr.Length, 3); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(1), 2); + AssertEq(arr.GetInt(2), 3); + + delete arr; + } + TestEnd(); + + TestStart("Array_InsertAtEnd"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(2); + + AssertTrue(arr.InsertInt(2, 3)); + AssertEq(arr.Length, 3); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(1), 2); + AssertEq(arr.GetInt(2), 3); + + delete arr; + } + TestEnd(); + + TestStart("Array_InsertHandle"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(3); + + JSON val = JSON.CreateInt(2); + AssertTrue(arr.Insert(1, val)); + + AssertEq(arr.Length, 3); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(1), 2); + AssertEq(arr.GetInt(2), 3); + + delete val; + delete arr; + } + TestEnd(); + + // Test Prepend methods + TestStart("Array_PrependInt"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(2); + arr.PushInt(3); + + AssertTrue(arr.PrependInt(1)); + AssertEq(arr.Length, 3); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(1), 2); + AssertEq(arr.GetInt(2), 3); + + delete arr; + } + TestEnd(); + + TestStart("Array_PrependBool"); + { + JSONArray arr = new JSONArray(); + arr.PushBool(false); + arr.PushBool(false); + + AssertTrue(arr.PrependBool(true)); + AssertEq(arr.Length, 3); + AssertTrue(arr.GetBool(0)); + AssertFalse(arr.GetBool(1)); + AssertFalse(arr.GetBool(2)); + + delete arr; + } + TestEnd(); + + TestStart("Array_PrependFloat"); + { + JSONArray arr = new JSONArray(); + arr.PushFloat(2.2); + arr.PushFloat(3.3); + + AssertTrue(arr.PrependFloat(1.1)); + AssertEq(arr.Length, 3); + AssertFloatEq(arr.GetFloat(0), 1.1); + AssertFloatEq(arr.GetFloat(1), 2.2); + + delete arr; + } + TestEnd(); + + TestStart("Array_PrependString"); + { + JSONArray arr = new JSONArray(); + arr.PushString("second"); + arr.PushString("third"); + + AssertTrue(arr.PrependString("first")); + AssertEq(arr.Length, 3); + + char buffer[64]; + arr.GetString(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "first"); + + arr.GetString(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "second"); + + delete arr; + } + TestEnd(); + + TestStart("Array_PrependInt64"); + { + JSONArray arr = new JSONArray(); + arr.PushInt64("2222222222222222"); + arr.PushInt64("3333333333333333"); + + AssertTrue(arr.PrependInt64("1111111111111111")); + AssertEq(arr.Length, 3); + + char buffer[32]; + arr.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "1111111111111111"); + + arr.GetInt64(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "2222222222222222"); + + delete arr; + } + TestEnd(); + + TestStart("Array_PrependNull"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(2); + + AssertTrue(arr.PrependNull()); + AssertEq(arr.Length, 3); + AssertTrue(arr.IsNull(0)); + AssertEq(arr.GetInt(1), 1); + AssertEq(arr.GetInt(2), 2); + + delete arr; + } + TestEnd(); + + TestStart("Array_PrependHandle"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(2); + arr.PushInt(3); + + JSON val = JSON.CreateInt(1); + AssertTrue(arr.Prepend(val)); + + AssertEq(arr.Length, 3); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(1), 2); + + delete val; + delete arr; + } + TestEnd(); + + TestStart("Array_PrependToEmpty"); + { + JSONArray arr = new JSONArray(); + + AssertTrue(arr.PrependInt(1)); + AssertEq(arr.Length, 1); + AssertEq(arr.GetInt(0), 1); + + delete arr; + } + TestEnd(); + + // Test combined Insert and Prepend + TestStart("Array_CombinedInsertPrepend"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(5); + arr.PrependInt(1); + arr.InsertInt(1, 3); + arr.InsertInt(1, 2); + arr.InsertInt(3, 4); + + AssertEq(arr.Length, 5); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(1), 2); + AssertEq(arr.GetInt(2), 3); + AssertEq(arr.GetInt(3), 4); + AssertEq(arr.GetInt(4), 5); + + delete arr; + } + TestEnd(); +} + +// ============================================================================ +// 2.4 Parse & Serialize Tests +// ============================================================================ + +void Test_ParseAndSerialize() +{ + PrintToServer("\n[Category] Parse & Serialize Tests"); + + // Test Parse string + TestStart("Parse_SimpleObject"); + { + JSON json = JSON.Parse("{\"key\":\"value\",\"num\":42}"); + AssertValidHandle(json); + AssertTrue(json.IsObject); + delete json; + } + TestEnd(); + + TestStart("Parse_SimpleArray"); + { + JSON json = JSON.Parse("[1,2,3,4,5]"); + AssertValidHandle(json); + AssertTrue(json.IsArray); + delete json; + } + TestEnd(); + + TestStart("Parse_NestedStructure"); + { + JSON json = JSON.Parse("{\"user\":{\"name\":\"test\",\"age\":25},\"items\":[1,2,3]}"); + AssertValidHandle(json); + delete json; + } + TestEnd(); + + // Test mutable/immutable + TestStart("Parse_ImmutableDocument"); + { + JSON json = JSON.Parse("{\"key\":\"value\"}"); + AssertValidHandle(json); + AssertTrue(json.IsImmutable); + AssertFalse(json.IsMutable); + delete json; + } + TestEnd(); + + TestStart("Parse_MutableDocument"); + { + JSON json = JSON.Parse("{\"key\":\"value\"}", .is_mutable_doc = true); + AssertValidHandle(json); + AssertTrue(json.IsMutable); + AssertFalse(json.IsImmutable); + delete json; + } + TestEnd(); + + // Test ToString + TestStart("Serialize_ToString"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("num", 42); + obj.SetString("str", "test"); + + char buffer[256]; + int len = obj.ToString(buffer, sizeof(buffer)); + AssertTrue(len > 0); + Assert(StrContains(buffer, "num") != -1); + Assert(StrContains(buffer, "test") != -1); + + delete obj; + } + TestEnd(); + + TestStart("Serialize_ToString_Pretty"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("a", 1); + obj.SetInt("b", 2); + + char buffer[256]; + int len = obj.ToString(buffer, sizeof(buffer), JSON_WRITE_PRETTY); + AssertTrue(len > 0); + Assert(StrContains(buffer, "\n") != -1, "Pretty output should contain newlines"); + + delete obj; + } + TestEnd(); + + // Test GetSerializedSize + TestStart("Serialize_GetSerializedSize"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("test", 123); + + int size = obj.GetSerializedSize(); + AssertTrue(size > 0); + + char[] buffer = new char[size]; + int written = obj.ToString(buffer, size); + AssertEq(written, size); + + delete obj; + } + TestEnd(); + + // Test read flags + TestStart("Parse_WithTrailingCommas"); + { + JSON json = JSON.Parse("[1,2,3,]", .flag = JSON_READ_ALLOW_TRAILING_COMMAS); + AssertValidHandle(json); + delete json; + } + TestEnd(); + + TestStart("Parse_WithComments"); + { + JSON json = JSON.Parse("/* comment */ {\"key\":\"value\"}", .flag = JSON_READ_ALLOW_COMMENTS); + AssertValidHandle(json); + delete json; + } + TestEnd(); + + // Test file operations (create temporary test file) + TestStart("Parse_ToFile_FromFile"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("filetest", 999); + obj.SetString("name", "testfile"); + + // Write to file + AssertTrue(obj.ToFile("json_test_temp.json")); + + // Read from file + JSONObject loaded = JSON.Parse("json_test_temp.json", true); + AssertValidHandle(loaded); + + // Verify content + JSONObject loadedObj = loaded; + AssertEq(loadedObj.GetInt("filetest"), 999); + + char buffer[64]; + loadedObj.GetString("name", buffer, sizeof(buffer)); + AssertStrEq(buffer, "testfile"); + + delete obj; + delete loaded; + + // Cleanup + DeleteFile("json_test_temp.json"); + } + TestEnd(); + + // Test round-trip serialization + TestStart("Parse_RoundTrip"); + { + char original[] = "{\"int\":42,\"float\":3.14,\"bool\":true,\"str\":\"test\",\"null\":null}"; + JSON json1 = JSON.Parse(original); + + char buffer[256]; + json1.ToString(buffer, sizeof(buffer)); + + JSON json2 = JSON.Parse(buffer); + AssertTrue(JSON.Equals(json1, json2)); + + delete json1; + delete json2; + } + TestEnd(); +} + +// ============================================================================ +// 2.5 Iterator Tests +// ============================================================================ + +void Test_Iterators() +{ + PrintToServer("\n[Category] Iterator Tests"); + + // Test ForeachObject + TestStart("Iterator_ForeachObject"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("a", 1); + obj.SetInt("b", 2); + obj.SetInt("c", 3); + + int count = 0; + char key[32]; + JSONObjIter iter = new JSONObjIter(obj); + + while (iter.Next(key, sizeof(key))) + { + count++; + JSON value = iter.Value; + AssertValidHandle(value); + delete value; + } + AssertFalse(iter.HasNext); + AssertEq(count, 3); + + AssertTrue(iter.Reset()); + + count = 0; + while (iter.Next(key, sizeof(key))) + { + count++; + JSON value = iter.Value; + AssertValidHandle(value); + delete value; + } + AssertEq(count, 3); + delete iter; + delete obj; + } + TestEnd(); + + // Test ForeachArray + TestStart("Iterator_ForeachArray"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(10); + arr.PushInt(20); + arr.PushInt(30); + + int count = 0; + JSONArrIter iter = new JSONArrIter(arr); + + while (iter.HasNext) + { + JSON value = iter.Next; + AssertValidHandle(value); + AssertEq(iter.Index, count); + delete value; + count++; + } + AssertFalse(iter.HasNext); + AssertEq(count, 3); + + AssertTrue(iter.Reset()); + + count = 0; + while (iter.HasNext) + { + JSON value = iter.Next; + AssertValidHandle(value); + AssertEq(iter.Index, count); + delete value; + count++; + } + AssertEq(count, 3); + delete iter; + delete arr; + } + TestEnd(); + + // Test ForeachKey + TestStart("Iterator_ForeachKey"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("key1", 1); + obj.SetInt("key2", 2); + obj.SetInt("key3", 3); + + int count = 0; + char key[32]; + JSONObjIter iter = new JSONObjIter(obj); + + while (iter.Next(key, sizeof(key))) + { + AssertTrue(strlen(key) > 0); + count++; + } + AssertFalse(iter.HasNext); + AssertEq(count, 3); + + AssertTrue(iter.Reset()); + + count = 0; + while (iter.Next(key, sizeof(key))) + { + AssertTrue(strlen(key) > 0); + count++; + } + AssertEq(count, 3); + delete iter; + delete obj; + } + TestEnd(); + + // Test ForeachIndex + TestStart("Iterator_ForeachIndex"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + arr.PushInt(2); + arr.PushInt(3); + + int count = 0; + JSONArrIter iter = new JSONArrIter(arr); + + while (iter.HasNext) + { + JSON value = iter.Next; + AssertEq(iter.Index, count); + delete value; + count++; + } + AssertFalse(iter.HasNext); + AssertEq(count, 3); + + AssertTrue(iter.Reset()); + + count = 0; + while (iter.HasNext) + { + JSON value = iter.Next; + AssertEq(iter.Index, count); + delete value; + count++; + } + AssertEq(count, 3); + delete iter; + delete arr; + } + TestEnd(); + + // Test empty iterations + TestStart("Iterator_EmptyObject"); + { + JSONObject obj = new JSONObject(); + char key[32]; + JSONObjIter iter = new JSONObjIter(obj); + AssertFalse(iter.Next(key, sizeof(key))); + AssertTrue(iter.Reset()); + AssertFalse(iter.Next(key, sizeof(key))); + delete iter; + + delete obj; + } + TestEnd(); + + TestStart("Iterator_EmptyArray"); + { + JSONArray arr = new JSONArray(); + JSONArrIter iter = new JSONArrIter(arr); + AssertFalse(iter.HasNext); + AssertTrue(iter.Reset()); + AssertFalse(iter.HasNext); + delete iter; + + delete arr; + } + TestEnd(); +} + +// ============================================================================ +// 2.6 JSON Pointer Tests +// ============================================================================ + +void Test_JSONPointer() +{ + PrintToServer("\n[Category] JSON Pointer Tests"); + + // Test PtrSet methods + TestStart("Pointer_PtrSetInt"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.PtrSetInt("/number", 42)); + AssertEq(obj.PtrGetInt("/number"), 42); + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrSetFloat"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.PtrSetFloat("/pi", 3.14159)); + AssertFloatEq(obj.PtrGetFloat("/pi"), 3.14159); + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrSetBool"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.PtrSetBool("/flag", true)); + AssertTrue(obj.PtrGetBool("/flag")); + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrSetString"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.PtrSetString("/name", "test")); + + char buffer[64]; + AssertTrue(obj.PtrGetString("/name", buffer, sizeof(buffer))); + AssertStrEq(buffer, "test"); + + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrSetInt64"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.PtrSetInt64("/bignum", "9223372036854775807")); + + char buffer[32]; + AssertTrue(obj.PtrGetInt64("/bignum", buffer, sizeof(buffer))); + AssertStrEq(buffer, "9223372036854775807"); + + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrSetNull"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.PtrSetNull("/nullable")); + AssertTrue(obj.PtrGetIsNull("/nullable")); + delete obj; + } + TestEnd(); + + // Test nested path creation + TestStart("Pointer_NestedPathCreation"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.PtrSetInt("/a/b/c/d", 123)); + AssertEq(obj.PtrGetInt("/a/b/c/d"), 123); + delete obj; + } + TestEnd(); + + // Test PtrGet with handle + TestStart("Pointer_PtrGet"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt("/test", 999); + + JSON val = obj.PtrGet("/test"); + AssertValidHandle(val); + AssertEq(val.GetInt(), 999); + + delete val; + delete obj; + } + TestEnd(); + + // Test PtrAdd methods + TestStart("Pointer_PtrAddInt"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt("/arr/0", 1); + AssertTrue(obj.PtrAddInt("/arr/1", 2)); + AssertEq(obj.PtrGetInt("/arr/1"), 2); + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrAddString"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetString("/items/0", "first"); + AssertTrue(obj.PtrAddString("/items/1", "second")); + + char buffer[64]; + obj.PtrGetString("/items/1", buffer, sizeof(buffer)); + AssertStrEq(buffer, "second"); + + delete obj; + } + TestEnd(); + + // Test PtrRemove + TestStart("Pointer_PtrRemove"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt("/remove_me", 1); + obj.PtrSetInt("/keep_me", 2); + + AssertTrue(obj.PtrRemove("/remove_me")); + + JSON val; + obj.PtrTryGetVal("/remove_me", val); + AssertNullHandle(val); + + delete obj; + } + TestEnd(); + + // Test PtrGetLength + TestStart("Pointer_PtrGetLength"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetString("/text", "hello"); + + int len = obj.PtrGetLength("/text"); + AssertEq(len, 6); // Including null terminator + + delete obj; + } + TestEnd(); + + // Test PtrTryGet methods + TestStart("Pointer_PtrTryGetInt"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt("/value", 42); + + int value; + AssertTrue(obj.PtrTryGetInt("/value", value)); + AssertEq(value, 42); + + AssertFalse(obj.PtrTryGetInt("/nonexistent", value)); + + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrTryGetBool"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetBool("/flag", true); + + bool value; + AssertTrue(obj.PtrTryGetBool("/flag", value)); + AssertTrue(value); + + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrTryGetFloat"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetFloat("/pi", 3.14); + + float value; + AssertTrue(obj.PtrTryGetFloat("/pi", value)); + AssertFloatEq(value, 3.14); + + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrTryGetString"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetString("/name", "test"); + + char buffer[64]; + AssertTrue(obj.PtrTryGetString("/name", buffer, sizeof(buffer))); + AssertStrEq(buffer, "test"); + + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrTryGetInt64"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt64("/bignum", "123456789012345"); + + char buffer[32]; + AssertTrue(obj.PtrTryGetInt64("/bignum", buffer, sizeof(buffer))); + AssertStrEq(buffer, "123456789012345"); + + delete obj; + } + TestEnd(); + + TestStart("Pointer_PtrTryGetVal"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt("/test", 42); + + JSON value; + AssertTrue(obj.PtrTryGetVal("/test", value)); + AssertValidHandle(value); + AssertEq(value.GetInt(), 42); + + delete value; + delete obj; + } + TestEnd(); +} + +// ============================================================================ +// 2.7 Advanced Features Tests +// ============================================================================ + +void Test_AdvancedFeatures() +{ + PrintToServer("\n[Category] Advanced Features Tests"); + + // Test DeepCopy + TestStart("Advanced_DeepCopy_Object"); + { + JSONObject original = new JSONObject(); + original.SetInt("num", 42); + original.SetString("str", "test"); + + JSONObject target = new JSONObject(); + JSONObject copy = JSON.DeepCopy(target, original); + + AssertValidHandle(copy); + AssertEq(copy.GetInt("num"), 42); + + char buffer[64]; + copy.GetString("str", buffer, sizeof(buffer)); + AssertStrEq(buffer, "test"); + + delete original; + delete target; + delete copy; + } + TestEnd(); + + TestStart("Advanced_DeepCopy_Array"); + { + JSONArray original = new JSONArray(); + original.PushInt(1); + original.PushInt(2); + original.PushInt(3); + + JSONArray target = new JSONArray(); + JSONArray copy = JSON.DeepCopy(target, original); + + AssertValidHandle(copy); + AssertEq(copy.Length, 3); + AssertEq(copy.GetInt(0), 1); + AssertEq(copy.GetInt(2), 3); + + delete original; + delete target; + delete copy; + } + TestEnd(); + + // Test Equals + TestStart("Advanced_Equals_True"); + { + JSONObject obj1 = new JSONObject(); + obj1.SetInt("a", 1); + obj1.SetString("b", "test"); + + JSONObject obj2 = new JSONObject(); + obj2.SetInt("a", 1); + obj2.SetString("b", "test"); + + AssertTrue(JSON.Equals(obj1, obj2)); + + delete obj1; + delete obj2; + } + TestEnd(); + + TestStart("Advanced_Equals_False"); + { + JSONObject obj1 = new JSONObject(); + obj1.SetInt("a", 1); + + JSONObject obj2 = new JSONObject(); + obj2.SetInt("a", 2); + + AssertFalse(JSON.Equals(obj1, obj2)); + + delete obj1; + delete obj2; + } + TestEnd(); + + // Test ToMutable/ToImmutable + TestStart("Advanced_ToMutable"); + { + JSON immutable = JSON.Parse("{\"key\":\"value\"}"); + AssertTrue(immutable.IsImmutable); + + JSON mutable = immutable.ToMutable(); + AssertValidHandle(mutable); + AssertTrue(mutable.IsMutable); + + delete immutable; + delete mutable; + } + TestEnd(); + + TestStart("Advanced_ToImmutable"); + { + JSONObject mutable = new JSONObject(); + mutable.SetInt("key", 42); + AssertTrue(mutable.IsMutable); + + JSON immutable = mutable.ToImmutable(); + AssertValidHandle(immutable); + AssertTrue(immutable.IsImmutable); + + delete mutable; + delete immutable; + } + TestEnd(); + + // Test ApplyJsonPatch (new value, immutable result) + TestStart("Advanced_ApplyJsonPatch"); + { + JSONObject original = new JSONObject(); + original.SetInt("score", 10); + original.SetString("name", "bot"); + + JSON patch = JSON.Parse("[{\"op\":\"replace\",\"path\":\"/score\",\"value\":42}]"); + AssertValidHandle(patch); + + JSON result = original.ApplyJsonPatch(patch); + AssertValidHandle(result); + AssertTrue(result.IsImmutable); + + AssertEq(result.PtrGetInt("/score"), 42); + char buffer[32]; + result.PtrGetString("/name", buffer, sizeof(buffer)); + AssertStrEq(buffer, "bot"); + + // ensure original unchanged + AssertEq(original.GetInt("score"), 10); + + delete result; + delete patch; + delete original; + } + TestEnd(); + + // Test ApplyJsonPatch resultMutable = true + TestStart("Advanced_ApplyJsonPatch_MutableResult"); + { + JSONObject original = new JSONObject(); + original.SetInt("count", 1); + + JSON patch = JSON.Parse("[{\"op\":\"add\",\"path\":\"/newField\",\"value\":\"hello\"}]"); + AssertValidHandle(patch); + + JSON result = original.ApplyJsonPatch(patch, true); + AssertValidHandle(result); + AssertTrue(result.IsMutable); + + char buffer[16]; + result.PtrGetString("/newField", buffer, sizeof(buffer)); + AssertStrEq(buffer, "hello"); + + delete result; + delete patch; + delete original; + } + TestEnd(); + + // Test JsonPatchInPlace + TestStart("Advanced_JsonPatchInPlace"); + { + JSONObject target = new JSONObject(); + target.SetInt("score", 5); + target.SetInt("lives", 3); + + JSON patch = JSON.Parse("[{\"op\":\"remove\",\"path\":\"/lives\"},{\"op\":\"replace\",\"path\":\"/score\",\"value\":9}]"); + AssertValidHandle(patch); + + AssertTrue(target.JsonPatchInPlace(patch)); + AssertEq(target.GetInt("score"), 9); + AssertFalse(target.HasKey("lives")); + + delete patch; + delete target; + } + TestEnd(); + + // Test ApplyMergePatch (immutable result) + TestStart("Advanced_ApplyMergePatch"); + { + JSONObject original = new JSONObject(); + original.PtrSetString("/settings/mode", "coop"); + original.PtrSetInt("/settings/difficulty", 1); + + JSON mergePatch = JSON.Parse("{\"settings\":{\"difficulty\":3,\"friendlyFire\":true}}"); + AssertValidHandle(mergePatch); + + JSON result = original.ApplyMergePatch(mergePatch); + AssertValidHandle(result); + AssertTrue(result.IsImmutable); + + AssertEq(result.PtrGetInt("/settings/difficulty"), 3); + AssertTrue(result.PtrGetBool("/settings/friendlyFire")); + + delete result; + delete mergePatch; + delete original; + } + TestEnd(); + + // Test ApplyMergePatch resultMutable = true + TestStart("Advanced_ApplyMergePatch_MutableResult"); + { + JSONObject original = new JSONObject(); + original.PtrSetString("/profile/name", "player"); + + JSON mergePatch = JSON.Parse("{\"profile\":{\"rank\":10}}"); + AssertValidHandle(mergePatch); + + JSON result = original.ApplyMergePatch(mergePatch, true); + AssertValidHandle(result); + AssertTrue(result.IsMutable); + + AssertEq(result.PtrGetInt("/profile/rank"), 10); + char buffer[16]; + result.PtrGetString("/profile/name", buffer, sizeof(buffer)); + AssertStrEq(buffer, "player"); + + delete result; + delete mergePatch; + delete original; + } + TestEnd(); + + // Test MergePatchInPlace + TestStart("Advanced_MergePatchInPlace"); + { + JSONObject target = new JSONObject(); + target.PtrSetString("/config/mode", "coop"); + target.PtrSetInt("/config/players", 4); + + JSON mergePatch = JSON.Parse("{\"config\":{\"players\":6,\"region\":\"EU\"}}"); + AssertValidHandle(mergePatch); + + AssertTrue(target.MergePatchInPlace(mergePatch)); + AssertEq(target.PtrGetInt("/config/players"), 6); + char buffer[16]; + target.PtrGetString("/config/region", buffer, sizeof(buffer)); + AssertStrEq(buffer, "EU"); + + delete mergePatch; + delete target; + } + TestEnd(); + + // Test Pack + TestStart("Advanced_Pack_SimpleObject"); + { + JSONObject json = JSON.Pack("{s:i,s:s,s:b}", + "num", 42, + "str", "test", + "bool", true + ); + + AssertValidHandle(json); + AssertTrue(json.IsObject); + + JSONObject obj = json; + AssertEq(obj.GetInt("num"), 42); + + char buffer[64]; + obj.GetString("str", buffer, sizeof(buffer)); + AssertStrEq(buffer, "test"); + + AssertTrue(obj.GetBool("bool")); + + delete json; + } + TestEnd(); + + TestStart("Advanced_Pack_Array"); + { + JSONArray json = JSON.Pack("[i,i,i]", 1, 2, 3); + + AssertValidHandle(json); + AssertTrue(json.IsArray); + + JSONArray arr = json; + AssertEq(arr.Length, 3); + AssertEq(arr.GetInt(0), 1); + AssertEq(arr.GetInt(2), 3); + + delete json; + } + TestEnd(); + + TestStart("Advanced_Pack_Nested"); + { + JSONObject json = JSON.Pack("{s:{s:s,s:i}}", + "user", + "name", "test", + "age", 25 + ); + + AssertValidHandle(json); + + char buffer[64]; + JSONObject obj = json; + obj.PtrGetString("/user/name", buffer, sizeof(buffer)); + AssertStrEq(buffer, "test"); + AssertEq(obj.PtrGetInt("/user/age"), 25); + + delete json; + } + TestEnd(); + + // Test mixed type array sorting + TestStart("Advanced_MixedTypeSort"); + { + JSONArray json = JSON.Parse("[true, 42, \"hello\", 1.5, false]", .is_mutable_doc = true); + JSONArray arr = json; + + AssertTrue(arr.Sort(JSON_SORT_ASC)); + AssertEq(arr.Length, 5); + + delete json; + } + TestEnd(); +} + +// ============================================================================ +// 2.8 Int64 Operations Tests +// ============================================================================ + +void Test_Int64Operations() +{ + PrintToServer("\n[Category] Int64 Operations Tests"); + + // Test JSON.SetInt64 - Modify value in-place + TestStart("Int64_SetInt64_Positive"); + { + JSON val = JSON.CreateInt(100); + AssertTrue(val.SetInt64("123456789012345")); + + char buffer[32]; + AssertTrue(val.GetInt64(buffer, sizeof(buffer))); + AssertStrEq(buffer, "123456789012345"); + + delete val; + } + TestEnd(); + + TestStart("Int64_SetInt64_Negative"); + { + JSON val = JSON.CreateInt(100); + AssertTrue(val.SetInt64("-987654321098765")); + + char buffer[32]; + AssertTrue(val.GetInt64(buffer, sizeof(buffer))); + AssertStrEq(buffer, "-987654321098765"); + + delete val; + } + TestEnd(); + + TestStart("Int64_SetInt64_Zero"); + { + JSON val = JSON.CreateInt(999); + AssertTrue(val.SetInt64("0")); + + char buffer[32]; + AssertTrue(val.GetInt64(buffer, sizeof(buffer))); + AssertStrEq(buffer, "0"); + + delete val; + } + TestEnd(); + + // Test JSONArray.SetInt64 - Replace value in array + TestStart("Int64_Array_SetInt64"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(100); + arr.PushInt(200); + arr.PushInt(300); + + AssertTrue(arr.SetInt64(1, "5555555555555555")); + + char buffer[32]; + arr.GetInt64(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "5555555555555555"); + + delete arr; + } + TestEnd(); + + TestStart("Int64_Array_SetInt64_Negative"); + { + JSONArray arr = new JSONArray(); + arr.PushInt64("1111111111111111"); + + AssertTrue(arr.SetInt64(0, "-2222222222222222")); + + char buffer[32]; + arr.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "-2222222222222222"); + + delete arr; + } + TestEnd(); + + // Test large unsigned int64 values + TestStart("Int64_Array_PushLargeUnsigned"); + { + JSONArray arr = new JSONArray(); + AssertTrue(arr.PushInt64("18446744073709551615")); + + char buffer[32]; + arr.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "18446744073709551615"); + + delete arr; + } + TestEnd(); + + TestStart("Int64_Array_IndexOfLargeValues"); + { + JSONArray arr = new JSONArray(); + arr.PushInt64("11111111111111111"); + arr.PushInt64("22222222222222222"); + arr.PushInt64("33333333333333333"); + + AssertEq(arr.IndexOfInt64("22222222222222222"), 1); + AssertEq(arr.IndexOfInt64("99999999999999999"), -1); + + delete arr; + } + TestEnd(); + + // Test PtrAddInt64 + TestStart("Int64_Pointer_PtrAddInt64"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt("/numbers/0", 1); + AssertTrue(obj.PtrAddInt64("/numbers/1", "7777777777777777")); + + char buffer[32]; + AssertTrue(obj.PtrGetInt64("/numbers/1", buffer, sizeof(buffer))); + AssertStrEq(buffer, "7777777777777777"); + + delete obj; + } + TestEnd(); + + TestStart("Int64_Pointer_PtrAddInt64_Negative"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt("/data/0", 1); + AssertTrue(obj.PtrAddInt64("/data/1", "-8888888888888888")); + + char buffer[32]; + AssertTrue(obj.PtrGetInt64("/data/1", buffer, sizeof(buffer))); + AssertStrEq(buffer, "-8888888888888888"); + + delete obj; + } + TestEnd(); + + // Test PtrAddInt64 with large unsigned value + TestStart("Int64_Pointer_PtrAddInt64_LargeUnsigned"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt("/bigvals/0", 1); + AssertTrue(obj.PtrAddInt64("/bigvals/1", "18446744073709551615")); + + char buffer[32]; + AssertTrue(obj.PtrGetInt64("/bigvals/1", buffer, sizeof(buffer))); + AssertStrEq(buffer, "18446744073709551615"); + + delete obj; + } + TestEnd(); + + // Test mixed signed/unsigned values + TestStart("Int64_Mixed_SignedUnsigned_Array"); + { + JSONArray arr = new JSONArray(); + arr.PushInt64("-9223372036854775808"); + arr.PushInt64("18446744073709551615"); + arr.PushInt64("0"); + arr.PushInt64("9223372036854775807"); + + AssertEq(arr.Length, 4); + + char buffer[32]; + arr.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "-9223372036854775808"); + + arr.GetInt64(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "18446744073709551615"); + + arr.GetInt64(2, buffer, sizeof(buffer)); + AssertStrEq(buffer, "0"); + + arr.GetInt64(3, buffer, sizeof(buffer)); + AssertStrEq(buffer, "9223372036854775807"); + + delete arr; + } + TestEnd(); + + TestStart("Int64_Mixed_SignedUnsigned_Object"); + { + JSONObject obj = new JSONObject(); + obj.SetInt64("min_int64", "-9223372036854775808"); + obj.SetInt64("max_int64", "9223372036854775807"); + obj.SetInt64("max_uint64", "18446744073709551615"); + + char buffer[32]; + + AssertTrue(obj.GetInt64("min_int64", buffer, sizeof(buffer))); + AssertStrEq(buffer, "-9223372036854775808"); + + AssertTrue(obj.GetInt64("max_int64", buffer, sizeof(buffer))); + AssertStrEq(buffer, "9223372036854775807"); + + AssertTrue(obj.GetInt64("max_uint64", buffer, sizeof(buffer))); + AssertStrEq(buffer, "18446744073709551615"); + + delete obj; + } + TestEnd(); + + // Test serialization with int64 + TestStart("Int64_Serialization_ToString"); + { + JSONObject obj = new JSONObject(); + obj.SetInt64("large_num", "9007199254740992"); + obj.SetInt64("negative_num", "-9007199254740992"); + + char json[256]; + int len = obj.ToString(json, sizeof(json)); + AssertTrue(len > 0); + + // Parse it back + JSONObject parsed = JSONObject.FromString(json); + AssertValidHandle(parsed); + + char buffer[32]; + AssertTrue(parsed.GetInt64("large_num", buffer, sizeof(buffer))); + AssertStrEq(buffer, "9007199254740992"); + + AssertTrue(parsed.GetInt64("negative_num", buffer, sizeof(buffer))); + AssertStrEq(buffer, "-9007199254740992"); + + delete obj; + delete parsed; + } + TestEnd(); + + TestStart("Int64_Serialization_Array"); + { + JSONArray arr = new JSONArray(); + arr.PushInt64("1234567890123456"); + arr.PushInt64("-9876543210987654"); + arr.PushInt64("18000000000000000000"); + + char json[256]; + int len = arr.ToString(json, sizeof(json)); + AssertTrue(len > 0); + + // Parse it back + JSONArray parsed = JSONArray.FromString(json); + AssertValidHandle(parsed); + AssertEq(parsed.Length, 3); + + char buffer[32]; + parsed.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "1234567890123456"); + + parsed.GetInt64(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "-9876543210987654"); + + parsed.GetInt64(2, buffer, sizeof(buffer)); + AssertStrEq(buffer, "18000000000000000000"); + + delete arr; + delete parsed; + } + TestEnd(); + + // Test boundary values + TestStart("Int64_Boundary_JustAboveInt32Max"); + { + JSON val = JSON.CreateInt64("2147483648"); + AssertValidHandle(val); + + char buffer[32]; + AssertTrue(val.GetInt64(buffer, sizeof(buffer))); + AssertStrEq(buffer, "2147483648"); + + delete val; + } + TestEnd(); + + TestStart("Int64_Boundary_JustBelowInt32Min"); + { + JSON val = JSON.CreateInt64("-2147483649"); + AssertValidHandle(val); + + char buffer[32]; + AssertTrue(val.GetInt64(buffer, sizeof(buffer))); + AssertStrEq(buffer, "-2147483649"); + + delete val; + } + TestEnd(); + + TestStart("Int64_Boundary_UInt32Max"); + { + JSON val = JSON.CreateInt64("4294967295"); + AssertValidHandle(val); + + char buffer[32]; + AssertTrue(val.GetInt64(buffer, sizeof(buffer))); + AssertStrEq(buffer, "4294967295"); + + delete val; + } + TestEnd(); + + TestStart("Int64_Boundary_JustAboveUInt32Max"); + { + JSON val = JSON.CreateInt64("4294967296"); + AssertValidHandle(val); + + char buffer[32]; + AssertTrue(val.GetInt64(buffer, sizeof(buffer))); + AssertStrEq(buffer, "4294967296"); + + delete val; + } + TestEnd(); + + // Test Int64 in nested structures + TestStart("Int64_Nested_ObjectInObject"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt64("/outer/inner/deep", "5432109876543210"); + + char buffer[32]; + AssertTrue(obj.PtrGetInt64("/outer/inner/deep", buffer, sizeof(buffer))); + AssertStrEq(buffer, "5432109876543210"); + + delete obj; + } + TestEnd(); + + TestStart("Int64_Nested_ArrayInObject"); + { + JSONObject obj = new JSONObject(); + obj.PtrSetInt64("/data/values/0", "1111111111111111"); + obj.PtrAddInt64("/data/values/1", "2222222222222222"); + obj.PtrAddInt64("/data/values/2", "3333333333333333"); + + char buffer[32]; + AssertTrue(obj.PtrGetInt64("/data/values/0", buffer, sizeof(buffer))); + AssertStrEq(buffer, "1111111111111111"); + + AssertTrue(obj.PtrGetInt64("/data/values/1", buffer, sizeof(buffer))); + AssertStrEq(buffer, "2222222222222222"); + + AssertTrue(obj.PtrGetInt64("/data/values/2", buffer, sizeof(buffer))); + AssertStrEq(buffer, "3333333333333333"); + + delete obj; + } + TestEnd(); + + // Test DeepCopy with int64 + TestStart("Int64_DeepCopy_Object"); + { + JSONObject original = new JSONObject(); + original.SetInt64("bignum", "9999999999999999"); + original.SetInt64("negative", "-8888888888888888"); + + JSONObject target = new JSONObject(); + JSONObject copy = JSON.DeepCopy(target, original); + + AssertValidHandle(copy); + + char buffer[32]; + AssertTrue(copy.GetInt64("bignum", buffer, sizeof(buffer))); + AssertStrEq(buffer, "9999999999999999"); + + AssertTrue(copy.GetInt64("negative", buffer, sizeof(buffer))); + AssertStrEq(buffer, "-8888888888888888"); + + delete original; + delete target; + delete copy; + } + TestEnd(); + + TestStart("Int64_DeepCopy_Array"); + { + JSONArray original = new JSONArray(); + original.PushInt64("1111111111111111"); + original.PushInt64("-2222222222222222"); + original.PushInt64("18446744073709551615"); + + JSONArray target = new JSONArray(); + JSONArray copy = JSON.DeepCopy(target, original); + + AssertValidHandle(copy); + AssertEq(copy.Length, 3); + + char buffer[32]; + copy.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "1111111111111111"); + + copy.GetInt64(1, buffer, sizeof(buffer)); + AssertStrEq(buffer, "-2222222222222222"); + + copy.GetInt64(2, buffer, sizeof(buffer)); + AssertStrEq(buffer, "18446744073709551615"); + + delete original; + delete target; + delete copy; + } + TestEnd(); + + // Test array sorting with int64 values + TestStart("Int64_Array_Sort_Ascending"); + { + JSONArray arr = new JSONArray(); + arr.PushInt64("9999999999999999"); + arr.PushInt64("1111111111111111"); + arr.PushInt64("5555555555555555"); + arr.PushInt64("3333333333333333"); + + AssertTrue(arr.Sort(JSON_SORT_ASC)); + + char buffer[32]; + arr.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "1111111111111111"); + + arr.GetInt64(3, buffer, sizeof(buffer)); + AssertStrEq(buffer, "9999999999999999"); + + delete arr; + } + TestEnd(); + + TestStart("Int64_Array_Sort_Descending"); + { + JSONArray arr = new JSONArray(); + arr.PushInt64("1111111111111111"); + arr.PushInt64("5555555555555555"); + arr.PushInt64("3333333333333333"); + arr.PushInt64("9999999999999999"); + + AssertTrue(arr.Sort(JSON_SORT_DESC)); + + char buffer[32]; + arr.GetInt64(0, buffer, sizeof(buffer)); + AssertStrEq(buffer, "9999999999999999"); + + arr.GetInt64(3, buffer, sizeof(buffer)); + AssertStrEq(buffer, "1111111111111111"); + + delete arr; + } + TestEnd(); + + // Test int64 with type checks + TestStart("Int64_TypeChecks"); + { + JSON val = JSON.CreateInt64("9223372036854775807"); + + AssertTrue(val.IsNum); + AssertTrue(val.IsInt); + AssertFalse(val.IsFloat); + AssertFalse(val.IsStr); + AssertFalse(val.IsBool); + + delete val; + } + TestEnd(); + + TestStart("Int64_TypeChecks_Signed"); + { + JSON val = JSON.CreateInt64("-9223372036854775808"); + + AssertTrue(val.IsNum); + AssertTrue(val.IsInt); + AssertTrue(val.IsSint); + AssertFalse(val.IsUint); + + delete val; + } + TestEnd(); + + TestStart("Int64_TypeChecks_LargeUnsigned"); + { + JSON val = JSON.CreateInt64("18446744073709551615"); + + AssertTrue(val.IsNum); + AssertTrue(val.IsInt); + // Large unsigned value should be detected as unsigned integer + AssertTrue(val.IsUint); + AssertFalse(val.IsSint); + + delete val; + } + TestEnd(); +} + +// ============================================================================ +// 2.9 Edge Cases & Error Handling Tests +// ============================================================================ + +void Test_EdgeCases() +{ + PrintToServer("\n[Category] Edge Cases & Error Handling Tests"); + + // Test empty containers + TestStart("EdgeCase_EmptyObject"); + { + JSONObject obj = new JSONObject(); + AssertEq(obj.Size, 0); + AssertFalse(obj.HasKey("anything")); + + char buffer[64]; + int len = obj.ToString(buffer, sizeof(buffer)); + AssertTrue(len > 0); + + delete obj; + } + TestEnd(); + + TestStart("EdgeCase_EmptyArray"); + { + JSONArray arr = new JSONArray(); + AssertEq(arr.Length, 0); + delete arr; + } + TestEnd(); + + // Test nonexistent keys/indices + TestStart("EdgeCase_NonexistentKey"); + { + JSONObject obj = new JSONObject(); + obj.SetInt("exists", 1); + + AssertTrue(obj.HasKey("exists")); + AssertFalse(obj.HasKey("notexists")); + + delete obj; + } + TestEnd(); + + TestStart("EdgeCase_ArrayOutOfBounds"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(1); + + // Verify array bounds + AssertEq(arr.Length, 1); + AssertEq(arr.GetInt(0), 1); + + delete arr; + } + TestEnd(); + + // Test very long strings + TestStart("EdgeCase_LongString"); + { + char longStr[1024]; + for (int i = 0; i < sizeof(longStr) - 1; i++) + { + longStr[i] = 'A' + (i % 26); + } + longStr[sizeof(longStr) - 1] = '\0'; + + JSON val = JSON.CreateString(longStr); + AssertValidHandle(val); + + char retrieved[1024]; + AssertTrue(val.GetString(retrieved, sizeof(retrieved))); + AssertStrEq(retrieved, longStr); + + delete val; + } + TestEnd(); + + // Test deep nesting + TestStart("EdgeCase_DeepNesting"); + { + JSONObject obj = new JSONObject(); + + // Create deeply nested structure + obj.PtrSetInt("/a/b/c/d/e/f/g/h/i/j", 42); + AssertEq(obj.PtrGetInt("/a/b/c/d/e/f/g/h/i/j"), 42); + + delete obj; + } + TestEnd(); + + // Test large arrays + TestStart("EdgeCase_LargeArray"); + { + JSONArray arr = new JSONArray(); + + for (int i = 0; i < 1000; i++) + { + arr.PushInt(i); + } + + AssertEq(arr.Length, 1000); + AssertEq(arr.GetInt(0), 0); + AssertEq(arr.GetInt(999), 999); + + delete arr; + } + TestEnd(); + + // Test large objects + TestStart("EdgeCase_LargeObject"); + { + JSONObject obj = new JSONObject(); + + char key[32]; + for (int i = 0; i < 100; i++) + { + FormatEx(key, sizeof(key), "key_%d", i); + obj.SetInt(key, i); + } + + AssertEq(obj.Size, 100); + AssertEq(obj.GetInt("key_0"), 0); + AssertEq(obj.GetInt("key_99"), 99); + + delete obj; + } + TestEnd(); + + // Test int64 boundaries + TestStart("EdgeCase_Int64_MaxValue"); + { + JSON val = JSON.CreateInt64("9223372036854775807"); + AssertValidHandle(val); + + char buffer[32]; + val.GetInt64(buffer, sizeof(buffer)); + AssertStrEq(buffer, "9223372036854775807"); + + delete val; + } + TestEnd(); + + TestStart("EdgeCase_Int64_MinValue"); + { + JSON val = JSON.CreateInt64("-9223372036854775808"); + AssertValidHandle(val); + + char buffer[32]; + val.GetInt64(buffer, sizeof(buffer)); + AssertStrEq(buffer, "-9223372036854775808"); + + delete val; + } + TestEnd(); + + // Test special float values + TestStart("EdgeCase_Float_VerySmall"); + { + JSON val = JSON.CreateFloat(0.000001); + AssertValidHandle(val); + AssertFloatEq(val.GetFloat(), 0.000001); + delete val; + } + TestEnd(); + + TestStart("EdgeCase_Float_VeryLarge"); + { + JSON val = JSON.CreateFloat(999999.999999); + AssertValidHandle(val); + AssertFloatEq(val.GetFloat(), 999999.999999, "", 0.001); + delete val; + } + TestEnd(); + + // Test special characters in strings + TestStart("EdgeCase_SpecialCharacters"); + { + JSON val = JSON.CreateString("Line1\nLine2\tTabbed\"Quoted\""); + AssertValidHandle(val); + + char buffer[128]; + val.GetString(buffer, sizeof(buffer)); + Assert(StrContains(buffer, "Line1") != -1); + + delete val; + } + TestEnd(); + + // Test removing from empty array + TestStart("EdgeCase_RemoveFromEmptyArray"); + { + JSONArray arr = new JSONArray(); + AssertFalse(arr.RemoveFirst()); + AssertFalse(arr.RemoveLast()); + delete arr; + } + TestEnd(); + + // Test clearing already empty containers + TestStart("EdgeCase_ClearEmpty"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.Clear()); + AssertEq(obj.Size, 0); + + JSONArray arr = new JSONArray(); + AssertTrue(arr.Clear()); + AssertEq(arr.Length, 0); + + delete obj; + delete arr; + } + TestEnd(); + + // Test IndexOf on empty array + TestStart("EdgeCase_IndexOfEmpty"); + { + JSONArray arr = new JSONArray(); + AssertEq(arr.IndexOfInt(42), -1); + AssertEq(arr.IndexOfString("test"), -1); + AssertEq(arr.IndexOfBool(true), -1); + delete arr; + } + TestEnd(); + + // Test pointer to nonexistent path + TestStart("EdgeCase_PointerNonexistentPath"); + { + JSONObject obj = new JSONObject(); + + // Note: PtrGet throws exception for nonexistent paths (expected behavior) + // Use PtrTryGet methods for safe access + int intVal; + AssertFalse(obj.PtrTryGetInt("/does/not/exist", intVal)); + + delete obj; + } + TestEnd(); + + // Test sorting empty containers + TestStart("EdgeCase_SortEmpty"); + { + JSONObject obj = new JSONObject(); + AssertTrue(obj.Sort()); + + JSONArray arr = new JSONArray(); + AssertTrue(arr.Sort()); + + delete obj; + delete arr; + } + TestEnd(); + + // Test single element operations + TestStart("EdgeCase_SingleElement"); + { + JSONArray arr = new JSONArray(); + arr.PushInt(42); + + AssertTrue(arr.Sort()); + AssertEq(arr.GetInt(0), 42); + + AssertEq(arr.IndexOfInt(42), 0); + + JSON first = arr.First; + JSON last = arr.Last; + AssertEq(first.GetInt(), last.GetInt()); + + delete first; + delete last; + delete arr; + } + TestEnd(); +} \ No newline at end of file