Minimal JSON parser

Dependents:   WNCInterface_M2XMQTTdemo

minimal-json.h

Committer:
defmacro
Date:
2016-06-27
Revision:
0:aa8ded7b33eb

File content as of revision 0:aa8ded7b33eb:

#ifndef MJSON_H_
#define MJSON_H_

#include <stdint.h>
#ifndef min
#define min(a, b) ((a) > (b) ? (b) : (a))
#endif  /* min */

#define MJSON_TYPE_NULL 1
#define MJSON_TYPE_TRUE 2
#define MJSON_TYPE_FALSE 3
#define MJSON_TYPE_STRING 4
#define MJSON_TYPE_NUMBER 5
#define MJSON_TYPE_OBJECT 6
#define MJSON_TYPE_ARRAY 7

#define MJSON_SUBTYPE_OBJECT_SEPARATOR 8
#define MJSON_SUBTYPE_OBJECT_END 9
#define MJSON_SUBTYPE_ARRAY_SEPARATOR 10
#define MJSON_SUBTYPE_ARRAY_END 11

#define MJSON_OK 0
#define MJSON_ERROR_READING -1
#define MJSON_ERROR_UNKNOWN_TYPE -2
#define MJSON_ERROR_CHECK_FAILURE -3
#define MJSON_ERROR_TEST_NOT_TRUE -4

/* This should at least be 5 to hold `false` */
#define MJSON_BUFFER_MAX_LENGTH 8

typedef int16_t mjson_status_t;

struct mjson_ctx;

typedef size_t (*mjson_reader)(struct mjson_ctx *ctx, char *data, size_t limit);

struct mjson_ctx {
  void *userdata;
  mjson_reader reader;
  char buffer[MJSON_BUFFER_MAX_LENGTH];
  size_t start;
  size_t length;
};

#define MJSON_BUFFER_START_(ctx) ((ctx)->buffer + (ctx)->start)

/* TODO: we can accept an optional length, so we won't read more data than
 * needed when dealing with numbers directly
 */
void mjson_init(struct mjson_ctx *ctx, void *userdata, mjson_reader reader)
{
  ctx->userdata = userdata;
  ctx->reader = reader;
  ctx->start = 0;
  ctx->length = 0;
}

mjson_status_t mjson_skip_value(struct mjson_ctx *ctx);

void mjson_shift_buffer_(struct mjson_ctx *ctx)
{
  size_t i;
  for (i = 0; i < ctx->length; i++) {
    ctx->buffer[i] = ctx->buffer[i + ctx->start];
  }
  ctx->start = 0;
}

mjson_status_t
mjson_ensure_byte_(struct mjson_ctx *ctx, size_t byte)
{
  if (ctx->length >= byte) { return MJSON_OK; }
  byte -= ctx->length;
  if (MJSON_BUFFER_MAX_LENGTH - ctx->length - ctx->start < byte) {
    mjson_shift_buffer_(ctx);
  }
  if (ctx->reader(ctx, &ctx->buffer[ctx->start + ctx->length], byte) < byte) {
    return MJSON_ERROR_READING;
  }
  ctx->length += byte;
  return MJSON_OK;
}

void
mjson_consume_byte_(struct mjson_ctx *ctx, size_t byte)
{
  ctx->start += byte;
  ctx->length -= byte;
}

mjson_status_t
mjson_read_byte_(struct mjson_ctx *ctx, char ch)
{
  mjson_status_t status;
  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }

  if (*MJSON_BUFFER_START_(ctx) == ch) {
    mjson_consume_byte_(ctx, 1);
    return MJSON_OK;
  }
  return MJSON_ERROR_TEST_NOT_TRUE;
}

mjson_status_t
mjson_equal_string_(const char* a, const char* b, size_t n) {
  size_t i;
  for (i = 0; i < n && a[i] && a[i] == b[i]; i++) ;
  return i == n ? MJSON_OK : MJSON_ERROR_TEST_NOT_TRUE;
}

