summaryrefslogtreecommitdiffstats
path: root/src/json.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 04:20:26 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 04:20:26 +0000
commit044203039cebe3c05161f8f104a039d4744ca6d0 (patch)
tree1073c2308492e6aea4c66cb7436ee92db2abfd42 /src/json.c
parentInitial commit. (diff)
downloadlibyang2-044203039cebe3c05161f8f104a039d4744ca6d0.tar.xz
libyang2-044203039cebe3c05161f8f104a039d4744ca6d0.zip
Adding upstream version 2.1.30.upstream/2.1.30
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/json.c')
-rw-r--r--src/json.c1047
1 files changed, 1047 insertions, 0 deletions
diff --git a/src/json.c b/src/json.c
new file mode 100644
index 0000000..5c45c8c
--- /dev/null
+++ b/src/json.c
@@ -0,0 +1,1047 @@
+/**
+ * @file json.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief Generic JSON format parser for libyang
+ *
+ * Copyright (c) 2020 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "common.h"
+#include "in_internal.h"
+#include "json.h"
+#include "tree_schema_internal.h"
+
+const char *
+lyjson_token2str(enum LYJSON_PARSER_STATUS status)
+{
+ switch (status) {
+ case LYJSON_ERROR:
+ return "error";
+ case LYJSON_ROOT:
+ return "document root";
+ case LYJSON_FALSE:
+ return "false";
+ case LYJSON_TRUE:
+ return "true";
+ case LYJSON_NULL:
+ return "null";
+ case LYJSON_OBJECT:
+ return "object";
+ case LYJSON_OBJECT_CLOSED:
+ return "object closed";
+ case LYJSON_OBJECT_EMPTY:
+ return "empty object";
+ case LYJSON_ARRAY:
+ return "array";
+ case LYJSON_ARRAY_CLOSED:
+ return "array closed";
+ case LYJSON_ARRAY_EMPTY:
+ return "empty array";
+ case LYJSON_NUMBER:
+ return "number";
+ case LYJSON_STRING:
+ return "string";
+ case LYJSON_END:
+ return "end of input";
+ }
+
+ return "";
+}
+
+static LY_ERR
+skip_ws(struct lyjson_ctx *jsonctx)
+{
+ /* skip leading whitespaces */
+ while (*jsonctx->in->current != '\0' && is_jsonws(*jsonctx->in->current)) {
+ if (*jsonctx->in->current == '\n') {
+ LY_IN_NEW_LINE(jsonctx->in);
+ }
+ ly_in_skip(jsonctx->in, 1);
+ }
+ if (*jsonctx->in->current == '\0') {
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_END);
+ }
+
+ return LY_SUCCESS;
+}
+
+/*
+ * @brief Set value corresponding to the current context's status
+ */
+static void
+lyjson_ctx_set_value(struct lyjson_ctx *jsonctx, const char *value, size_t value_len, ly_bool dynamic)
+{
+ assert(jsonctx);
+
+ if (jsonctx->dynamic) {
+ free((char *)jsonctx->value);
+ }
+ jsonctx->value = value;
+ jsonctx->value_len = value_len;
+ jsonctx->dynamic = dynamic;
+}
+
+static LY_ERR
+lyjson_check_next(struct lyjson_ctx *jsonctx)
+{
+ if (jsonctx->status.count == 1) {
+ /* top level value (JSON-text), ws expected */
+ if ((*jsonctx->in->current == '\0') || is_jsonws(*jsonctx->in->current)) {
+ return LY_SUCCESS;
+ }
+ } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_OBJECT) {
+ LY_CHECK_RET(skip_ws(jsonctx));
+ if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == '}')) {
+ return LY_SUCCESS;
+ }
+ } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_ARRAY) {
+ LY_CHECK_RET(skip_ws(jsonctx));
+ if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == ']')) {
+ return LY_SUCCESS;
+ }
+ }
+
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Unexpected character \"%c\" after JSON %s.",
+ *jsonctx->in->current, lyjson_token2str(lyjson_ctx_status(jsonctx, 0)));
+ return LY_EVALID;
+}
+
+/**
+ * Input is expected to start after the opening quotation-mark.
+ * When succeeds, input is moved after the closing quotation-mark.
+ */
+static LY_ERR
+lyjson_string_(struct lyjson_ctx *jsonctx)
+{
+#define BUFSIZE 24
+#define BUFSIZE_STEP 128
+
+ const char *in = jsonctx->in->current, *start;
+ char *buf = NULL;
+ size_t offset; /* read offset in input buffer */
+ size_t len; /* length of the output string (write offset in output buffer) */
+ size_t size = 0; /* size of the output buffer */
+ size_t u;
+ uint64_t start_line;
+
+ assert(jsonctx);
+
+ /* init */
+ start = in;
+ start_line = jsonctx->in->line;
+ offset = len = 0;
+
+ /* parse */
+ while (in[offset]) {
+ if (in[offset] == '\\') {
+ /* escape sequence */
+ const char *slash = &in[offset];
+ uint32_t value;
+ uint8_t i = 1;
+
+ if (!buf) {
+ /* prepare output buffer */
+ buf = malloc(BUFSIZE);
+ LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM);
+ size = BUFSIZE;
+ }
+
+ /* allocate enough for the offset and next character,
+ * we will need 4 bytes at most since we support only the predefined
+ * (one-char) entities and character references */
+ if (len + offset + 4 >= size) {
+ size_t increment;
+
+ for (increment = BUFSIZE_STEP; len + offset + 4 >= size + increment; increment += BUFSIZE_STEP) {}
+ buf = ly_realloc(buf, size + increment);
+ LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM);
+ size += BUFSIZE_STEP;
+ }
+
+ if (offset) {
+ /* store what we have so far */
+ memcpy(&buf[len], in, offset);
+ len += offset;
+ in += offset;
+ offset = 0;
+ }
+
+ switch (in[++offset]) {
+ case '"':
+ /* quotation mark */
+ value = 0x22;
+ break;
+ case '\\':
+ /* reverse solidus */
+ value = 0x5c;
+ break;
+ case '/':
+ /* solidus */
+ value = 0x2f;
+ break;
+ case 'b':
+ /* backspace */
+ value = 0x08;
+ break;
+ case 'f':
+ /* form feed */
+ value = 0x0c;
+ break;
+ case 'n':
+ /* line feed */
+ value = 0x0a;
+ break;
+ case 'r':
+ /* carriage return */
+ value = 0x0d;
+ break;
+ case 't':
+ /* tab */
+ value = 0x09;
+ break;
+ case 'u':
+ /* Basic Multilingual Plane character \uXXXX */
+ offset++;
+ for (value = i = 0; i < 4; i++) {
+ if (!in[offset + i]) {
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid basic multilingual plane character \"%s\".", slash);
+ goto error;
+ } else if (isdigit(in[offset + i])) {
+ u = (in[offset + i] - '0');
+ } else if (in[offset + i] > 'F') {
+ u = LY_BASE_DEC + (in[offset + i] - 'a');
+ } else {
+ u = LY_BASE_DEC + (in[offset + i] - 'A');
+ }
+ value = (LY_BASE_HEX * value) + u;
+ }
+ break;
+ default:
+ /* invalid escape sequence */
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character escape sequence \\%c.", in[offset]);
+ goto error;
+
+ }
+
+ offset += i; /* add read escaped characters */
+ LY_CHECK_ERR_GOTO(ly_pututf8(&buf[len], value, &u),
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character reference \"%.*s\" (0x%08x).",
+ (int)(&in[offset] - slash), slash, value),
+ error);
+ len += u; /* update number of bytes in buffer */
+ in += offset; /* move the input by the processed bytes stored in the buffer ... */
+ offset = 0; /* ... and reset the offset index for future moving data into buffer */
+
+ } else if (in[offset] == '"') {
+ /* end of string */
+ if (buf) {
+ /* realloc exact size string */
+ buf = ly_realloc(buf, len + offset + 1);
+ LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM);
+ size = len + offset + 1;
+ if (offset) {
+ memcpy(&buf[len], in, offset);
+ }
+
+ /* set terminating NULL byte */
+ buf[len + offset] = '\0';
+ }
+ len += offset;
+ ++offset;
+ in += offset;
+ goto success;
+ } else {
+ /* get it as UTF-8 character for check */
+ const char *c = &in[offset];
+ uint32_t code = 0;
+ size_t code_len = 0;
+
+ LY_CHECK_ERR_GOTO(ly_getutf8(&c, &code, &code_len),
+ LOGVAL(jsonctx->ctx, LY_VCODE_INCHAR, in[offset]), error);
+
+ LY_CHECK_ERR_GOTO(!is_jsonstrchar(code),
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character in JSON string \"%.*s\" (0x%08x).",
+ (int)(&in[offset] - start + code_len), start, code),
+ error);
+
+ /* character is ok, continue */
+ offset += code_len;
+ }
+ }
+
+ /* EOF reached before endchar */
+ LOGVAL(jsonctx->ctx, LY_VCODE_EOF);
+ LOGVAL_LINE(jsonctx->ctx, start_line, LYVE_SYNTAX, "Missing quotation-mark at the end of a JSON string.");
+
+error:
+ free(buf);
+ return LY_EVALID;
+
+success:
+ jsonctx->in->current = in;
+ if (buf) {
+ lyjson_ctx_set_value(jsonctx, buf, len, 1);
+ } else {
+ lyjson_ctx_set_value(jsonctx, start, len, 0);
+ }
+
+ return LY_SUCCESS;
+
+#undef BUFSIZE
+#undef BUFSIZE_STEP
+}
+
+/*
+ *
+ * Wrapper around lyjson_string_() adding LYJSON_STRING status into context to allow using lyjson_string_() for parsing object's name.
+ */
+static LY_ERR
+lyjson_string(struct lyjson_ctx *jsonctx)
+{
+ LY_CHECK_RET(lyjson_string_(jsonctx));
+
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_STRING);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ return LY_SUCCESS;
+}
+
+/**
+ * @brief Calculate how many @p c characters there are in a row.
+ *
+ * @param[in] str Count from this position.
+ * @param[in] end Position after the last checked character.
+ * @param[in] c Checked character.
+ * @param[in] backwards Set to 1, if to proceed from end-1 to str.
+ * @return Number of characters in a row.
+ */
+static uint32_t
+lyjson_count_in_row(const char *str, const char *end, char c, ly_bool backwards)
+{
+ uint32_t cnt;
+
+ assert(str && end);
+
+ if (str >= end) {
+ return 0;
+ }
+
+ if (!backwards) {
+ for (cnt = 0; (str != end) && (*str == c); ++str, ++cnt) {}
+ } else {
+ --end;
+ --str;
+ for (cnt = 0; (str != end) && (*end == c); --end, ++cnt) {}
+ }
+
+ return cnt;
+}
+
+/**
+ * @brief Check if the number can be shortened to zero.
+ *
+ * @param[in] in Start of input string;
+ * @param[in] end End of input string;
+ * @return 1 if number is zero, otherwise 0.
+ */
+static ly_bool
+lyjson_number_is_zero(const char *in, const char *end)
+{
+ assert(in < end);
+
+ if ((in[0] == '-') || (in[0] == '+')) {
+ in++;
+ assert(in < end);
+ }
+ if ((in[0] == '0') && (in[1] == '.')) {
+ in += 2;
+ if (!(in < end)) {
+ return 1;
+ }
+ }
+
+ return lyjson_count_in_row(in, end, '0', 0) == end - in;
+}
+
+/**
+ * @brief Allocate buffer for number in string format.
+ *
+ * @param[in] jsonctx JSON context.
+ * @param[in] num_len Required space in bytes for a number.
+ * Terminating null byte is added by default.
+ * @param[out] buffer Output allocated buffer.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lyjson_get_buffer_for_number(const struct ly_ctx *ctx, uint64_t num_len, char **buffer)
+{
+ *buffer = NULL;
+
+ LY_CHECK_ERR_RET((num_len + 1) > LY_NUMBER_MAXLEN, LOGVAL(ctx, LYVE_SEMANTICS,
+ "Number encoded as a string exceeded the LY_NUMBER_MAXLEN limit."), LY_EVALID);
+
+ /* allocate buffer for the result (add NULL-byte) */
+ *buffer = malloc(num_len + 1);
+ LY_CHECK_ERR_RET(!(*buffer), LOGMEM(ctx), LY_EMEM);
+ return LY_SUCCESS;
+}
+
+/**
+ * @brief Copy the 'numeric part' (@p num) except its decimal point
+ * (@p dec_point) and insert the new decimal point (@p dp_position)
+ * only if it is to be placed in the 'numeric part' range (@p num).
+ *
+ * @param[in] num Begin of the 'numeric part'.
+ * @param[in] num_len Length of the 'numeric part'.
+ * @param[in] dec_point Pointer to the old decimal point.
+ * If it has a NULL value, it is ignored.
+ * @param[in] dp_position Position of the new decimal point.
+ * If it has a negative value, it is ignored.
+ * @param[out] dst Memory into which the copied result is written.
+ * @return Number of characters written to the @p dst.
+ */
+static uint32_t
+lyjson_exp_number_copy_num_part(const char *num, uint32_t num_len,
+ char *dec_point, int32_t dp_position, char *dst)
+{
+ int32_t dec_point_idx;
+ int32_t n, d;
+
+ assert(num && dst);
+
+ dec_point_idx = dec_point ? dec_point - num : INT32_MAX;
+ assert((dec_point_idx >= 0) && (dec_point_idx != dp_position));
+
+ for (n = 0, d = 0; (uint32_t)n < num_len; n++) {
+ if (n == dec_point_idx) {
+ continue;
+ } else if (d == dp_position) {
+ dst[d++] = '.';
+ dst[d++] = num[n];
+ } else {
+ dst[d++] = num[n];
+ }
+ }
+
+ return d;
+}
+
+/**
+ * @brief Convert JSON number with exponent into the representation
+ * used by YANG.
+ *
+ * The input numeric string must be syntactically valid. Also, before
+ * calling this function, checks should be performed using the
+ * ::lyjson_number_is_zero().
+ *
+ * @param[in] ctx Context for the error message.
+ * @param[in] in Beginning of the string containing the number.
+ * @param[in] exponent Pointer to the letter E/e.
+ * @param[in] total_len Total size of the input number.
+ * @param[out] res Conversion result.
+ * @param[out] res_len Length of the result.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lyjson_exp_number(const struct ly_ctx *ctx, const char *in, const char *exponent,
+ uint64_t total_len, char **res, size_t *res_len)
+{
+
+#define MAYBE_WRITE_MINUS(ARRAY, INDEX, FLAG) \
+ if (FLAG) { \
+ ARRAY[INDEX++] = '-'; \
+ }
+
+/* Length of leading zero followed by the decimal point. */
+#define LEADING_ZERO 1
+
+/* Flags for the ::lyjson_count_in_row() */
+#define FORWARD 0
+#define BACKWARD 1
+
+ /* Buffer where the result is stored. */
+ char *buf;
+ /* Size without space for terminating NULL-byte. */
+ uint64_t buf_len;
+ /* Index to buf. */
+ uint32_t i = 0;
+ /* A 'numeric part' doesn't contain a minus sign or an leading zero.
+ * For example, in 0.45, there is the leading zero.
+ */
+ const char *num;
+ /* Length of the 'numeric part' ends before E/e. */
+ uint16_t num_len;
+ /* Position of decimal point in the num. */
+ char *dec_point;
+ /* Final position of decimal point in the buf. */
+ int32_t dp_position;
+ /* Exponent as integer. */
+ long long e_val;
+ /* Byte for the decimal point. */
+ int8_t dot;
+ /* Required additional byte for the minus sign. */
+ uint8_t minus;
+ /* The number of zeros. */
+ long zeros;
+ /* If the number starts with leading zero followed by the decimal point. */
+ ly_bool leading_zero;
+
+ assert(ctx && in && exponent && res && res_len && (total_len > 2));
+ assert((in < exponent) && ((*exponent == 'e') || (*exponent == 'E')));
+
+ if ((exponent - in) > UINT16_MAX) {
+ LOGVAL(ctx, LYVE_SEMANTICS, "JSON number is too long.");
+ return LY_EVALID;
+ }
+
+ /* Convert exponent. */
+ errno = 0;
+ e_val = strtoll(exponent + 1, NULL, LY_BASE_DEC);
+ if (errno || (e_val > UINT16_MAX) || (e_val < -UINT16_MAX)) {
+ LOGVAL(ctx, LYVE_SEMANTICS,
+ "Exponent out-of-bounds in a JSON Number value (%.*s).",
+ (int)total_len, in);
+ return LY_EVALID;
+ }
+
+ minus = in[0] == '-';
+ if (in[minus] == '0') {
+ assert(in[minus + 1] == '.');
+ leading_zero = 1;
+ /* The leading zero has been found, it will be skipped. */
+ num = &in[minus + 1];
+ } else {
+ leading_zero = 0;
+ /* Set to the first number. */
+ num = &in[minus];
+ }
+ num_len = exponent - num;
+
+ /* Find the location of the decimal points. */
+ dec_point = ly_strnchr(num, '.', num_len);
+ dp_position = dec_point ?
+ dec_point - num + e_val :
+ num_len + e_val;
+
+ /* Remove zeros after the decimal point from the end of
+ * the 'numeric part' because these are useless.
+ * (For example, in 40.001000 these are the last 3).
+ */
+ num_len -= dp_position > 0 ?
+ lyjson_count_in_row(num + dp_position - 1, exponent, '0', BACKWARD) :
+ lyjson_count_in_row(num, exponent, '0', BACKWARD);
+
+ /* Decide what to do with the dot from the 'numeric part'. */
+ if (dec_point && ((int32_t)(num_len - 1) == dp_position)) {
+ /* Decimal point in the last place is useless. */
+ dot = -1;
+ } else if (dec_point) {
+ /* Decimal point is shifted. */
+ dot = 0;
+ } else {
+ /* Additional byte for the decimal point is requred. */
+ dot = 1;
+ }
+
+ /* Final composition of the result. */
+ if (dp_position <= 0) {
+ /* Adding decimal point before the integer with adding additional zero(s). */
+
+ zeros = labs(dp_position);
+ buf_len = minus + LEADING_ZERO + dot + zeros + num_len;
+ LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf));
+ MAYBE_WRITE_MINUS(buf, i, minus);
+ buf[i++] = '0';
+ buf[i++] = '.';
+ memset(buf + i, '0', zeros);
+ i += zeros;
+ dp_position = -1;
+ lyjson_exp_number_copy_num_part(num, num_len, dec_point, dp_position, buf + i);
+ } else if (leading_zero && (dp_position < (ssize_t)num_len)) {
+ /* Insert decimal point between the integer's digits. */
+
+ /* Set a new range of 'numeric part'. Old decimal point is skipped. */
+ num++;
+ num_len--;
+ dp_position--;
+ /* Get the number of useless zeros between the old
+ * and new decimal point. For example, in the number 0.005E1,
+ * there is one useless zero.
+ */
+ zeros = lyjson_count_in_row(num, num + dp_position + 1, '0', FORWARD);
+ /* If the new decimal point will be in the place of the first non-zero subnumber. */
+ if (zeros == (dp_position + 1)) {
+ /* keep one zero as leading zero */
+ zeros--;
+ /* new decimal point will be behind the leading zero */
+ dp_position = 1;
+ dot = 1;
+ } else {
+ dot = 0;
+ }
+ buf_len = minus + dot + (num_len - zeros);
+ LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf));
+ MAYBE_WRITE_MINUS(buf, i, minus);
+ /* Skip useless zeros and copy. */
+ lyjson_exp_number_copy_num_part(num + zeros, num_len - zeros, NULL, dp_position, buf + i);
+ } else if (dp_position < (ssize_t)num_len) {
+ /* Insert decimal point between the integer's digits. */
+
+ buf_len = minus + dot + num_len;
+ LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf));
+ MAYBE_WRITE_MINUS(buf, i, minus);
+ lyjson_exp_number_copy_num_part(num, num_len, dec_point, dp_position, buf + i);
+ } else if (leading_zero) {
+ /* Adding decimal point after the decimal value make the integer result. */
+
+ /* Set a new range of 'numeric part'. Old decimal point is skipped. */
+ num++;
+ num_len--;
+ /* Get the number of useless zeros. */
+ zeros = lyjson_count_in_row(num, num + num_len, '0', FORWARD);
+ buf_len = minus + dp_position - zeros;
+ LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf));
+ MAYBE_WRITE_MINUS(buf, i, minus);
+ /* Skip useless zeros and copy. */
+ i += lyjson_exp_number_copy_num_part(num + zeros, num_len - zeros, NULL, dp_position, buf + i);
+ /* Add multiples of ten behind the 'numeric part'. */
+ memset(buf + i, '0', buf_len - i);
+ } else {
+ /* Adding decimal point after the decimal value make the integer result. */
+
+ buf_len = minus + dp_position;
+ LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf));
+ MAYBE_WRITE_MINUS(buf, i, minus);
+ i += lyjson_exp_number_copy_num_part(num, num_len, dec_point, dp_position, buf + i);
+ /* Add multiples of ten behind the 'numeric part'. */
+ memset(buf + i, '0', buf_len - i);
+ }
+
+ buf[buf_len] = '\0';
+ *res = buf;
+ *res_len = buf_len;
+
+#undef MAYBE_WRITE_MINUS
+#undef LEADING_ZERO
+#undef FORWARD
+#undef BACKWARD
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+lyjson_number(struct lyjson_ctx *jsonctx)
+{
+ size_t offset = 0, num_len;
+ const char *in = jsonctx->in->current, *exponent = NULL;
+ uint8_t minus = 0;
+ char *num;
+
+ if (in[offset] == '-') {
+ ++offset;
+ minus = 1;
+ }
+
+ if (in[offset] == '0') {
+ ++offset;
+ } else if (isdigit(in[offset])) {
+ ++offset;
+ while (isdigit(in[offset])) {
+ ++offset;
+ }
+ } else {
+invalid_character:
+ if (in[offset]) {
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character in JSON Number value (\"%c\").", in[offset]);
+ } else {
+ LOGVAL(jsonctx->ctx, LY_VCODE_EOF);
+ }
+ return LY_EVALID;
+ }
+
+ if (in[offset] == '.') {
+ ++offset;
+ if (!isdigit(in[offset])) {
+ goto invalid_character;
+ }
+ while (isdigit(in[offset])) {
+ ++offset;
+ }
+ }
+
+ if ((in[offset] == 'e') || (in[offset] == 'E')) {
+ exponent = &in[offset];
+ ++offset;
+ if ((in[offset] == '+') || (in[offset] == '-')) {
+ ++offset;
+ }
+ if (!isdigit(in[offset])) {
+ goto invalid_character;
+ }
+ while (isdigit(in[offset])) {
+ ++offset;
+ }
+ }
+
+ if (lyjson_number_is_zero(in, exponent ? exponent : &in[offset])) {
+ lyjson_ctx_set_value(jsonctx, in, minus + 1, 0);
+ } else if (exponent && lyjson_number_is_zero(exponent + 1, &in[offset])) {
+ lyjson_ctx_set_value(jsonctx, in, exponent - in, 0);
+ } else if (exponent) {
+ LY_CHECK_RET(lyjson_exp_number(jsonctx->ctx, in, exponent, offset, &num, &num_len));
+ lyjson_ctx_set_value(jsonctx, num, num_len, 1);
+ } else {
+ if (offset > LY_NUMBER_MAXLEN) {
+ LOGVAL(jsonctx->ctx, LYVE_SEMANTICS,
+ "Number encoded as a string exceeded the LY_NUMBER_MAXLEN limit.");
+ return LY_EVALID;
+ }
+ lyjson_ctx_set_value(jsonctx, in, offset, 0);
+ }
+ ly_in_skip(jsonctx->in, offset);
+
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_NUMBER);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+lyjson_object_name(struct lyjson_ctx *jsonctx)
+{
+ if (*jsonctx->in->current != '"') {
+ LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current),
+ jsonctx->in->current, "a JSON object's member");
+ return LY_EVALID;
+ }
+ ly_in_skip(jsonctx->in, 1);
+
+ LY_CHECK_RET(lyjson_string_(jsonctx));
+ LY_CHECK_RET(skip_ws(jsonctx));
+ if (*jsonctx->in->current != ':') {
+ LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current,
+ "a JSON object's name-separator ':'");
+ return LY_EVALID;
+ }
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(skip_ws(jsonctx));
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+lyjson_object(struct lyjson_ctx *jsonctx)
+{
+ LY_CHECK_RET(skip_ws(jsonctx));
+
+ if (*jsonctx->in->current == '}') {
+ assert(jsonctx->depth);
+ jsonctx->depth--;
+ /* empty object */
+ ly_in_skip(jsonctx->in, 1);
+ lyjson_ctx_set_value(jsonctx, NULL, 0, 0);
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT_EMPTY);
+ return LY_SUCCESS;
+ }
+
+ LY_CHECK_RET(lyjson_object_name(jsonctx));
+
+ /* output data are set by lyjson_string_() */
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT);
+
+ return LY_SUCCESS;
+}
+
+/**
+ * @brief Process JSON array envelope
+ *
+ * @param[in] jsonctx JSON parser context
+ * @return LY_SUCCESS or LY_EMEM
+ */
+static LY_ERR
+lyjson_array(struct lyjson_ctx *jsonctx)
+{
+ LY_CHECK_RET(skip_ws(jsonctx));
+
+ if (*jsonctx->in->current == ']') {
+ /* empty array */
+ ly_in_skip(jsonctx->in, 1);
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY_EMPTY);
+ } else {
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY);
+ }
+
+ /* erase previous values, array has no value on its own */
+ lyjson_ctx_set_value(jsonctx, NULL, 0, 0);
+
+ return LY_SUCCESS;
+}
+
+static LY_ERR
+lyjson_value(struct lyjson_ctx *jsonctx)
+{
+ if (jsonctx->status.count && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) {
+ return LY_SUCCESS;
+ }
+
+ if ((*jsonctx->in->current == 'f') && !strncmp(jsonctx->in->current, "false", ly_strlen_const("false"))) {
+ /* false */
+ lyjson_ctx_set_value(jsonctx, jsonctx->in->current, ly_strlen_const("false"), 0);
+ ly_in_skip(jsonctx->in, ly_strlen_const("false"));
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_FALSE);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ } else if ((*jsonctx->in->current == 't') && !strncmp(jsonctx->in->current, "true", ly_strlen_const("true"))) {
+ /* true */
+ lyjson_ctx_set_value(jsonctx, jsonctx->in->current, ly_strlen_const("true"), 0);
+ ly_in_skip(jsonctx->in, ly_strlen_const("true"));
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_TRUE);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ } else if ((*jsonctx->in->current == 'n') && !strncmp(jsonctx->in->current, "null", ly_strlen_const("null"))) {
+ /* none */
+ lyjson_ctx_set_value(jsonctx, "", 0, 0);
+ ly_in_skip(jsonctx->in, ly_strlen_const("null"));
+ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_NULL);
+ LY_CHECK_RET(lyjson_check_next(jsonctx));
+
+ } else if (*jsonctx->in->current == '"') {
+ /* string */
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(lyjson_string(jsonctx));
+
+ } else if (*jsonctx->in->current == '[') {
+ /* array */
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(lyjson_array(jsonctx));
+
+ } else if (*jsonctx->in->current == '{') {
+ jsonctx->depth++;
+ if (jsonctx->depth > LY_MAX_BLOCK_DEPTH) {
+ LOGERR(jsonctx->ctx, LY_EINVAL,
+ "The maximum number of block nestings has been exceeded.");
+ return LY_EINVAL;
+ }
+ /* object */
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(lyjson_object(jsonctx));
+
+ } else if ((*jsonctx->in->current == '-') || ((*jsonctx->in->current >= '0') && (*jsonctx->in->current <= '9'))) {
+ /* number */
+ LY_CHECK_RET(lyjson_number(jsonctx));
+
+ } else {
+ /* unexpected value */
+ LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current),
+ jsonctx->in->current, "a JSON value");
+ return LY_EVALID;
+ }
+
+ return LY_SUCCESS;
+}
+
+LY_ERR
+lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, ly_bool subtree, struct lyjson_ctx **jsonctx_p)
+{
+ LY_ERR ret = LY_SUCCESS;
+ struct lyjson_ctx *jsonctx;
+
+ assert(ctx);
+ assert(in);
+ assert(jsonctx_p);
+
+ /* new context */
+ jsonctx = calloc(1, sizeof *jsonctx);
+ LY_CHECK_ERR_RET(!jsonctx, LOGMEM(ctx), LY_EMEM);
+ jsonctx->ctx = ctx;
+ jsonctx->in = in;
+
+ LOG_LOCSET(NULL, NULL, NULL, in);
+
+ /* parse JSON value, if any */
+ LY_CHECK_GOTO(ret = skip_ws(jsonctx), cleanup);
+ if (lyjson_ctx_status(jsonctx, 0) == LYJSON_END) {
+ /* empty data input */
+ goto cleanup;
+ }
+
+ if (subtree) {
+ ret = lyjson_object(jsonctx);
+ jsonctx->depth++;
+ } else {
+ ret = lyjson_value(jsonctx);
+ }
+ if ((jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) {
+ LOGVAL(jsonctx->ctx, LY_VCODE_EOF);
+ ret = LY_EVALID;
+ }
+
+cleanup:
+ if (ret) {
+ lyjson_ctx_free(jsonctx);
+ } else {
+ *jsonctx_p = jsonctx;
+ }
+ return ret;
+}
+
+void
+lyjson_ctx_backup(struct lyjson_ctx *jsonctx)
+{
+ if (jsonctx->backup.dynamic) {
+ free((char *)jsonctx->backup.value);
+ }
+ jsonctx->backup.status = lyjson_ctx_status(jsonctx, 0);
+ jsonctx->backup.status_count = jsonctx->status.count;
+ jsonctx->backup.value = jsonctx->value;
+ jsonctx->backup.value_len = jsonctx->value_len;
+ jsonctx->backup.input = jsonctx->in->current;
+ jsonctx->backup.dynamic = jsonctx->dynamic;
+ jsonctx->backup.depth = jsonctx->depth;
+ jsonctx->dynamic = 0;
+}
+
+void
+lyjson_ctx_restore(struct lyjson_ctx *jsonctx)
+{
+ if (jsonctx->dynamic) {
+ free((char *)jsonctx->value);
+ }
+ jsonctx->status.count = jsonctx->backup.status_count;
+ jsonctx->status.objs[jsonctx->backup.status_count - 1] = (void *)jsonctx->backup.status;
+ jsonctx->value = jsonctx->backup.value;
+ jsonctx->value_len = jsonctx->backup.value_len;
+ jsonctx->in->current = jsonctx->backup.input;
+ jsonctx->dynamic = jsonctx->backup.dynamic;
+ jsonctx->depth = jsonctx->backup.depth;
+ jsonctx->backup.dynamic = 0;
+}
+
+LY_ERR
+lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status)
+{
+ LY_ERR ret = LY_SUCCESS;
+ ly_bool toplevel = 0;
+ enum LYJSON_PARSER_STATUS prev;
+
+ assert(jsonctx);
+
+ prev = lyjson_ctx_status(jsonctx, 0);
+
+ if ((prev == LYJSON_OBJECT) || (prev == LYJSON_ARRAY)) {
+ /* get value for the object's member OR the first value in the array */
+ ret = lyjson_value(jsonctx);
+ goto result;
+ } else {
+ /* the previous token is closed and should be completely processed */
+ LYJSON_STATUS_POP_RET(jsonctx);
+ prev = lyjson_ctx_status(jsonctx, 0);
+ }
+
+ if (!jsonctx->status.count) {
+ /* we are done with the top level value */
+ toplevel = 1;
+ }
+ LY_CHECK_RET(skip_ws(jsonctx));
+ if (toplevel && !jsonctx->status.count) {
+ /* EOF expected, but there are some data after the top level token */
+ LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Expecting end-of-input, but some data follows the top level JSON value.");
+ return LY_EVALID;
+ }
+
+ if (toplevel) {
+ /* we are done */
+ goto result;
+ }
+
+ /* continue with the next token */
+ assert(prev == LYJSON_OBJECT || prev == LYJSON_ARRAY);
+
+ if (*jsonctx->in->current == ',') {
+ /* sibling item in the ... */
+ ly_in_skip(jsonctx->in, 1);
+ LY_CHECK_RET(skip_ws(jsonctx));
+
+ if (prev == LYJSON_OBJECT) {
+ /* ... object - get another object's member */
+ ret = lyjson_object_name(jsonctx);
+ } else { /* LYJSON_ARRAY */
+ /* ... array - get another complete value */
+ ret = lyjson_value(jsonctx);
+ }
+ } else if (((prev == LYJSON_OBJECT) && (*jsonctx->in->current == '}')) ||
+ ((prev == LYJSON_ARRAY) && (*jsonctx->in->current == ']'))) {
+ if (*jsonctx->in->current == '}') {
+ assert(jsonctx->depth);
+ jsonctx->depth--;
+ }
+ ly_in_skip(jsonctx->in, 1);
+ LYJSON_STATUS_POP_RET(jsonctx);
+ LYJSON_STATUS_PUSH_RET(jsonctx, prev + 1);
+ } else {
+ /* unexpected value */
+ LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current,
+ prev == LYJSON_ARRAY ? "another JSON value in array" : "another JSON object's member");
+ return LY_EVALID;
+ }
+
+result:
+ if ((ret == LY_SUCCESS) && (jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) {
+ LOGVAL(jsonctx->ctx, LY_VCODE_EOF);
+ ret = LY_EVALID;
+ }
+
+ if ((ret == LY_SUCCESS) && status) {
+ *status = lyjson_ctx_status(jsonctx, 0);
+ }
+
+ return ret;
+}
+
+enum LYJSON_PARSER_STATUS
+lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index)
+{
+ assert(jsonctx);
+
+ if (jsonctx->status.count < index) {
+ return LYJSON_ERROR;
+ } else if (jsonctx->status.count == index) {
+ return LYJSON_ROOT;
+ } else {
+ return (enum LYJSON_PARSER_STATUS)(uintptr_t)jsonctx->status.objs[jsonctx->status.count - (index + 1)];
+ }
+}
+
+void
+lyjson_ctx_free(struct lyjson_ctx *jsonctx)
+{
+ if (!jsonctx) {
+ return;
+ }
+
+ LOG_LOCBACK(0, 0, 0, 1);
+
+ if (jsonctx->dynamic) {
+ free((char *)jsonctx->value);
+ }
+ if (jsonctx->backup.dynamic) {
+ free((char *)jsonctx->backup.value);
+ }
+
+ ly_set_erase(&jsonctx->status, NULL);
+
+ free(jsonctx);
+}