diff -r 838b096c1d08 -r 21884374edbb src/cx/json.h --- a/src/cx/json.h Sun Dec 15 11:11:16 2024 +0100 +++ b/src/cx/json.h Sun Dec 15 12:19:21 2024 +0100 @@ -46,92 +46,291 @@ extern "C" { #endif + +/** + * The type of the parsed token. + */ enum cx_json_token_type { + /** + * No complete token parsed, yet. + */ CX_JSON_NO_TOKEN, + /** + * The presumed token contains syntactical errors. + */ CX_JSON_TOKEN_ERROR, + /** + * A "begin of array" '[' token. + */ CX_JSON_TOKEN_BEGIN_ARRAY, + /** + * A "begin of object" '{' token. + */ CX_JSON_TOKEN_BEGIN_OBJECT, + /** + * An "end of array" ']' token. + */ CX_JSON_TOKEN_END_ARRAY, + /** + * An "end of object" '}' token. + */ CX_JSON_TOKEN_END_OBJECT, + /** + * A colon ':' token separating names and values. + */ CX_JSON_TOKEN_NAME_SEPARATOR, + /** + * A comma ',' token separating object entries or array elements. + */ CX_JSON_TOKEN_VALUE_SEPARATOR, + /** + * A string token. + */ CX_JSON_TOKEN_STRING, + /** + * A number token that can be represented as integer. + */ CX_JSON_TOKEN_INTEGER, + /** + * A number token that cannot be represented as integer. + */ CX_JSON_TOKEN_NUMBER, + /** + * A literal token. + */ CX_JSON_TOKEN_LITERAL, + /** + * A white-space token. + */ CX_JSON_TOKEN_SPACE }; +/** + * The type of some JSON value. + */ enum cx_json_value_type { + /** + * Reserved. + */ CX_JSON_NOTHING, // this allows us to always return non-NULL values + /** + * A JSON object. + */ CX_JSON_OBJECT, + /** + * A JSON array. + */ CX_JSON_ARRAY, + /** + * A string. + */ CX_JSON_STRING, + /** + * A number that contains an integer. + */ CX_JSON_INTEGER, + /** + * A number, not necessarily an integer. + */ CX_JSON_NUMBER, + /** + * A literal (true, false, null). + */ CX_JSON_LITERAL }; +/** + * JSON literal types. + */ enum cx_json_literal { + /** + * The \c null literal. + */ CX_JSON_NULL, + /** + * The \c true literal. + */ CX_JSON_TRUE, + /** + * The \c false literal. + */ CX_JSON_FALSE }; +/** + * Type alias for the token type enum. + */ typedef enum cx_json_token_type CxJsonTokenType; +/** + * Type alias for the value type enum. + */ typedef enum cx_json_value_type CxJsonValueType; +/** + * Type alias for the JSON parser interface. + */ typedef struct cx_json_s CxJson; + +/** + * Type alias for the token struct. + */ typedef struct cx_json_token_s CxJsonToken; +/** + * Type alias for the JSON value struct. + */ typedef struct cx_json_value_s CxJsonValue; +/** + * Type alias for the JSON array struct. + */ typedef struct cx_json_array_s CxJsonArray; +/** + * Type alias for the JSON object struct. + */ typedef struct cx_json_object_s CxJsonObject; +/** + * Type alias for a JSON string. + */ typedef struct cx_mutstr_s CxJsonString; +/** + * Type alias for a number that can be represented as 64-bit signed integer. + */ typedef int64_t CxJsonInteger; +/** + * Type alias for number that is not an integer. + */ typedef double CxJsonNumber; +/** + * Type alias for a JSON literal. + */ typedef enum cx_json_literal CxJsonLiteral; +/** + * Type alias for a key/value pair in a JSON object. + */ typedef struct cx_json_obj_value_s CxJsonObjValue; - +/** + * JSON array structure. + */ struct cx_json_array_s { + /** + * The array data. + */ CX_ARRAY_DECLARE(CxJsonValue*, array); }; +/** + * JSON object structure. + */ struct cx_json_object_s { + /** + * The key/value entries. + */ CX_ARRAY_DECLARE(CxJsonObjValue, values); }; +/** + * Structure for a key/value entry in a JSON object. + */ struct cx_json_obj_value_s { + /** + * The key a.k.a. name of the value. + */ char *name; + /** + * The value. + */ CxJsonValue *value; }; +/** + * Structure for a JSON value. + */ struct cx_json_value_s { + /** + * The allocator with which the value was allocated. + * + * Required for recursively deallocating memory of objects and arrays. + */ const CxAllocator *allocator; + /** + * The type of this value. + * + * Specifies how the \c value union shall be resolved. + */ CxJsonValueType type; + /** + * The value data. + */ union { + /** + * The array data if type is #CX_JSON_ARRAY. + */ CxJsonArray array; + /** + * The object data if type is #CX_JSON_OBJECT. + */ CxJsonObject object; + /** + * The string data if type is #CX_JSON_STRING. + */ CxJsonString string; + /** + * The integer if type is #CX_JSON_INTEGER. + */ CxJsonInteger integer; + /** + * The number if type is #CX_JSON_NUMBER. + */ CxJsonNumber number; + /** + * The literal type if type is #CX_JSON_LITERAL. + */ CxJsonLiteral literal; } value; }; +/** + * Structure for a parsed token. + */ struct cx_json_token_s { + /** + * The token type. + */ CxJsonTokenType tokentype; + /** + * True, iff the \c content must be passed to cx_strfree(). + */ bool allocated; + /** + * The token text, if any. + * + * This is not necessarily set when the token type already + * uniquely identifies the content. + */ cxmutstr content; }; +/** + * The JSON parser interface. + */ struct cx_json_s { + /** + * The allocator used for produced JSON values. + */ const CxAllocator *allocator; + /** + * The input buffer. + */ CxBuffer buffer; + /** + * Used internally. + * + * Remembers the prefix of the last uncompleted token. + */ CxJsonToken uncompleted; /** @@ -161,7 +360,9 @@ */ CxJsonValue* vbuf_internal[8]; - int error; // TODO: currently unused + /** + * Used internally. + */ bool tokenizer_escape; // TODO: check if it can be replaced with look-behind }; @@ -215,12 +416,36 @@ */ typedef enum cx_json_status CxJsonStatus; +/** + * Initializes the json interface. + * + * @param json the json interface + * @param allocator the allocator that shall be used for the produced values + * @see cxJsonDestroy() + */ cx_attr_nonnull_arg(1) void cxJsonInit(CxJson *json, const CxAllocator *allocator); +/** + * Destroys the json interface. + * + * @param json the json interface + * @see cxJsonInit() + */ cx_attr_nonnull void cxJsonDestroy(CxJson *json); +/** + * Adds more data to the input buffer. + * + * The data will be copied. + * + * @param json the json interface + * @param buf the source buffer + * @param len the length of the source buffer + * @return zero on success, non-zero on internal allocation error + * @see cxJsonFill() + */ cx_attr_nonnull cx_attr_access_r(2, 3) int cxJsonFilln(CxJson *json, const char *buf, size_t len); @@ -228,6 +453,16 @@ #ifdef __cplusplus } // extern "C" +/** + * Adds more data to the input buffer. + * + * The data will be copied. + * + * @param json the json interface + * @param str the string to add to the buffer + * @return zero on success, non-zero on internal allocation error + * @see cxJsonFilln() + */ cx_attr_nonnull static inline int cxJsonFill( CxJson *json, @@ -236,6 +471,16 @@ return cxJsonFilln(json, str.ptr, str.length); } +/** + * Adds more data to the input buffer. + * + * The data will be copied. + * + * @param json the json interface + * @param str the string to add to the buffer + * @return zero on success, non-zero on internal allocation error + * @see cxJsonFilln() + */ cx_attr_nonnull static inline int cxJsonFill( CxJson *json, @@ -244,6 +489,16 @@ return cxJsonFilln(json, str.ptr, str.length); } +/** + * Adds more data to the input buffer. + * + * The data will be copied. + * + * @param json the json interface + * @param str the string to add to the buffer + * @return zero on success, non-zero on internal allocation error + * @see cxJsonFilln() + */ cx_attr_nonnull cx_attr_cstr_arg(2) static inline int cxJsonFill( @@ -255,6 +510,16 @@ extern "C" { #else // __cplusplus +/** + * Adds more data to the input buffer. + * + * The data will be copied. + * + * @param json the json interface + * @param str the string to add to the buffer + * @return zero on success, non-zero on internal allocation error + * @see cxJsonFilln() + */ #define cxJsonFill(json, str) _Generic((str), \ cxstring: cx_json_fill_cxstr, \ cxmutstr: cx_json_fill_mutstr, \ @@ -262,6 +527,16 @@ const char*: cx_json_fill_str) \ (json, str) +/** + * Adds more data to the input buffer. + * + * Internal function - please use the #cxJsonFill() macro. + * + * @param json the json interface + * @param str the string to add to the buffer + * @return zero on success, non-zero on internal allocation error + * @see cxJsonFill() + */ cx_attr_nonnull static inline int cx_json_fill_cxstr( CxJson *json, @@ -270,6 +545,16 @@ return cxJsonFilln(json, str.ptr, str.length); } +/** + * Adds more data to the input buffer. + * + * Internal function - please use the #cxJsonFill() macro. + * + * @param json the json interface + * @param str the string to add to the buffer + * @return zero on success, non-zero on internal allocation error + * @see cxJsonFill() + */ cx_attr_nonnull static inline int cx_json_fill_mutstr( CxJson *json, @@ -278,6 +563,16 @@ return cxJsonFilln(json, str.ptr, str.length); } +/** + * Adds more data to the input buffer. + * + * Internal function - please use the #cxJsonFill() macro. + * + * @param json the json interface + * @param str the string to add to the buffer + * @return zero on success, non-zero on internal allocation error + * @see cxJsonFill() + */ cx_attr_nonnull cx_attr_cstr_arg(2) static inline int cx_json_fill_str( @@ -288,77 +583,216 @@ } #endif +/** + * Recursively deallocates the memory of a JSON value. + * + * \remark The type of each deallocated value will be changed + * to #CX_JSON_NOTHING and values of such type will be skipped + * by the de-allocation. That means, this function protects + * you from double-frees when you are accidentally freeing + * a nested value and then the parent value (or vice versa). + * + * @param value the value + */ void cxJsonValueFree(CxJsonValue *value); +/** + * Tries to obtain the next JSON value. + * + * + * @param json the json interface + * @param value a pointer where the next value shall be stored + * @return a status code + */ cx_attr_nonnull +cx_attr_access_w(2) CxJsonStatus cxJsonNext(CxJson *json, CxJsonValue **value); +/** + * Checks if the specified value is a JSON object. + * + * @param value a pointer to the value + * @return true if the value is a JSON object, false otherwise + */ cx_attr_nonnull static inline bool cxJsonIsObject(const CxJsonValue *value) { return value->type == CX_JSON_OBJECT; } +/** + * Checks if the specified value is a JSON array. + * + * @param value a pointer to the value + * @return true if the value is a JSON array, false otherwise + */ cx_attr_nonnull static inline bool cxJsonIsArray(const CxJsonValue *value) { return value->type == CX_JSON_ARRAY; } +/** + * Checks if the specified value is a string. + * + * @param value a pointer to the value + * @return true if the value is a string, false otherwise + */ cx_attr_nonnull static inline bool cxJsonIsString(const CxJsonValue *value) { return value->type == CX_JSON_STRING; } +/** + * Checks if the specified value is a JSON number. + * + * This function will return true for both floating point and + * integer numbers. + * + * @param value a pointer to the value + * @return true if the value is a JSON number, false otherwise + * @see cxJsonIsInteger() + */ cx_attr_nonnull static inline bool cxJsonIsNumber(const CxJsonValue *value) { return value->type == CX_JSON_NUMBER || value->type == CX_JSON_INTEGER; } +/** + * Checks if the specified value is an integer number. + * + * @param value a pointer to the value + * @return true if the value is an integer number, false otherwise + * @see cxJsonIsNumber() + */ cx_attr_nonnull static inline bool cxJsonIsInteger(const CxJsonValue *value) { return value->type == CX_JSON_INTEGER; } +/** + * Checks if the specified value is a JSON literal. + * + * JSON literals are \c true, \c false, and \c null. + * + * @param value a pointer to the value + * @return true if the value is a JSON literal, false otherwise + * @see cxJsonIsTrue() + * @see cxJsonIsFalse() + * @see cxJsonIsNull() + */ cx_attr_nonnull static inline bool cxJsonIsLiteral(const CxJsonValue *value) { return value->type == CX_JSON_LITERAL; } +/** + * Checks if the specified value is a Boolean literal. + * + * @param value a pointer to the value + * @return true if the value is either \c true or \c false, false otherwise + * @see cxJsonIsTrue() + * @see cxJsonIsFalse() + */ cx_attr_nonnull static inline bool cxJsonIsBool(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal != CX_JSON_NULL; } +/** + * Checks if the specified value is \c true. + * + * \remark Be advised, that this is not the same as + * testing \c !cxJsonIsFalse(v). + * + * @param value a pointer to the value + * @return true if the value is \c true, false otherwise + * @see cxJsonIsBool() + * @see cxJsonIsFalse() + */ cx_attr_nonnull static inline bool cxJsonIsTrue(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_TRUE; } +/** + * Checks if the specified value is \c false. + * + * \remark Be advised, that this is not the same as + * testing \c !cxJsonIsTrue(v). + * + * @param value a pointer to the value + * @return true if the value is \c false, false otherwise + * @see cxJsonIsBool() + * @see cxJsonIsTrue() + */ cx_attr_nonnull static inline bool cxJsonIsFalse(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_FALSE; } +/** + * Checks if the specified value is \c null. + * + * @param value a pointer to the value + * @return true if the value is \c null, false otherwise + * @see cxJsonIsLiteral() + */ cx_attr_nonnull static inline bool cxJsonIsNull(const CxJsonValue *value) { return cxJsonIsLiteral(value) && value->value.literal == CX_JSON_NULL; } +/** + * Obtains a C string from the given JSON value. + * + * If the value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as C string + * @see cxJsonIsString() + */ cx_attr_nonnull cx_attr_returns_nonnull static inline char *cxJsonAsString(const CxJsonValue *value) { return value->value.string.ptr; } +/** + * Obtains a UCX string from the given JSON value. + * + * If the value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as UCX string + * @see cxJsonIsString() + */ cx_attr_nonnull static inline cxstring cxJsonAsCxString(const CxJsonValue *value) { return cx_strcast(value->value.string); } +/** + * Obtains a mutable UCX string from the given JSON value. + * + * If the value is not a string, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as mutable UCX string + * @see cxJsonIsString() + */ cx_attr_nonnull static inline cxmutstr cxJsonAsCxMutStr(const CxJsonValue *value) { return value->value.string; } +/** + * Obtains a double-precision floating point value from the given JSON value. + * + * If the value is not a JSON number, the behavior is undefined. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsNumber() + */ cx_attr_nonnull static inline double cxJsonAsDouble(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { @@ -368,6 +802,18 @@ } } +/** + * Obtains a 64-bit signed integer from the given JSON value. + * + * If the value is not a JSON number, the behavior is undefined. + * If it is a JSON number, but not an integer, the value will be + * converted to an integer, possibly losing precision. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsNumber() + * @see cxJsonIsInteger() + */ cx_attr_nonnull static inline int64_t cxJsonAsInteger(const CxJsonValue *value) { if (value->type == CX_JSON_INTEGER) { @@ -377,23 +823,67 @@ } } +/** + * Obtains a Boolean value from the given JSON value. + * + * If the value is not a JSON literal, the behavior is undefined. + * The \c null literal is interpreted as \c false. + * + * @param value the JSON value + * @return the value represented as double + * @see cxJsonIsLiteral() + */ cx_attr_nonnull static inline bool cxJsonAsBool(const CxJsonValue *value) { return value->value.literal == CX_JSON_TRUE; } +/** + * Returns the size of a JSON array. + * + * If the value is not a JSON array, the behavior is undefined. + * + * @param value the JSON value + * @return the size of the array + * @see cxJsonIsArray() + */ cx_attr_nonnull static inline size_t cxJsonArrSize(const CxJsonValue *value) { return value->value.array.array_size; } +/** + * Returns an element from a JSON array. + * + * If the value is not a JSON array, the behavior is undefined. + * + * This function guarantees to return a value. If the index is + * out of bounds, the returned value will be of type + * #CX_JSON_NOTHING, but never \c NULL. + * + * @param value the JSON value + * @param index the index in the array + * @return the value at the specified index + * @see cxJsonIsArray() + */ cx_attr_nonnull cx_attr_returns_nonnull CxJsonValue *cxJsonArrGet(const CxJsonValue *value, size_t index); -// TODO: add cxJsonArrIter() - -// TODO: implement cxJsonObjGet as a _Generic with support for cxstring +/** + * Returns a value corresponding to a key in a JSON object. + * + * If the value is not a JSON object, the behavior is undefined. + * + * This function guarantees to return a JSON value. If the + * object does not contain \p name, the returned JSON value + * will be of type #CX_JSON_NOTHING, but never \c NULL. + * + * @param value the JSON object + * @param name the key to look up + * @return the value corresponding to the key + * @see cxJsonIsObject() + */ cx_attr_nonnull cx_attr_returns_nonnull cx_attr_cstr_arg(2)