mjson_status_t
mjson_read_type(struct mjson_ctx *ctx)
{
  mjson_status_t status;
  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }

  switch (*MJSON_BUFFER_START_(ctx)) {
    case '"':
      mjson_consume_byte_(ctx, 1);
      return MJSON_TYPE_STRING;
    case '{':
      mjson_consume_byte_(ctx, 1);
      return MJSON_TYPE_OBJECT;
    case '[':
      mjson_consume_byte_(ctx, 1);
      return MJSON_TYPE_ARRAY;
    case 't':
      status = mjson_ensure_byte_(ctx, 4);
      if (status != MJSON_OK) { return status; }
      if (mjson_equal_string_(MJSON_BUFFER_START_(ctx), "true", 4) == MJSON_OK) {
        mjson_consume_byte_(ctx, 4);
        return MJSON_TYPE_TRUE;
      } else {
        return MJSON_ERROR_UNKNOWN_TYPE;
      }
      break;
    case 'f':
      status = mjson_ensure_byte_(ctx, 5);
      if (status != MJSON_OK) { return status; }
      if (mjson_equal_string_(MJSON_BUFFER_START_(ctx), "false", 5) == MJSON_OK) {
        mjson_consume_byte_(ctx, 5);
        return MJSON_TYPE_FALSE;
      } else {
        return MJSON_ERROR_UNKNOWN_TYPE;
      }
      break;
    case 'n':
      status = mjson_ensure_byte_(ctx, 4);
      if (status != MJSON_OK) { return status; }
      if (mjson_equal_string_(MJSON_BUFFER_START_(ctx), "null", 4) == MJSON_OK) {
        mjson_consume_byte_(ctx, 4);
        return MJSON_TYPE_NULL;
      } else {
        return MJSON_ERROR_UNKNOWN_TYPE;
      }
      break;
    case '-':
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      return MJSON_TYPE_NUMBER;
  }
  return MJSON_ERROR_UNKNOWN_TYPE;
}

mjson_status_t mjson_readcheck_null(struct mjson_ctx *ctx)
{
  return mjson_read_type(ctx) == MJSON_TYPE_NULL ?
      MJSON_OK : MJSON_ERROR_CHECK_FAILURE;
}

mjson_status_t mjson_readcheck_boolean(struct mjson_ctx *ctx, int8_t *b)
{
  mjson_status_t t = mjson_read_type(ctx);
  switch (t) {
    case MJSON_TYPE_TRUE:
      *b = 1;
      return MJSON_OK;
    case MJSON_TYPE_FALSE:
      *b = 0;
      return MJSON_OK;
  }
  return MJSON_ERROR_CHECK_FAILURE;
}

mjson_status_t mjson_readcheck_string_start(struct mjson_ctx *ctx)
{
  return mjson_read_type(ctx) == MJSON_TYPE_STRING ?
      MJSON_OK : MJSON_ERROR_CHECK_FAILURE;
}

mjson_status_t mjson_read_partial_string(struct mjson_ctx *ctx,
                                         char *data, size_t length,
                                         size_t *out_length)
{
  mjson_status_t status;
  size_t i = 0;

  while (i < length) {
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }

    if (*MJSON_BUFFER_START_(ctx) == '"') {
      break;
    } else if (*MJSON_BUFFER_START_(ctx) == '\\') {
      status = mjson_ensure_byte_(ctx, 2);
      if (status != MJSON_OK) { return status; }
      switch (MJSON_BUFFER_START_(ctx)[1]) {
        case '"':
          data[i++] = '"';
          break;
        case '\\':
          data[i++] = '\\';
          break;
        case '/':
          data[i++] = '/';
          break;
        case 'b':
          data[i++] = '\b';
          break;
        case 'f':
          data[i++] = '\f';
          break;
        case 'n':
          data[i++] = '\n';
          break;
        case 'r':
          data[i++] = '\r';
          break;
        case 't':
          data[i++] = '\t';
          break;
        default:
          return MJSON_ERROR_UNKNOWN_TYPE;
      }
      mjson_consume_byte_(ctx, 2);
    } else {
      data[i++] = *MJSON_BUFFER_START_(ctx);
      mjson_consume_byte_(ctx, 1);
    }
  }

  *out_length = i;
  return MJSON_OK;
}

