blob: 273fe624ab3a60989a4138c3803b2989a22ee2d5 [file]
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
* Copyright (C) 2021-2024 Cyril Hrubis <metan@ucw.cz>
*/
/**
* @file ujson_reader.h
* @brief A recursive descend JSON parser.
*
* All the function that parse JSON return zero on success and non-zero on a
* failure. Once an error has happened all subsequent attempts to parse more
* return with non-zero exit status immediatelly. This is designed so that we
* can parse several values without checking each return value and only check
* if error has happened at the end of the sequence.
*/
#ifndef UJSON_READER_H
#define UJSON_READER_H
#include <stdio.h>
#include <ujson_common.h>
/**
* @brief An ujson_reader initializer with default values.
*
* @param buf A pointer to a buffer with JSON data.
* @param buf_len A JSON data buffer lenght.
* @param rflags enum ujson_reader_flags.
*
* @return An ujson_reader initialized with default values.
*/
#define UJSON_READER_INIT(buf, buf_len, rflags) { \
.max_depth = UJSON_RECURSION_MAX, \
.err_print = UJSON_ERR_PRINT, \
.err_print_priv = UJSON_ERR_PRINT_PRIV, \
.json = buf, \
.len = buf_len, \
.flags = rflags \
}
/** @brief Reader flags. */
enum ujson_reader_flags {
/** @brief If set warnings are treated as errors. */
UJSON_READER_STRICT = 0x01,
};
/**
* @brief A JSON parser internal state.
*/
struct ujson_reader {
/** Pointer to a null terminated JSON string */
const char *json;
/** A length of the JSON string */
size_t len;
/** A current offset into the JSON string */
size_t off;
/** An offset to the start of the last array or object */
size_t sub_off;
/** Recursion depth increased when array/object is entered decreased on leave */
unsigned int depth;
/** Maximal recursion depth */
unsigned int max_depth;
/** Reader flags. */
enum ujson_reader_flags flags;
/** Handler to print errors and warnings */
void (*err_print)(void *err_print_priv, const char *line);
void *err_print_priv;
char err[UJSON_ERR_MAX];
char buf[];
};
/**
* @brief An ujson_val initializer.
*
* @param sbuf A pointer to a buffer used for string values.
* @param sbuf_size A length of the buffer used for string values.
*
* @return An ujson_val initialized with default values.
*/
#define UJSON_VAL_INIT(sbuf, sbuf_size) { \
.buf = sbuf, \
.buf_size = sbuf_size, \
}
/**
* @brief A parsed JSON key value pair.
*/
struct ujson_val {
/**
* @brief A value type
*
* UJSON_VALUE_VOID means that no value was parsed.
*/
enum ujson_type type;
/** An user supplied buffer and size to store a string values to. */
char *buf;
size_t buf_size;
/**
* @brief An index to attribute list.
*
* This is set by the ujson_obj_first_filter() and
* ujson_obj_next_filter() functions.
*/
size_t idx;
/** An union to store the parsed value into. */
union {
/** @brief A boolean value. */
int val_bool;
/** @brief An integer value. */
long long val_int;
/** @brief A string value. */
const char *val_str;
};
/**
* @brief A floating point value.
*
* Since integer values are subset of floating point values val_float
* is always set when val_int was set.
*/
double val_float;
/** @brief An ID for object values */
char id[UJSON_ID_MAX];
char buf__[];
};
/**
* @brief Allocates a JSON value.
*
* @param buf_size A maximal buffer size for a string value, pass 0 for default.
* @return A newly allocated JSON value.
*/
ujson_val *ujson_val_alloc(size_t buf_size);
/**
* @brief Frees a JSON value.
*
* @param self A JSON value previously allocated by ujson_val_alloc().
*/
void ujson_val_free(ujson_val *self);
/**
* @brief Checks is result has valid type.
*
* @param res An ujson value.
* @return Zero if result is not valid, non-zero otherwise.
*/
static inline int ujson_val_valid(struct ujson_val *res)
{
return !!res->type;
}
/**
* @brief Fills the reader error.
*
* Once buffer error is set all parsing functions return immediatelly with type
* set to UJSON_VOID.
*
* @param self An ujson_reader
* @param fmt A printf like format string
* @param ... A printf like parameters
*/
void ujson_err(ujson_reader *self, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
/**
* @brief Prints error stored in the buffer.
*
* The error takes into consideration the current offset in the buffer and
* prints a few preceding lines along with the exact position of the error.
*
* The error is passed to the err_print() handler.
*
* @param self A ujson_reader
*/
void ujson_err_print(ujson_reader *self);
/**
* @brief Prints a warning.
*
* Uses the print handler in the buffer to print a warning along with a few
* lines of context from the JSON at the current position.
*
* @param self A ujson_reader
* @param fmt A printf-like error string.
* @param ... A printf-like parameters.
*/
void ujson_warn(ujson_reader *self, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
/**
* @brief Returns true if error was encountered.
*
* @param self A ujson_reader
* @return True if error was encountered false otherwise.
*/
static inline int ujson_reader_err(ujson_reader *self)
{
return !!self->err[0];
}
/**
* @brief Returns the type of next element in buffer.
*
* @param self An ujson_reader
* @return A type of next element in the buffer.
*/
enum ujson_type ujson_next_type(ujson_reader *self);
/**
* @brief Returns if first element in JSON is object or array.
*
* @param self A ujson_reader
* @return On success returns UJSON_OBJ or UJSON_ARR. On failure UJSON_VOID.
*/
enum ujson_type ujson_reader_start(ujson_reader *self);
/**
* @brief Starts parsing of a JSON object.
*
* @param self An ujson_reader
* @param res An ujson_val to store the parsed value to.
*
* @return Zero on success, non-zero otherwise.
*/
int ujson_obj_first(ujson_reader *self, struct ujson_val *res);
/**
* @brief Parses next value from a JSON object.
*
* If the res->type is UJSON_OBJ or UJSON_ARR it has to be parsed or skipped
* before next call to this function.
*
* @param self An ujson_reader.
* @param res A ujson_val to store the parsed value to.
*
* @return Zero on success, non-zero otherwise.
*/
int ujson_obj_next(ujson_reader *self, struct ujson_val *res);
/**
* @brief A loop over a JSON object.
*
* @code
* UJSON_OBJ_FOREACH(reader, val) {
* printf("Got value id '%s' type '%s'", val->id, ujson_type_name(val->type));
* ...
* }
* @endcode
*
* @param self An ujson_reader.
* @param res An ujson_val to store the next parsed value to.
*/
#define UJSON_OBJ_FOREACH(self, res) \
for (ujson_obj_first(self, res); ujson_val_valid(res); ujson_obj_next(self, res))
/**
* @brief Utility function for log(n) lookup in a sorted array.
*
* @param list Analphabetically sorted array.
* @param list_len Array length.
*
* @return An array index or (size_t)-1 if key wasn't found.
*/
size_t ujson_lookup(const void *arr, size_t memb_size, size_t list_len,
const char *key);
/**
* @brief A JSON object attribute description i.e. key and type.
*/
typedef struct ujson_obj_attr {
/** @brief A JSON object key name. */
const char *key;
/**
* @brief A JSON object value type.
*
* Note that because integer numbers are subset of floating point
* numbers if requested type was UJSON_FLOAT it will match if parsed
* type was UJSON_INT and the val_float will be set in addition to
* val_int.
*/
enum ujson_type type;
} ujson_obj_attr;
/** @brief A JSON object description */
typedef struct ujson_obj {
/**
* @brief A list of attributes.
*
* Attributes we are looking for, the parser sets the val->idx for these.
*/
const ujson_obj_attr *attrs;
/** @brief A size of attrs array. */
size_t attr_cnt;
} ujson_obj;
static inline size_t ujson_obj_lookup(const ujson_obj *obj, const char *key)
{
return ujson_lookup(obj->attrs, sizeof(*obj->attrs), obj->attr_cnt, key);
}
/** @brief An ujson_obj_attr initializer. */
#define UJSON_OBJ_ATTR(keyv, typev) \
{.key = keyv, .type = typev}
/** @brief An ujson_obj_attr intializer with an array index. */
#define UJSON_OBJ_ATTR_IDX(key_idx, keyv, typev) \
[key_idx] = {.key = keyv, .type = typev}
/**
* @brief Starts parsing of a JSON object with attribute lists.
*
* @param self An ujson_reader.
* @param res An ujson_val to store the parsed value to.
* @param obj An ujson_obj object description.
* @param ign A list of keys to ignore.
*
* @return Zero on success, non-zero otherwise.
*/
int ujson_obj_first_filter(ujson_reader *self, struct ujson_val *res,
const struct ujson_obj *obj, const struct ujson_obj *ign);
/**
* @brief An empty object attribute list.
*
* To be passed to UJSON_OBJ_FOREACH_FITLER() as ignore list.
*/
extern const struct ujson_obj *ujson_empty_obj;
/**
* @brief Parses next value from a JSON object with attribute lists.
*
* If the res->type is UJSON_OBJ or UJSON_ARR it has to be parsed or skipped
* before next call to this function.
*
* @param self An ujson_reader.
* @param res An ujson_val to store the parsed value to.
* @param obj An ujson_obj object description.
* @param ign A list of keys to ignore. If set to NULL all unknown keys are
* ignored, if set to ujson_empty_obj all unknown keys produce warnings.
*
* @return Zero on success, non-zero otherwise.
*/
int ujson_obj_next_filter(ujson_reader *self, struct ujson_val *res,
const struct ujson_obj *obj, const struct ujson_obj *ign);
/**
* @brief A loop over a JSON object with a pre-defined list of expected attributes.
*
* @code
* static struct ujson_obj_attr attrs[] = {
* UJSON_OBJ_ATTR("bool", UJSON_BOOL),
* UJSON_OBJ_ATTR("number", UJSON_INT),
* };
*
* static struct ujson_obj obj = {
* .attrs = filter_attrs,
* .attr_cnt = UJSON_ARRAY_SIZE(filter_attrs)
* };
*
* UJSON_OBJ_FOREACH_FILTER(reader, val, &obj, NULL) {
* printf("Got value id '%s' type '%s'",
* attrs[val->idx].id, ujson_type_name(val->type));
* ...
* }
* @endcode
*
* @param self An ujson_reader.
* @param res An ujson_val to store the next parsed value to.
* @param obj An ujson_obj with a description of attributes to parse.
* @param ign An ujson_obj with a description of attributes to ignore.
*/
#define UJSON_OBJ_FOREACH_FILTER(self, res, obj, ign) \
for (ujson_obj_first_filter(self, res, obj, ign); \
ujson_val_valid(res); \
ujson_obj_next_filter(self, res, obj, ign))
/**
* @brief Skips parsing of a JSON object.
*
* @param self An ujson_reader.
*
* @return Zero on success, non-zero otherwise.
*/
int ujson_obj_skip(ujson_reader *self);
/**
* @brief Starts parsing of a JSON array.
*
* @param self An ujson_reader.
* @param res An ujson_val to store the parsed value to.
*
* @return Zero on success, non-zero otherwise.
*/
int ujson_arr_first(ujson_reader *self, struct ujson_val *res);
/**
* @brief Parses next value from a JSON array.
*
* If the res->type is UJSON_OBJ or UJSON_ARR it has to be parsed or skipped
* before next call to this function.
*
* @param self An ujson_reader.
* @param res An ujson_val to store the parsed value to.
*
* @return Zero on success, non-zero otherwise.
*/
int ujson_arr_next(ujson_reader *self, struct ujson_val *res);
/**
* @brief A loop over a JSON array.
*
* @code
* UJSON_ARR_FOREACH(reader, val) {
* printf("Got value type '%s'", ujson_type_name(val->type));
* ...
* }
* @endcode
*
* @param self An ujson_reader.
* @param res An ujson_val to store the next parsed value to.
*/
#define UJSON_ARR_FOREACH(self, res) \
for (ujson_arr_first(self, res); ujson_val_valid(res); ujson_arr_next(self, res))
/**
* @brief Skips parsing of a JSON array.
*
* @param self A ujson_reader.
*
* @return Zero on success, non-zero otherwise.
*/
int ujson_arr_skip(ujson_reader *self);
/**
* @brief A JSON reader state.
*/
typedef struct ujson_reader_state {
size_t off;
unsigned int depth;
} ujson_reader_state;
/**
* @brief Returns a parser state at the start of current object/array.
*
* This function could be used for the parser to return to the start of the
* currently parsed object or array.
*
* @param self A ujson_reader
* @return A state that points to a start of the last object or array.
*/
static inline ujson_reader_state ujson_reader_state_save(ujson_reader *self)
{
struct ujson_reader_state ret = {
.off = self->sub_off,
.depth = self->depth,
};
return ret;
}
/**
* @brief Returns the parser to a saved state.
*
* This function could be used for the parser to return to the start of
* object or array saved by t the ujson_reader_state_get() function.
*
* @param self A ujson_reader
* @param state An parser state as returned by the ujson_reader_state_get().
*/
static inline void ujson_reader_state_load(ujson_reader *self, ujson_reader_state state)
{
if (ujson_reader_err(self))
return;
self->off = state.off;
self->sub_off = state.off;
self->depth = state.depth;
}
/**
* @brief Resets the parser to a start.
*
* @param self A ujson_reader
*/
static inline void ujson_reader_reset(ujson_reader *self)
{
self->off = 0;
self->sub_off = 0;
self->depth = 0;
self->err[0] = 0;
}
/**
* @brief Loads a file into an ujson_reader buffer.
*
* The reader has to be later freed by ujson_reader_free().
*
* @param path A path to a file.
* @return A ujson_reader or NULL in a case of a failure.
*/
ujson_reader *ujson_reader_load(const char *path);
/**
* @brief Frees an ujson_reader buffer.
*
* @param self A ujson_reader allocated by ujson_reader_load() function.
*/
void ujson_reader_free(ujson_reader *self);
/**
* @brief Prints errors and warnings at the end of parsing.
*
* Checks if self->err is set and prints the error with ujson_reader_err()
*
* Checks if there is any text left after the parser has finished with
* ujson_reader_consumed() and prints a warning if there were any non-whitespace
* characters left.
*
* @param self A ujson_reader
*/
void ujson_reader_finish(ujson_reader *self);
/**
* @brief Returns non-zero if whole buffer has been consumed.
*
* @param self A ujson_reader.
* @return Non-zero if whole buffer was consumed.
*/
static inline int ujson_reader_consumed(ujson_reader *self)
{
return self->off >= self->len;
}
#endif /* UJSON_H */