--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cx/json.h Sat Oct 19 17:25:11 2024 +0200 @@ -0,0 +1,299 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/** + * \file json.h + * \brief Interface for parsing data from JSON files. + * \author Mike Becker + * \author Olaf Wintermann + * \copyright 2-Clause BSD License + */ + +#ifndef UCX_JSON_H +#define UCX_JSON_H + +#include "common.h" +#include "string.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum cx_json_token_type { + CX_JSON_NO_TOKEN, + CX_JSON_TOKEN_ERROR, + CX_JSON_TOKEN_BEGIN_ARRAY, + CX_JSON_TOKEN_BEGIN_OBJECT, + CX_JSON_TOKEN_END_ARRAY, + CX_JSON_TOKEN_END_OBJECT, + CX_JSON_TOKEN_NAME_SEPARATOR, + CX_JSON_TOKEN_VALUE_SEPARATOR, + CX_JSON_TOKEN_STRING, + CX_JSON_TOKEN_INTEGER, + CX_JSON_TOKEN_NUMBER, + CX_JSON_TOKEN_LITERAL, + CX_JSON_TOKEN_SPACE +}; + +enum cx_json_value_type { + CX_JSON_NOTHING, // this allows us to always return non-NULL values + CX_JSON_OBJECT, + CX_JSON_ARRAY, + CX_JSON_STRING, + CX_JSON_INTEGER, // TODO: the spec does not know integer types + CX_JSON_NUMBER, + CX_JSON_LITERAL +}; + +enum cx_json_literal_type { + CX_JSON_NULL, + CX_JSON_TRUE, + CX_JSON_FALSE +}; + +enum cx_json_reader_type { + CX_JSON_READER_OBJECT_BEGIN, + CX_JSON_READER_OBJECT_END, + CX_JSON_READER_ARRAY_BEGIN, + CX_JSON_READER_ARRAY_END, + CX_JSON_READER_STRING, + CX_JSON_READER_INTEGER, + CX_JSON_READER_NUMBER, + CX_JSON_READER_LITERAL +}; + +typedef enum cx_json_token_type CxJsonTokenType; +typedef enum cx_json_value_type CxJsonValueType; +typedef enum cx_json_literal_type CxJsonLiteralType; +typedef enum cx_json_reader_type CxJsonReaderType; + +typedef struct cx_json_s CxJson; +typedef struct cx_json_token_s CxJsonToken; + +typedef struct cx_json_value_s CxJsonValue; + +typedef struct cx_json_array_s CxJsonArray; +typedef struct cx_json_object_s CxJsonObject; +typedef struct cx_mutstr_s CxJsonString; +typedef struct cx_json_integer_s CxJsonInteger; +typedef struct cx_json_number_s CxJsonNumber; +typedef struct cx_json_literal_s CxJsonLiteral; + +typedef struct cx_json_obj_value_s CxJsonObjValue; + +struct cx_json_token_s { + CxJsonTokenType tokentype; + const char *content; + size_t length; + size_t alloc; +}; + +struct cx_json_s { + const char *buffer; + size_t size; + size_t pos; + + CxJsonToken uncompleted; + int tokenizer_escape; + + int *states; + int nstates; + int states_alloc; + + CxJsonToken reader_token; + CxJsonReaderType reader_type; + int value_ready; + char *value_name; + size_t value_name_len; + char *value_str; + size_t value_str_len; + int64_t value_int; + double value_double; + + CxJsonValue **readvalue_stack; + int readvalue_nelm; + int readvalue_alloc; + CxJsonValue *read_value; + int readvalue_initialized; + + int reader_array_alloc; + + int error; +}; + +struct cx_json_array_s { + CxJsonValue **array; + size_t alloc; + size_t size; +}; + +struct cx_json_object_s { + CxJsonObjValue *values; + size_t alloc; + size_t size; +}; + +struct cx_json_obj_value_s { + char *name; + CxJsonValue *value; +}; + +// TODO: remove single member structs + +struct cx_json_integer_s { + int64_t value; +}; + +struct cx_json_number_s { + double value; +}; + +struct cx_json_literal_s { + CxJsonLiteralType literal; +}; + +struct cx_json_value_s { + CxJsonValueType type; + union { + CxJsonArray array; + CxJsonObject object; + CxJsonString string; + CxJsonInteger integer; + CxJsonNumber number; + CxJsonLiteral literal; + } value; +}; + +// TODO: add support for CxAllocator + +__attribute__((__nonnull__)) +void cxJsonInit(CxJson *json); + +__attribute__((__nonnull__)) +void cxJsonDestroy(CxJson *json); + +__attribute__((__nonnull__)) +void cxJsonFill(CxJson *json, const char *buf, size_t len); + +// TODO: discuss if it is intentional that cxJsonNext() will usually parse an entire file in one go +__attribute__((__nonnull__)) +int cxJsonNext(CxJson *json, CxJsonValue **value); + +void cxJsonValueFree(CxJsonValue *value); + +__attribute__((__nonnull__)) +static inline bool cxJsonIsObject(CxJsonValue *value) { + return value->type == CX_JSON_OBJECT; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonIsArray(CxJsonValue *value) { + return value->type == CX_JSON_ARRAY; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonIsString(CxJsonValue *value) { + return value->type == CX_JSON_STRING; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonIsNumber(CxJsonValue *value) { + // TODO: this is not good, because an integer is also a number + return value->type == CX_JSON_NUMBER; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonIsInteger(CxJsonValue *value) { + return value->type == CX_JSON_INTEGER; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonIsLiteral(CxJsonValue *value) { + return value->type == CX_JSON_LITERAL; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonIsBool(CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal.literal != CX_JSON_NULL; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonIsTrue(CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal.literal == CX_JSON_TRUE; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonIsFalse(CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal.literal == CX_JSON_FALSE; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonIsNull(CxJsonValue *value) { + return cxJsonIsLiteral(value) && value->value.literal.literal == CX_JSON_NULL; +} + +__attribute__((__nonnull__)) +static inline cxmutstr cxJsonAsString(CxJsonValue *value) { + // TODO: do we need a separate method to return this directly as cxstring? + return value->value.string; +} + +__attribute__((__nonnull__)) +static inline double cxJsonAsDouble(CxJsonValue *value) { + return value->value.number.value; +} + +__attribute__((__nonnull__)) +static inline int64_t cxJsonAsInteger(CxJsonValue *value) { + return value->value.integer.value; +} + +__attribute__((__nonnull__)) +static inline bool cxJsonAsBool(CxJsonValue *value) { + return value->value.literal.literal == CX_JSON_TRUE; +} + +__attribute__((__nonnull__)) +static inline size_t cxJsonArrSize(CxJsonValue *value) { + return value->value.array.size; +} + +__attribute__((__nonnull__, __returns_nonnull__)) +CxJsonValue *cxJsonArrGet(CxJsonValue *value, size_t index); + +// TODO: add cxJsonArrIter() + +// TODO: implement cxJsonObjGet as a _Generic with support for cxstring +__attribute__((__nonnull__, __returns_nonnull__)) +CxJsonValue *cxJsonObjGet(CxJsonValue *value, const char* name); + +#ifdef __cplusplus +} +#endif + +#endif /* UCX_JSON_H */ +