mjson_status_t mjson_read_string_end(struct mjson_ctx *ctx)
{
  return mjson_read_byte_(ctx, '"');
}

/* After this you don't have to do mjson_read_string_end */
mjson_status_t mjson_read_full_string(struct mjson_ctx *ctx,
                                      char *data, size_t length,
                                      size_t *out_length)
{
  mjson_status_t status;
  size_t full_length;
  status = mjson_read_partial_string(ctx, data, length, &full_length);
  if (status != MJSON_OK) { return status; }

  while (1) {
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }

    if (*MJSON_BUFFER_START_(ctx) == '"') {
      mjson_consume_byte_(ctx, 1);
      *out_length = full_length;
      return MJSON_OK;
    } else if (*MJSON_BUFFER_START_(ctx) == '\\') {
      status = mjson_ensure_byte_(ctx, 2);
      if (status != MJSON_OK) { return status; }
      mjson_consume_byte_(ctx, 2);
      full_length++;
    } else {
      mjson_consume_byte_(ctx, 1);
      full_length++;
    }
  }
  /* Not reachable */
  return MJSON_ERROR_CHECK_FAILURE;
}

mjson_status_t mjson_skip_string(struct mjson_ctx *ctx)
{
  size_t temp;
  return mjson_read_full_string(ctx, NULL, 0, &temp);
}

mjson_status_t mjson_readcheck_array_start(struct mjson_ctx *ctx)
{
  return mjson_read_type(ctx) == MJSON_TYPE_ARRAY ?
      MJSON_OK : MJSON_ERROR_CHECK_FAILURE;
}

mjson_status_t mjson_read_array_separator_or_end(struct mjson_ctx *ctx)
{
  mjson_status_t status;
  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }

  if (*MJSON_BUFFER_START_(ctx) == ',') {
    mjson_consume_byte_(ctx, 1);
    return MJSON_SUBTYPE_ARRAY_SEPARATOR;
  } else if (*MJSON_BUFFER_START_(ctx) == ']') {
    mjson_consume_byte_(ctx, 1);
    return MJSON_SUBTYPE_ARRAY_END;
  }
  return MJSON_ERROR_TEST_NOT_TRUE;
}

mjson_status_t mjson_skip_array(struct mjson_ctx *ctx)
{
  mjson_status_t status;

  while (mjson_read_array_separator_or_end(ctx) != MJSON_SUBTYPE_ARRAY_END) {
    status = mjson_skip_value(ctx);
    if (status != MJSON_OK) { return status; }
  }

  return MJSON_OK;
}

mjson_status_t mjson_readcheck_object_start(struct mjson_ctx *ctx)
{
  return mjson_read_type(ctx) == MJSON_TYPE_OBJECT ?
      MJSON_OK : MJSON_ERROR_CHECK_FAILURE;
}

mjson_status_t mjson_read_object_key_separator(struct mjson_ctx *ctx)
{
  return mjson_read_byte_(ctx, ':');
}

mjson_status_t mjson_read_object_separator_or_end(struct mjson_ctx *ctx)
{
  mjson_status_t status;
  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }

  if (*MJSON_BUFFER_START_(ctx) == ',') {
    mjson_consume_byte_(ctx, 1);
    return MJSON_SUBTYPE_OBJECT_SEPARATOR;
  } else if (*MJSON_BUFFER_START_(ctx) == '}') {
    mjson_consume_byte_(ctx, 1);
    return MJSON_SUBTYPE_OBJECT_END;
  }
  return MJSON_ERROR_TEST_NOT_TRUE;
}

