1105 lines
28 KiB
C
1105 lines
28 KiB
C
#include "json.h"
|
|
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/**
|
|
* @brief Determines whether a character `ch` is whitespace
|
|
*/
|
|
#define is_whitespace(ch) (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t')
|
|
|
|
#ifdef JSON_SKIP_WHITESPACE
|
|
void json_skip_whitespace(typed(json_string) * str_ptr) {
|
|
while (is_whitespace(**str_ptr))
|
|
(*str_ptr)++;
|
|
}
|
|
#else
|
|
#define json_skip_whitespace(arg)
|
|
#endif
|
|
|
|
#ifdef JSON_DEBUG
|
|
#define log(str, ...) printf(str "\n", ##__VA_ARGS__)
|
|
void json_debug_print(typed(json_string) str, typed(size) len) {
|
|
for (size_t i = 0; i < len; i++) {
|
|
if (str[i] == '\0')
|
|
break;
|
|
|
|
putchar(str[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
#else
|
|
#define log(str, ...)
|
|
#endif
|
|
|
|
#define define_result_type(name) \
|
|
result(name) result_ok(name)(typed(name) value) { \
|
|
result(name) retval = { \
|
|
.is_ok = true, \
|
|
.inner = \
|
|
{ \
|
|
.value = value, \
|
|
}, \
|
|
}; \
|
|
return retval; \
|
|
} \
|
|
result(name) result_err(name)(typed(json_error) err) { \
|
|
result(name) retval = { \
|
|
.is_ok = false, \
|
|
.inner = \
|
|
{ \
|
|
.err = err, \
|
|
}, \
|
|
}; \
|
|
return retval; \
|
|
} \
|
|
typed(json_boolean) result_is_ok(name)(result(name) * result) { \
|
|
return result->is_ok; \
|
|
} \
|
|
typed(json_boolean) result_is_err(name)(result(name) * result) { \
|
|
return !result->is_ok; \
|
|
} \
|
|
typed(name) result_unwrap(name)(result(name) * result) { \
|
|
return result->inner.value; \
|
|
} \
|
|
typed(json_error) result_unwrap_err(name)(result(name) * result) { \
|
|
return result->inner.err; \
|
|
}
|
|
|
|
/**
|
|
* @brief Allocate `count` number of items of `type` in memory
|
|
* and return the pointer to the newly allocated memory
|
|
*/
|
|
#define allocN(type, count) (type *)malloc((count) * sizeof(type))
|
|
|
|
/**
|
|
* @brief Allocate an item of `type` in memory and return the
|
|
* pointer to the newly allocated memory
|
|
*/
|
|
#define alloc(type) allocN(type, 1)
|
|
|
|
/**
|
|
* @brief Re-allocate `count` number of items of `type` in memory
|
|
* and return the pointer to the newly allocated memory
|
|
*/
|
|
#define reallocN(ptr, type, count) (type *)realloc(ptr, (count) * sizeof(type))
|
|
|
|
/**
|
|
* @brief Parses a JSON element {json_element_t} and moves the string
|
|
* pointer to the end of the parsed element
|
|
*/
|
|
static result(json_entry) json_parse_entry(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Guesses the element type at the start of a string
|
|
*/
|
|
static result(json_element_type) json_guess_element_type(typed(json_string));
|
|
|
|
/**
|
|
* @brief Whether a token represents a string. Like '"'
|
|
*/
|
|
static bool json_is_string(char);
|
|
|
|
/**
|
|
* @brief Whether a token represents a number. Like '0'
|
|
*/
|
|
static bool json_is_number(char);
|
|
|
|
/**
|
|
* @brief Whether a token represents a object. Like '"'
|
|
*/
|
|
static bool json_is_object(char);
|
|
|
|
/**
|
|
* @brief Whether a token represents a array. Like '['
|
|
*/
|
|
static bool json_is_array(char);
|
|
|
|
/**
|
|
* @brief Whether a token represents a boolean. Like 't'
|
|
*/
|
|
static bool json_is_boolean(char);
|
|
|
|
/**
|
|
* @brief Whether a token represents a null. Like 'n'
|
|
*/
|
|
static bool json_is_null(char);
|
|
|
|
/**
|
|
* @brief Parses a JSON element value {json_element_value_t} based
|
|
* on the `type` parameter passed and moves the string pointer
|
|
* to end of the parsed element
|
|
*/
|
|
static result(json_element_value)
|
|
json_parse_element_value(typed(json_string) *, typed(json_element_type));
|
|
|
|
/**
|
|
* @brief Parses a `String` {json_string_t} and moves the string
|
|
* pointer to the end of the parsed string
|
|
*/
|
|
static result(json_element_value) json_parse_string(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Parses a `Number` {json_number_t} and moves the string
|
|
* pointer to the end of the parsed number
|
|
*/
|
|
static result(json_element_value) json_parse_number(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Parses a `Object` {json_object_t} and moves the string
|
|
* pointer to the end of the parsed object
|
|
*/
|
|
static result(json_element_value) json_parse_object(typed(json_string) *);
|
|
|
|
static typed(uint64) json_key_hash(typed(json_string));
|
|
|
|
/**
|
|
* @brief Parses a `Array` {json_array_t} and moves the string
|
|
* pointer to the end of the parsed array
|
|
*/
|
|
static result(json_element_value) json_parse_array(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Parses a `Boolean` {json_boolean_t} and moves the string
|
|
* pointer to the end of the parsed boolean
|
|
*/
|
|
static result(json_element_value) json_parse_boolean(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Skips a Key-Value pair
|
|
*
|
|
* @return true If a valid entry is skipped
|
|
* @return false If entry was invalid (still skips)
|
|
*/
|
|
static bool json_skip_entry(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Skips an element value
|
|
*
|
|
* @return true If a valid element is skipped
|
|
* @return false If element was invalid (still skips)
|
|
*/
|
|
static bool json_skip_element_value(typed(json_string) *,
|
|
typed(json_element_type));
|
|
|
|
/**
|
|
* @brief Skips a string value
|
|
*
|
|
* @return true If a valid string is skipped
|
|
* @return false If string was invalid (still skips)
|
|
*/
|
|
static bool json_skip_string(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Skips a number value
|
|
*
|
|
* @return true If a valid number is skipped
|
|
* @return false If number was invalid (still skips)
|
|
*/
|
|
static bool json_skip_number(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Skips an object value
|
|
*
|
|
* @return true If a valid object is skipped
|
|
* @return false If object was invalid (still skips)
|
|
*/
|
|
static bool json_skip_object(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Skips an array value
|
|
*
|
|
* @return true If a valid array is skipped
|
|
* @return false If array was invalid (still skips)
|
|
*/
|
|
static bool json_skip_array(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Skips a boolean value
|
|
*
|
|
* @return true If a valid boolean is skipped
|
|
* @return false If boolean was invalid (still skips)
|
|
*/
|
|
static bool json_skip_boolean(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Moves a JSON string pointer beyond any whitespace
|
|
*/
|
|
// static void json_skip_whitespace_actual(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Moves a JSON string pointer beyond `null` literal
|
|
*
|
|
*/
|
|
static void json_skip_null(typed(json_string) *);
|
|
|
|
/**
|
|
* @brief Prints a JSON element {json_element_t} type
|
|
*/
|
|
static void json_print_element(typed(json_element) *, int, int);
|
|
|
|
/**
|
|
* @brief Prints a `String` {json_string_t} type
|
|
*/
|
|
static void json_print_string(typed(json_string));
|
|
|
|
/**
|
|
* @brief Prints a `Number` {json_number_t} type
|
|
*/
|
|
static void json_print_number(typed(json_number));
|
|
|
|
/**
|
|
* @brief Prints an `Object` {json_object_t} type
|
|
*/
|
|
static void json_print_object(typed(json_object) *, int, int);
|
|
|
|
/**
|
|
* @brief Prints an `Array` {json_array_t} type
|
|
*/
|
|
static void json_print_array(typed(json_array) *, int, int);
|
|
|
|
/**
|
|
* @brief Prints a `Boolean` {json_boolean_t} type
|
|
*/
|
|
static void json_print_boolean(typed(json_boolean));
|
|
|
|
/**
|
|
* @brief Frees a `String` (json_string_t) from memory
|
|
*/
|
|
static void json_free_string(typed(json_string));
|
|
|
|
/**
|
|
* @brief Frees an `Object` (json_object_t) from memory
|
|
*/
|
|
static void json_free_object(typed(json_object) *);
|
|
|
|
/**
|
|
* @brief Frees an `Array` (json_array_t) from memory
|
|
*/
|
|
static void json_free_array(typed(json_array) *);
|
|
|
|
/**
|
|
* @brief Utility function to convert an escaped string to a formatted string
|
|
*/
|
|
static result(json_string)
|
|
json_unescape_string(typed(json_string), typed(size));
|
|
|
|
/**
|
|
* @brief Offset to the last `"` of a JSON string
|
|
*/
|
|
static typed(size) json_string_len(typed(json_string));
|
|
|
|
result(json_element) json_parse(typed(json_string) json_str) {
|
|
if (json_str == NULL) {
|
|
return result_err(json_element)(JSON_ERROR_EMPTY);
|
|
}
|
|
|
|
typed(size) len = strlen(json_str);
|
|
if (len == 0) {
|
|
return result_err(json_element)(JSON_ERROR_EMPTY);
|
|
}
|
|
|
|
result_try(json_element, json_element_type, type,
|
|
json_guess_element_type(json_str));
|
|
result_try(json_element, json_element_value, value,
|
|
json_parse_element_value(&json_str, type));
|
|
|
|
const typed(json_element) element = {
|
|
.type = type,
|
|
.value = value,
|
|
};
|
|
|
|
return result_ok(json_element)(element);
|
|
}
|
|
|
|
result(json_entry) json_parse_entry(typed(json_string) * str_ptr) {
|
|
result_try(json_entry, json_element_value, key, json_parse_string(str_ptr));
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
// Skip the ':' delimiter
|
|
(*str_ptr)++;
|
|
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
result(json_element_type) type_result = json_guess_element_type(*str_ptr);
|
|
if (result_is_err(json_element_type)(&type_result)) {
|
|
free((void *)key.as_string);
|
|
return result_map_err(json_entry, json_element_type, &type_result);
|
|
}
|
|
typed(json_element_type) type =
|
|
result_unwrap(json_element_type)(&type_result);
|
|
|
|
result(json_element_value) value_result =
|
|
json_parse_element_value(str_ptr, type);
|
|
if (result_is_err(json_element_value)(&value_result)) {
|
|
free((void *)key.as_string);
|
|
return result_map_err(json_entry, json_element_value, &value_result);
|
|
}
|
|
typed(json_element_value) value =
|
|
result_unwrap(json_element_value)(&value_result);
|
|
|
|
typed(json_entry) entry = {
|
|
.key = key.as_string,
|
|
.element =
|
|
{
|
|
.type = type,
|
|
.value = value,
|
|
},
|
|
};
|
|
|
|
return result_ok(json_entry)(entry);
|
|
}
|
|
|
|
result(json_element_type) json_guess_element_type(typed(json_string) str) {
|
|
const char ch = *str;
|
|
typed(json_element_type) type;
|
|
|
|
if (json_is_string(ch))
|
|
type = JSON_ELEMENT_TYPE_STRING;
|
|
else if (json_is_object(ch))
|
|
type = JSON_ELEMENT_TYPE_OBJECT;
|
|
else if (json_is_array(ch))
|
|
type = JSON_ELEMENT_TYPE_ARRAY;
|
|
else if (json_is_null(ch))
|
|
type = JSON_ELEMENT_TYPE_NULL;
|
|
else if (json_is_number(ch))
|
|
type = JSON_ELEMENT_TYPE_NUMBER;
|
|
else if (json_is_boolean(ch))
|
|
type = JSON_ELEMENT_TYPE_BOOLEAN;
|
|
else
|
|
return result_err(json_element_type)(JSON_ERROR_INVALID_TYPE);
|
|
|
|
return result_ok(json_element_type)(type);
|
|
}
|
|
|
|
bool json_is_string(char ch) { return ch == '"'; }
|
|
|
|
bool json_is_number(char ch) {
|
|
return (ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.' ||
|
|
ch == 'e' || ch == 'E';
|
|
}
|
|
|
|
bool json_is_object(char ch) { return ch == '{'; }
|
|
|
|
bool json_is_array(char ch) { return ch == '['; }
|
|
|
|
bool json_is_boolean(char ch) { return ch == 't' || ch == 'f'; }
|
|
|
|
bool json_is_null(char ch) { return ch == 'n'; }
|
|
|
|
result(json_element_value)
|
|
json_parse_element_value(typed(json_string) * str_ptr,
|
|
typed(json_element_type) type) {
|
|
switch (type) {
|
|
case JSON_ELEMENT_TYPE_STRING:
|
|
return json_parse_string(str_ptr);
|
|
case JSON_ELEMENT_TYPE_NUMBER:
|
|
return json_parse_number(str_ptr);
|
|
case JSON_ELEMENT_TYPE_OBJECT:
|
|
return json_parse_object(str_ptr);
|
|
case JSON_ELEMENT_TYPE_ARRAY:
|
|
return json_parse_array(str_ptr);
|
|
case JSON_ELEMENT_TYPE_BOOLEAN:
|
|
return json_parse_boolean(str_ptr);
|
|
case JSON_ELEMENT_TYPE_NULL:
|
|
json_skip_null(str_ptr);
|
|
return result_err(json_element_value)(JSON_ERROR_EMPTY);
|
|
default:
|
|
return result_err(json_element_value)(JSON_ERROR_INVALID_TYPE);
|
|
}
|
|
}
|
|
|
|
result(json_element_value) json_parse_string(typed(json_string) * str_ptr) {
|
|
// Skip the first '"' character
|
|
(*str_ptr)++;
|
|
|
|
typed(size) len = json_string_len(*str_ptr);
|
|
if (len == 0) {
|
|
// Skip the end quote
|
|
(*str_ptr)++;
|
|
return result_err(json_element_value)(JSON_ERROR_EMPTY);
|
|
}
|
|
|
|
result_try(json_element_value, json_string, output,
|
|
json_unescape_string(*str_ptr, len));
|
|
|
|
// Skip to beyond the string
|
|
(*str_ptr) += len + 1;
|
|
|
|
typed(json_element_value) retval = {0};
|
|
retval.as_string = output;
|
|
|
|
return result_ok(json_element_value)(retval);
|
|
}
|
|
|
|
result(json_element_value) json_parse_number(typed(json_string) * str_ptr) {
|
|
typed(json_string) temp_str = *str_ptr;
|
|
bool has_decimal = false;
|
|
|
|
while (json_is_number(*temp_str)) {
|
|
if (*temp_str == '.') {
|
|
has_decimal = true;
|
|
}
|
|
|
|
temp_str++;
|
|
}
|
|
|
|
typed(json_number) number = {0};
|
|
typed(json_number_value) val = {0};
|
|
|
|
if (has_decimal) {
|
|
errno = 0;
|
|
|
|
val.as_double = strtod(*str_ptr, (char **)str_ptr);
|
|
|
|
number.type = JSON_NUMBER_TYPE_DOUBLE;
|
|
number.value = val;
|
|
|
|
if (errno == EINVAL || errno == ERANGE)
|
|
return result_err(json_element_value)(JSON_ERROR_INVALID_VALUE);
|
|
} else {
|
|
errno = 0;
|
|
|
|
val.as_long = strtol(*str_ptr, (char **)str_ptr, 10);
|
|
|
|
number.type = JSON_NUMBER_TYPE_LONG;
|
|
number.value = val;
|
|
|
|
if (errno == EINVAL || errno == ERANGE)
|
|
return result_err(json_element_value)(JSON_ERROR_INVALID_VALUE);
|
|
}
|
|
|
|
typed(json_element_value) retval = {0};
|
|
retval.as_number = number;
|
|
|
|
return result_ok(json_element_value)(retval);
|
|
}
|
|
|
|
result(json_element_value) json_parse_object(typed(json_string) * str_ptr) {
|
|
typed(json_string) temp_str = *str_ptr;
|
|
|
|
// ******* First find the number of valid entries *******
|
|
// Skip the first '{' character
|
|
temp_str++;
|
|
|
|
json_skip_whitespace(&temp_str);
|
|
|
|
if (*temp_str == '}') {
|
|
// Skip the end '}' in the actual pointer
|
|
(*str_ptr) = temp_str + 1;
|
|
return result_err(json_element_value)(JSON_ERROR_EMPTY);
|
|
}
|
|
|
|
typed(size) count = 0;
|
|
|
|
while (*temp_str != '\0') {
|
|
// Skip any accidental whitespace
|
|
json_skip_whitespace(&temp_str);
|
|
|
|
// If the entry could be skipped
|
|
if (json_skip_entry(&temp_str)) {
|
|
count++;
|
|
}
|
|
|
|
// Skip any accidental whitespace
|
|
json_skip_whitespace(&temp_str);
|
|
|
|
if (*temp_str == '}')
|
|
break;
|
|
|
|
// Skip the ',' to move to the next entry
|
|
temp_str++;
|
|
}
|
|
|
|
if (count == 0)
|
|
return result_err(json_element_value)(JSON_ERROR_EMPTY);
|
|
|
|
// ******* Initialize the hash map *******
|
|
// Now we have a perfectly sized hash map
|
|
typed(json_entry) **entries = allocN(typed(json_entry) *, count);
|
|
for (size_t i = 0; i < count; i++)
|
|
entries[i] = NULL;
|
|
|
|
// Skip the first '{' character
|
|
(*str_ptr)++;
|
|
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
while (**str_ptr != '\0') {
|
|
// Skip any accidental whitespace
|
|
json_skip_whitespace(str_ptr);
|
|
result(json_entry) entry_result = json_parse_entry(str_ptr);
|
|
|
|
if (result_is_ok(json_entry)(&entry_result)) {
|
|
typed(json_entry) entry = result_unwrap(json_entry)(&entry_result);
|
|
typed(uint64) bucket = json_key_hash(entry.key) % count;
|
|
|
|
// Bucket size is exactly count. So there will be at max
|
|
// count misses in the worst case
|
|
for (size_t i = 0; i < count; i++) {
|
|
if (entries[bucket] == NULL) {
|
|
typed(json_entry) *temp_entry = alloc(typed(json_entry));
|
|
memcpy(temp_entry, &entry, sizeof(typed(json_entry)));
|
|
entries[bucket] = temp_entry;
|
|
break;
|
|
}
|
|
|
|
bucket = (bucket + 1) % count;
|
|
}
|
|
}
|
|
|
|
// Skip any accidental whitespace
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
if (**str_ptr == '}')
|
|
break;
|
|
|
|
// Skip the ',' to move to the next entry
|
|
(*str_ptr)++;
|
|
}
|
|
|
|
// Skip the '}' closing brace
|
|
(*str_ptr)++;
|
|
|
|
typed(json_object) *object = alloc(typed(json_object));
|
|
object->count = count;
|
|
object->entries = entries;
|
|
|
|
typed(json_element_value) retval = {0};
|
|
retval.as_object = object;
|
|
|
|
return result_ok(json_element_value)(retval);
|
|
}
|
|
|
|
typed(uint64) json_key_hash(typed(json_string) str) {
|
|
typed(uint64) hash = 0;
|
|
|
|
while (*str != '\0')
|
|
hash += (hash * 31) + *str++;
|
|
|
|
return hash;
|
|
}
|
|
|
|
result(json_element_value) json_parse_array(typed(json_string) * str_ptr) {
|
|
// Skip the starting '[' character
|
|
(*str_ptr)++;
|
|
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
// Unfortunately the array is empty
|
|
if (**str_ptr == ']') {
|
|
// Skip the end ']'
|
|
(*str_ptr)++;
|
|
return result_err(json_element_value)(JSON_ERROR_EMPTY);
|
|
}
|
|
|
|
typed(size) count = 0;
|
|
typed(json_element) *elements = NULL;
|
|
|
|
while (**str_ptr != '\0') {
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
// Guess the type
|
|
result(json_element_type) type_result = json_guess_element_type(*str_ptr);
|
|
if (result_is_ok(json_element_type)(&type_result)) {
|
|
typed(json_element_type) type =
|
|
result_unwrap(json_element_type)(&type_result);
|
|
|
|
// Parse the value based on guessed type
|
|
result(json_element_value) value_result =
|
|
json_parse_element_value(str_ptr, type);
|
|
if (result_is_ok(json_element_value)(&value_result)) {
|
|
typed(json_element_value) value =
|
|
result_unwrap(json_element_value)(&value_result);
|
|
|
|
count++;
|
|
elements = reallocN(elements, typed(json_element), count);
|
|
elements[count - 1].type = type;
|
|
elements[count - 1].value = value;
|
|
}
|
|
|
|
json_skip_whitespace(str_ptr);
|
|
}
|
|
|
|
// Reached the end
|
|
if (**str_ptr == ']')
|
|
break;
|
|
|
|
// Skip the ','
|
|
(*str_ptr)++;
|
|
}
|
|
|
|
// Skip the ']' closing array
|
|
(*str_ptr)++;
|
|
|
|
if (count == 0)
|
|
return result_err(json_element_value)(JSON_ERROR_EMPTY);
|
|
|
|
typed(json_array) *array = alloc(typed(json_array));
|
|
array->count = count;
|
|
array->elements = elements;
|
|
|
|
typed(json_element_value) retval = {0};
|
|
retval.as_array = array;
|
|
|
|
return result_ok(json_element_value)(retval);
|
|
}
|
|
|
|
result(json_element_value) json_parse_boolean(typed(json_string) * str_ptr) {
|
|
typed(json_boolean) output;
|
|
|
|
switch (**str_ptr) {
|
|
case 't':
|
|
output = true;
|
|
(*str_ptr) += 4;
|
|
break;
|
|
|
|
case 'f':
|
|
output = false;
|
|
(*str_ptr) += 5;
|
|
break;
|
|
}
|
|
|
|
typed(json_element_value) retval = {0};
|
|
retval.as_boolean = output;
|
|
|
|
return result_ok(json_element_value)(retval);
|
|
}
|
|
|
|
result(json_element)
|
|
json_object_find(typed(json_object) * obj, typed(json_string) key) {
|
|
if (key == NULL || strlen(key) == 0)
|
|
return result_err(json_element)(JSON_ERROR_INVALID_KEY);
|
|
|
|
typed(uint64) bucket = json_key_hash(key) % obj->count;
|
|
|
|
// Bucket size is exactly obj->count. So there will be at max
|
|
// obj->count misses in the worst case
|
|
for (size_t i = 0; i < obj->count; i++) {
|
|
typed(json_entry) *entry = obj->entries[bucket];
|
|
if (strcmp(key, entry->key) == 0)
|
|
return result_ok(json_element)(entry->element);
|
|
|
|
bucket = (bucket + 1) % obj->count;
|
|
}
|
|
|
|
return result_err(json_element)(JSON_ERROR_INVALID_KEY);
|
|
}
|
|
|
|
bool json_skip_entry(typed(json_string) * str_ptr) {
|
|
json_skip_string(str_ptr);
|
|
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
// Skip the ':' delimiter
|
|
(*str_ptr)++;
|
|
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
result(json_element_type) type_result = json_guess_element_type(*str_ptr);
|
|
if (result_is_err(json_element_type)(&type_result))
|
|
return false;
|
|
|
|
typed(json_element_type) type =
|
|
result_unwrap(json_element_type)(&type_result);
|
|
|
|
return json_skip_element_value(str_ptr, type);
|
|
}
|
|
|
|
bool json_skip_element_value(typed(json_string) * str_ptr,
|
|
typed(json_element_type) type) {
|
|
switch (type) {
|
|
case JSON_ELEMENT_TYPE_STRING:
|
|
return json_skip_string(str_ptr);
|
|
case JSON_ELEMENT_TYPE_NUMBER:
|
|
return json_skip_number(str_ptr);
|
|
case JSON_ELEMENT_TYPE_OBJECT:
|
|
return json_skip_object(str_ptr);
|
|
case JSON_ELEMENT_TYPE_ARRAY:
|
|
return json_skip_array(str_ptr);
|
|
case JSON_ELEMENT_TYPE_BOOLEAN:
|
|
return json_skip_boolean(str_ptr);
|
|
case JSON_ELEMENT_TYPE_NULL:
|
|
json_skip_null(str_ptr);
|
|
return false;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool json_skip_string(typed(json_string) * str_ptr) {
|
|
// Skip the initial '"'
|
|
(*str_ptr)++;
|
|
|
|
// Find the length till the last '"'
|
|
typed(size) len = json_string_len(*str_ptr);
|
|
|
|
// Skip till the end of the string
|
|
(*str_ptr) += len + 1;
|
|
|
|
return len > 0;
|
|
}
|
|
|
|
bool json_skip_number(typed(json_string) * str_ptr) {
|
|
while (json_is_number(**str_ptr)) {
|
|
(*str_ptr)++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool json_skip_object(typed(json_string) * str_ptr) {
|
|
// Skip the first '{' character
|
|
(*str_ptr)++;
|
|
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
if (**str_ptr == '}') {
|
|
// Skip the end '}'
|
|
(*str_ptr)++;
|
|
return false;
|
|
}
|
|
|
|
while (**str_ptr != '\0') {
|
|
// Skip any accidental whitespace
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
json_skip_entry(str_ptr);
|
|
|
|
// Skip any accidental whitespace
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
if (**str_ptr == '}')
|
|
break;
|
|
|
|
// Skip the ',' to move to the next entry
|
|
(*str_ptr)++;
|
|
}
|
|
|
|
// Skip the '}' closing brace
|
|
(*str_ptr)++;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool json_skip_array(typed(json_string) * str_ptr) {
|
|
// Skip the starting '[' character
|
|
(*str_ptr)++;
|
|
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
// Unfortunately the array is empty
|
|
if (**str_ptr == ']') {
|
|
// Skip the end ']'
|
|
(*str_ptr)++;
|
|
return false;
|
|
}
|
|
|
|
while (**str_ptr != '\0') {
|
|
json_skip_whitespace(str_ptr);
|
|
|
|
// Guess the type
|
|
result(json_element_type) type_result = json_guess_element_type(*str_ptr);
|
|
if (result_is_ok(json_element_type)(&type_result)) {
|
|
typed(json_element_type) type =
|
|
result_unwrap(json_element_type)(&type_result);
|
|
|
|
// Parse the value based on guessed type
|
|
json_skip_element_value(str_ptr, type);
|
|
|
|
json_skip_whitespace(str_ptr);
|
|
}
|
|
|
|
// Reached the end
|
|
if (**str_ptr == ']')
|
|
break;
|
|
|
|
// Skip the ','
|
|
(*str_ptr)++;
|
|
}
|
|
|
|
// Skip the ']' closing array
|
|
(*str_ptr)++;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool json_skip_boolean(typed(json_string) * str_ptr) {
|
|
switch (**str_ptr) {
|
|
case 't':
|
|
(*str_ptr) += 4;
|
|
return true;
|
|
|
|
case 'f':
|
|
(*str_ptr) += 5;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void json_skip_null(typed(json_string) * str_ptr) { (*str_ptr) += 4; }
|
|
|
|
void json_print(typed(json_element) * element, int indent) {
|
|
json_print_element(element, indent, 0);
|
|
}
|
|
|
|
void json_print_element(typed(json_element) * element, int indent,
|
|
int indent_level) {
|
|
|
|
switch (element->type) {
|
|
case JSON_ELEMENT_TYPE_STRING:
|
|
json_print_string(element->value.as_string);
|
|
break;
|
|
case JSON_ELEMENT_TYPE_NUMBER:
|
|
json_print_number(element->value.as_number);
|
|
break;
|
|
case JSON_ELEMENT_TYPE_OBJECT:
|
|
json_print_object(element->value.as_object, indent, indent_level);
|
|
break;
|
|
case JSON_ELEMENT_TYPE_ARRAY:
|
|
json_print_array(element->value.as_array, indent, indent_level);
|
|
break;
|
|
case JSON_ELEMENT_TYPE_BOOLEAN:
|
|
json_print_boolean(element->value.as_boolean);
|
|
break;
|
|
case JSON_ELEMENT_TYPE_NULL:
|
|
break;
|
|
// Do nothing
|
|
}
|
|
}
|
|
|
|
void json_print_string(typed(json_string) string) { printf("\"%s\"", string); }
|
|
|
|
void json_print_number(typed(json_number) number) {
|
|
switch (number.type) {
|
|
case JSON_NUMBER_TYPE_DOUBLE:
|
|
printf("%f", number.value.as_double);
|
|
break;
|
|
|
|
case JSON_NUMBER_TYPE_LONG:
|
|
printf("%ld", number.value.as_long);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void json_print_object(typed(json_object) * object, int indent,
|
|
int indent_level) {
|
|
printf("{\n");
|
|
|
|
for (size_t i = 0; i < object->count; i++) {
|
|
for (int j = 0; j < indent * (indent_level + 1); j++)
|
|
printf(" ");
|
|
|
|
typed(json_entry) *entry = object->entries[i];
|
|
|
|
json_print_string(entry->key);
|
|
printf(": ");
|
|
json_print_element(&entry->element, indent, indent_level + 1);
|
|
|
|
if (i != object->count - 1)
|
|
printf(",");
|
|
printf("\n");
|
|
}
|
|
|
|
for (int j = 0; j < indent * indent_level; j++)
|
|
printf(" ");
|
|
printf("}");
|
|
}
|
|
|
|
void json_print_array(typed(json_array) * array, int indent, int indent_level) {
|
|
printf("[\n");
|
|
|
|
for (size_t i = 0; i < array->count; i++) {
|
|
typed(json_element) element = array->elements[i];
|
|
for (int j = 0; j < indent * (indent_level + 1); j++)
|
|
printf(" ");
|
|
json_print_element(&element, indent, indent_level + 1);
|
|
|
|
if (i != array->count - 1)
|
|
printf(",");
|
|
printf("\n");
|
|
}
|
|
|
|
for (int i = 0; i < indent * indent_level; i++)
|
|
printf(" ");
|
|
printf("]");
|
|
}
|
|
|
|
void json_print_boolean(typed(json_boolean) boolean) {
|
|
printf("%s", boolean ? "true" : "false");
|
|
}
|
|
|
|
void json_free(typed(json_element) * element) {
|
|
switch (element->type) {
|
|
case JSON_ELEMENT_TYPE_STRING:
|
|
json_free_string(element->value.as_string);
|
|
break;
|
|
|
|
case JSON_ELEMENT_TYPE_OBJECT:
|
|
json_free_object(element->value.as_object);
|
|
break;
|
|
|
|
case JSON_ELEMENT_TYPE_ARRAY:
|
|
json_free_array(element->value.as_array);
|
|
break;
|
|
|
|
case JSON_ELEMENT_TYPE_NUMBER:
|
|
case JSON_ELEMENT_TYPE_BOOLEAN:
|
|
case JSON_ELEMENT_TYPE_NULL:
|
|
// Do nothing
|
|
break;
|
|
}
|
|
}
|
|
|
|
void json_free_string(typed(json_string) string) { free((void *)string); }
|
|
|
|
void json_free_object(typed(json_object) * object) {
|
|
if (object == NULL)
|
|
return;
|
|
|
|
if (object->count == 0) {
|
|
free(object);
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < object->count; i++) {
|
|
typed(json_entry) *entry = object->entries[i];
|
|
|
|
if (entry != NULL) {
|
|
free((void *)entry->key);
|
|
json_free(&entry->element);
|
|
free(entry);
|
|
}
|
|
}
|
|
|
|
free(object->entries);
|
|
free(object);
|
|
}
|
|
|
|
void json_free_array(typed(json_array) * array) {
|
|
if (array == NULL)
|
|
return;
|
|
|
|
if (array->count == 0) {
|
|
free(array);
|
|
return;
|
|
}
|
|
|
|
// Recursively free each element in the array
|
|
for (size_t i = 0; i < array->count; i++) {
|
|
typed(json_element) element = array->elements[i];
|
|
json_free(&element);
|
|
}
|
|
|
|
// Lastly free
|
|
free(array->elements);
|
|
free(array);
|
|
}
|
|
|
|
typed(json_string) json_error_to_string(typed(json_error) error) {
|
|
switch (error) {
|
|
case JSON_ERROR_EMPTY:
|
|
return "Empty";
|
|
case JSON_ERROR_INVALID_KEY:
|
|
return "Invalid key";
|
|
case JSON_ERROR_INVALID_TYPE:
|
|
return "Invalid type";
|
|
case JSON_ERROR_INVALID_VALUE:
|
|
return "Invalid value";
|
|
|
|
default:
|
|
return "Unknown error";
|
|
}
|
|
}
|
|
|
|
typed(size) json_string_len(typed(json_string) str) {
|
|
typed(size) len = 0;
|
|
|
|
typed(json_string) iter = str;
|
|
while (*iter != '\0') {
|
|
if (*iter == '\\')
|
|
iter += 2;
|
|
|
|
if (*iter == '"') {
|
|
len = iter - str;
|
|
break;
|
|
}
|
|
|
|
iter++;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
result(json_string)
|
|
json_unescape_string(typed(json_string) str, typed(size) len) {
|
|
typed(size) count = 0;
|
|
typed(json_string) iter = str;
|
|
|
|
while ((size_t)(iter - str) < len) {
|
|
if (*iter == '\\')
|
|
iter++;
|
|
|
|
count++;
|
|
iter++;
|
|
}
|
|
|
|
char *output = allocN(char, count + 1);
|
|
typed(size) offset = 0;
|
|
iter = str;
|
|
|
|
while ((size_t)(iter - str) < len) {
|
|
if (*iter == '\\') {
|
|
iter++;
|
|
|
|
switch (*iter) {
|
|
case 'b':
|
|
output[offset] = '\b';
|
|
break;
|
|
case 'f':
|
|
output[offset] = '\f';
|
|
break;
|
|
case 'n':
|
|
output[offset] = '\n';
|
|
break;
|
|
case 'r':
|
|
output[offset] = '\r';
|
|
break;
|
|
case 't':
|
|
output[offset] = '\t';
|
|
break;
|
|
case '"':
|
|
output[offset] = '"';
|
|
break;
|
|
case '\\':
|
|
output[offset] = '\\';
|
|
break;
|
|
default:
|
|
return result_err(json_string)(JSON_ERROR_INVALID_VALUE);
|
|
}
|
|
} else {
|
|
output[offset] = *iter;
|
|
}
|
|
|
|
offset++;
|
|
iter++;
|
|
}
|
|
|
|
output[offset] = '\0';
|
|
return result_ok(json_string)((typed(json_string))output);
|
|
}
|
|
|
|
define_result_type(json_element_type)
|
|
define_result_type(json_element_value)
|
|
define_result_type(json_element)
|
|
define_result_type(json_entry)
|
|
define_result_type(json_string)
|
|
define_result_type(size)
|
|
|