mjson_status_t mjson_skip_object(struct mjson_ctx *ctx)
{
  mjson_status_t status;

  while (mjson_read_object_separator_or_end(ctx) != MJSON_SUBTYPE_OBJECT_END) {
    status = mjson_readcheck_string_start(ctx);
    if (status != MJSON_OK) { return status; }

    status = mjson_skip_string(ctx);
    if (status != MJSON_OK) { return status; }

    status = mjson_read_object_key_separator(ctx);
    if (status != MJSON_OK) { return status; }

    status = mjson_skip_value(ctx);
    if (status != MJSON_OK) { return status; }
  }

  return MJSON_OK;
}

mjson_status_t mjson_read_int8(struct mjson_ctx *ctx, int8_t *out)
{
  int8_t val = 0, sign = 1;
  mjson_status_t status;

  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }
  if (MJSON_BUFFER_START_(ctx)[0] == '-') {
    mjson_consume_byte_(ctx, 1);
    sign = -1;
  }

  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }
  while (MJSON_BUFFER_START_(ctx)[0] >= '0' &&
         MJSON_BUFFER_START_(ctx)[0] <= '9') {
    val = val * 10 + (MJSON_BUFFER_START_(ctx)[0] - '0');

    mjson_consume_byte_(ctx, 1);
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }
  }
  *out = val * sign;
  /* Skip additional parts */
  while ((MJSON_BUFFER_START_(ctx)[0] >= '0' &&
          MJSON_BUFFER_START_(ctx)[0] <= '9') ||
         MJSON_BUFFER_START_(ctx)[0] == '.' ||
         MJSON_BUFFER_START_(ctx)[0] == 'e' ||
         MJSON_BUFFER_START_(ctx)[0] == 'E' ||
         MJSON_BUFFER_START_(ctx)[0] == '+' ||
         MJSON_BUFFER_START_(ctx)[0] == '-') {
    mjson_consume_byte_(ctx, 1);
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }
  }
  return MJSON_OK;
}

mjson_status_t mjson_read_int16(struct mjson_ctx *ctx, int16_t *out)
{
  int16_t val = 0, sign = 1;
  mjson_status_t status;

  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }
  if (MJSON_BUFFER_START_(ctx)[0] == '-') {
    mjson_consume_byte_(ctx, 1);
    sign = -1;
  }

  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }
  while (MJSON_BUFFER_START_(ctx)[0] >= '0' &&
         MJSON_BUFFER_START_(ctx)[0] <= '9') {
    val = val * 10 + (MJSON_BUFFER_START_(ctx)[0] - '0');

    mjson_consume_byte_(ctx, 1);
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }
  }
  *out = val * sign;
  /* Skip additional parts */
  while ((MJSON_BUFFER_START_(ctx)[0] >= '0' &&
          MJSON_BUFFER_START_(ctx)[0] <= '9') ||
         MJSON_BUFFER_START_(ctx)[0] == '.' ||
         MJSON_BUFFER_START_(ctx)[0] == 'e' ||
         MJSON_BUFFER_START_(ctx)[0] == 'E' ||
         MJSON_BUFFER_START_(ctx)[0] == '+' ||
         MJSON_BUFFER_START_(ctx)[0] == '-') {
    mjson_consume_byte_(ctx, 1);
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }
  }
  return MJSON_OK;
}

mjson_status_t mjson_read_int32(struct mjson_ctx *ctx, int32_t *out)
{
  int32_t val = 0, sign = 1;
  mjson_status_t status;

  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }
  if (MJSON_BUFFER_START_(ctx)[0] == '-') {
    mjson_consume_byte_(ctx, 1);
    sign = -1;
  }

  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }
  while (MJSON_BUFFER_START_(ctx)[0] >= '0' &&
         MJSON_BUFFER_START_(ctx)[0] <= '9') {
    val = val * 10 + (MJSON_BUFFER_START_(ctx)[0] - '0');

    mjson_consume_byte_(ctx, 1);
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }
  }
  *out = val * sign;
  /* Skip additional parts */
  while ((MJSON_BUFFER_START_(ctx)[0] >= '0' &&
          MJSON_BUFFER_START_(ctx)[0] <= '9') ||
         MJSON_BUFFER_START_(ctx)[0] == '.' ||
         MJSON_BUFFER_START_(ctx)[0] == 'e' ||
         MJSON_BUFFER_START_(ctx)[0] == 'E' ||
         MJSON_BUFFER_START_(ctx)[0] == '+' ||
         MJSON_BUFFER_START_(ctx)[0] == '-') {
    mjson_consume_byte_(ctx, 1);
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }
  }
  return MJSON_OK;
}

mjson_status_t mjson_read_int64(struct mjson_ctx *ctx, int64_t *out)
{
  int64_t val = 0, sign = 1;
  mjson_status_t status;

  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }
  if (MJSON_BUFFER_START_(ctx)[0] == '-') {
    mjson_consume_byte_(ctx, 1);
    sign = -1;
  }

  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }
  while (MJSON_BUFFER_START_(ctx)[0] >= '0' &&
         MJSON_BUFFER_START_(ctx)[0] <= '9') {
    val = val * 10 + (MJSON_BUFFER_START_(ctx)[0] - '0');

    mjson_consume_byte_(ctx, 1);
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }
  }
  *out = val * sign;
  /* Skip additional parts */
  while ((MJSON_BUFFER_START_(ctx)[0] >= '0' &&
          MJSON_BUFFER_START_(ctx)[0] <= '9') ||
         MJSON_BUFFER_START_(ctx)[0] == '.' ||
         MJSON_BUFFER_START_(ctx)[0] == 'e' ||
         MJSON_BUFFER_START_(ctx)[0] == 'E' ||
         MJSON_BUFFER_START_(ctx)[0] == '+' ||
         MJSON_BUFFER_START_(ctx)[0] == '-') {
    mjson_consume_byte_(ctx, 1);
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }
  }
  return MJSON_OK;
}
/*
 * We don't really support parsing to double directly here, since to implement
 * full double parsing semantics, we would need pow(). However, if pow() is
 * available, it is very likely that atof() is also available. Hence providing
 * copying number as a string will be enough.
 */
mjson_status_t mjson_read_number_as_string(struct mjson_ctx *ctx,
                                           char *data, size_t length,
                                           size_t *out_length) {
  mjson_status_t status;
  size_t i = 0;
  char ch;

  while (i < length) {
    status = mjson_ensure_byte_(ctx, 1);
    if (status != MJSON_OK) { return status; }

    ch = MJSON_BUFFER_START_(ctx)[0];
    if ((ch >= '0' && ch <= '9') ||
        ch == '.' ||
        ch == '+' || ch == '-' ||
        ch == 'e' || ch == 'E') {
      data[i++] = ch;
      mjson_consume_byte_(ctx, 1);
    } else {
      break;
    }
  }
  status = mjson_ensure_byte_(ctx, 1);
  if (status != MJSON_OK) { return status; }
  ch = MJSON_BUFFER_START_(ctx)[0];
  while ((ch >= '0' && ch <= '9') ||
      ch == '.' ||
      ch == '+' || ch == '-' ||
      ch == 'e' || ch == 'E') {
    i++;
    mjson_consume_byte_(ctx, 1);
  }
  *out_length = i;
  return MJSON_OK;
}

mjson_status_t mjson_skip_number(struct mjson_ctx *ctx)
{
  size_t temp;
  return mjson_read_number_as_string(ctx, NULL, 0, &temp);
}

mjson_status_t mjson_skip_value(struct mjson_ctx *ctx) {
  mjson_status_t t = mjson_read_type(ctx);
  switch (t) {
    case MJSON_TYPE_ARRAY:
      return mjson_skip_array(ctx);
    case MJSON_TYPE_OBJECT:
      return mjson_skip_object(ctx);
    case MJSON_TYPE_NUMBER:
      return mjson_skip_number(ctx);
    case MJSON_TYPE_STRING:
      return mjson_skip_string(ctx);
    case MJSON_TYPE_NULL:
    case MJSON_TYPE_TRUE:
    case MJSON_TYPE_FALSE:
      return MJSON_OK;
  }
  return t;
}

#endif  /* MJSON_H_ */