diff --git a/docs/examples/CMakeLists.txt b/docs/examples/CMakeLists.txt index b4f49c0..1cf18a4 100644 --- a/docs/examples/CMakeLists.txt +++ b/docs/examples/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8...3.14) add_subdirectory(echo-server) -set(TARGET_LIST child co_cpp_future_wait co_cpp_future test_delay chan_3 primes co_uv_fs co_uv_stream co_uv_listen co_uv_connect co_uv_spawn co_uv_url co_http_request co_http_response co_parse_http go_channel go_sleep go_select go_wait_group go_panic go_readfile go_multi_args) +set(TARGET_LIST child co_json co_cpp_future_wait co_cpp_future test_delay chan_3 primes co_uv_fs co_uv_stream co_uv_listen co_uv_connect co_uv_spawn co_uv_url co_http_request co_http_response co_parse_http go_channel go_sleep go_select go_wait_group go_panic go_readfile go_multi_args) foreach (TARGET ${TARGET_LIST}) add_executable(${TARGET} ${TARGET}.c) target_link_libraries(${TARGET} ze) diff --git a/docs/examples/co_json.c b/docs/examples/co_json.c new file mode 100644 index 0000000..634e560 --- /dev/null +++ b/docs/examples/co_json.c @@ -0,0 +1,14 @@ +#include "ze.h" + +int co_main(int argc, char **argv) { + json_t *encoded = json_encode("si.s.v", + kv("name", "John Smith"), + kv("age", 25), + kv("address.city", "Cupertino"), + kv("contact.emails", json_parse(json_for("ss", "email@example.com", "email2@example.com")))); + + if (is_json(encoded)) + puts(json_serialize(encoded, true)); + + return 0; +} diff --git a/include/compat/parson.h b/include/compat/parson.h index b701de4..5d78f77 100644 --- a/include/compat/parson.h +++ b/include/compat/parson.h @@ -244,6 +244,8 @@ JSON_Value *json_value_init_array(void); JSON_Value *json_value_init_string(const char *string); /* copies passed string */ JSON_Value *json_value_init_string_with_len(const char *string, size_t length); /* copies passed string, length shouldn't include last null character */ JSON_Value *json_value_init_number(double number); +JSON_Value *json_value_init_number_as_string(const char *number); +JSON_Value *json_value_init_number_as_string_with_len(const char *number_as_string, size_t number_as_string_length); JSON_Value *json_value_init_boolean(int boolean); JSON_Value *json_value_init_null(void); JSON_Value *json_value_deep_copy(const JSON_Value *value); @@ -255,6 +257,9 @@ JSON_Array *json_value_get_array(const JSON_Value *value); const char *json_value_get_string(const JSON_Value *value); size_t json_value_get_string_len(const JSON_Value *value); /* doesn't account for last null character */ double json_value_get_number(const JSON_Value *value); +const char *json_value_get_number_as_string(const JSON_Value *value); +size_t json_value_get_number_as_string_len(const JSON_Value *value); + int json_value_get_boolean(const JSON_Value *value); JSON_Value *json_value_get_parent(const JSON_Value *value); diff --git a/include/coroutine.h b/include/coroutine.h index 500f251..2139e30 100644 --- a/include/coroutine.h +++ b/include/coroutine.h @@ -38,6 +38,7 @@ #endif #include +#include "compat/parson.h" #if defined(_MSC_VER) #define ZE_MPROTECT 1 @@ -386,6 +387,7 @@ typedef struct routine_s routine_t; typedef struct oa_hash_s hash_t; typedef struct ex_ptr_s ex_ptr_t; typedef struct ex_context_s ex_context_t; +typedef struct json_value_t json_t; typedef hash_t ht_string_t; typedef hash_t wait_group_t; typedef hash_t wait_result_t; @@ -845,6 +847,14 @@ C_API u_string base64_decode(u_string_t src); C_API int co_array_init(co_array_t *); +C_API bool is_json(json_t *schema); +C_API string json_serialize(json_t *json, bool is_pretty); +C_API json_t *json_parse(string_t text); +C_API json_t *json_read(string_t filename); +C_API int json_write(string_t filename, string_t text); +C_API json_t *json_encode(string_t desc, ...); +C_API string json_for(string_t desc, ...); + /* Creates an unbuffered channel, similar to golang channels. */ C_API channel_t *channel(void); diff --git a/include/uv_routine.h b/include/uv_routine.h index 15cf2fc..a95d632 100644 --- a/include/uv_routine.h +++ b/include/uv_routine.h @@ -3,7 +3,6 @@ #include #include "uv_tls.h" -#include "compat/parson.h" #include /* Public API qualifier. */ @@ -186,6 +185,7 @@ C_API void uv_close_free(void *handle); C_API void coroutine_event_cleanup(void *handle); C_API char *fs_readfile(const char *path); +C_API int fs_write_file(const char *path, const char *text); C_API uv_file fs_open(const char *path, int flags, int mode); C_API int fs_close(uv_file fd); C_API uv_stat_t *fs_fstat(uv_file fd); diff --git a/src/json.c b/src/json.c new file mode 100644 index 0000000..0f0f4f7 --- /dev/null +++ b/src/json.c @@ -0,0 +1,238 @@ +#include "../include/ze.h" + +ZE_FORCE_INLINE bool is_json(json_t *schema) { + return (is_empty(schema) || json_value_get_type(schema) == JSONError) ? false : true; +} + +string json_serialize(json_t *json, bool is_pretty) { + string json_string = NULL; + if (!is_empty(json)) { + if (is_pretty) + json_string = json_serialize_to_string_pretty((const json_t *)json); + else + json_string = json_serialize_to_string((const json_t *)json); + + defer(json_free_serialized_string, json_string); + } + + return json_string; +} + +ZE_FORCE_INLINE int json_write(string_t filename, string_t text) { + return fs_write_file(filename, text); +} + +ZE_FORCE_INLINE json_t *json_parse(string_t text) { + return json_parse_string(text); +} + +json_t *json_read(string_t filename) { + string file_contents = fs_readfile(filename); + if (is_empty(file_contents)) + return NULL; + + return json_parse(file_contents); +} + +json_t *json_encode(string_t desc, ...) { + int count = (int)strlen(desc); + json_t *json_root = json_value_init_object(); + defer(json_value_free, json_root); + JSON_Object *json_object = json_value_get_object(json_root); + + va_list argp; + string key, value_char; + int value_bool; + JSON_Status status = JSONSuccess; + void_t value_any = NULL; + JSON_Array *value_array = NULL; + double value_float = 0; + int64_t value_int = 0; + size_t value_max = 0; + bool is_dot = false, is_array = false, is_double = false, is_int = false, is_max = false; + + va_start(argp, desc); + for (int i = 0; i < count; i++) { + switch (*desc++) { + case '.': + is_dot = true; + break; + case 'e': + if (is_array) { + is_array = false; + value_array = NULL; + is_dot = false; + } + break; + case 'a': + if (!is_array) { + key = va_arg(argp, string); + status = json_object_set_value(json_object, key, json_value_init_array()); + value_array = json_object_get_array(json_object, key); + is_array = true; + is_dot = false; + } + break; + case 'n': + if (!is_array) + key = va_arg(argp, string); + + if (is_array) + status = json_array_append_null(value_array); + else if (is_dot) + status = json_object_dotset_null(json_object, key); + else + status = json_object_set_null(json_object, key); + is_dot = false; + break; + case 'd': + is_int = true; + case 'f': + if (!is_int) + is_double = true; + case 'i': + if (!is_double && !is_int) + is_max = true; + + if (!is_array) + key = va_arg(argp, string); + + if (is_double) + value_float = va_arg(argp, double); + else if (is_int) + value_int = va_arg(argp, int64_t); + else + value_max = va_arg(argp, size_t); + + if (is_array) + status = json_array_append_number(value_array, (is_double ? value_float + : is_int ? (int)value_int + : (unsigned long)value_max)); + else if (is_dot) + status = json_object_dotset_number(json_object, key, (is_double ? value_float + : is_int ? (int)value_int + : (unsigned long)value_max)); + else + status = json_object_set_number(json_object, key, (is_double ? value_float + : is_int ? (int)value_int + : (unsigned long)value_max)); + + is_dot = false; + is_double = false; + is_int = false; + is_max = false; + break; + case 'b': + if (!is_array) + key = va_arg(argp, string); + + value_bool = va_arg(argp, int); + if (is_array) + status = json_array_append_boolean(value_array, value_bool); + else if (is_dot) + status = json_object_dotset_boolean(json_object, key, value_bool); + else + status = json_object_set_boolean(json_object, key, value_bool); + is_dot = false; + break; + case 's': + if (!is_array) + key = va_arg(argp, string); + + value_char = va_arg(argp, string); + if (is_array) + status = json_array_append_string(value_array, value_char); + else if (is_dot) + status = json_object_dotset_string(json_object, key, value_char); + else + status = json_object_set_string(json_object, key, value_char); + is_dot = false; + break; + case 'v': + if (!is_array) + key = va_arg(argp, string); + + value_any = va_arg(argp, void_t); + if (is_array) + status = json_array_append_value(value_array, value_any); + else if (is_dot) + status = json_object_dotset_value(json_object, key, value_any); + else + status = json_object_set_value(json_object, key, value_any); + is_dot = false; + break; + default: + break; + } + } + va_end(argp); + + return json_root; +} + +string json_for(string_t desc, ...) { + int count = (int)strlen(desc); + json_t *json_root = json_value_init_object(); + defer(json_value_free, json_root); + JSON_Object *json_object = json_value_get_object(json_root); + JSON_Status status = json_object_set_value(json_object, "array", json_value_init_array()); + JSON_Array *value_array = json_object_get_array(json_object, "array"); + + va_list argp; + string value_char; + int value_bool; + void_t value_any = NULL; + double value_float = 0; + int64_t value_int = 0; + size_t value_max = 0; + bool is_double = false, is_int = false, is_max = false; + + va_start(argp, desc); + for (int i = 0; i < count; i++) { + switch (*desc++) { + case 'n': + status = json_array_append_null(value_array); + break; + case 'd': + is_int = true; + case 'f': + if (!is_int) + is_double = true; + case 'i': + if (!is_double && !is_int) + is_max = true; + + if (is_double) + value_float = va_arg(argp, double); + else if (is_int) + value_int = va_arg(argp, int64_t); + else + value_max = va_arg(argp, size_t); + + status = json_array_append_number(value_array, (is_double ? value_float + : is_int ? (int)value_int + : (unsigned long)value_max)); + is_double = false; + is_int = false; + is_max = false; + break; + case 'b': + value_bool = va_arg(argp, int); + status = json_array_append_boolean(value_array, value_bool); + break; + case 's': + value_char = va_arg(argp, string); + status = json_array_append_string(value_array, value_char); + break; + case 'v': + value_any = va_arg(argp, void_t); + status = json_array_append_value(value_array, value_any); + break; + default: + break; + } + } + va_end(argp); + + return json_serialize(json_array_get_wrapping_value(value_array), false); +} diff --git a/src/parson.c b/src/parson.c index 11b3c86..f508c99 100644 --- a/src/parson.c +++ b/src/parson.c @@ -113,10 +113,15 @@ typedef struct json_string { size_t length; } JSON_String; +typedef struct json_number { + double number; + JSON_String number_as_string; +} JSON_Number; + /* Type definitions */ typedef union json_value_value { JSON_String string; - double number; + JSON_Number number; JSON_Object *object; JSON_Array *array; int boolean; @@ -183,6 +188,7 @@ static void json_array_free(JSON_Array *array); /* JSON Value */ static JSON_Value *json_value_init_string_no_copy(char *string, size_t length); +static JSON_Value *json_value_init_number_as_string_no_copy(double number, char *number_as_string, size_t number_as_string_length); static const JSON_String *json_value_get_string_desc(const JSON_Value *value); /* Parser */ @@ -200,7 +206,7 @@ static JSON_Value *parse_value(const char **string, size_t nesting); /* Serialization */ static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, parson_bool_t is_pretty, char *num_buf); -static int json_serialize_string(const char *string, size_t len, char *buf); +static int json_serialize_string(const char *string, size_t len, int quotes, char *buf); /* Various */ static char *read_file(const char *filename) { @@ -783,6 +789,19 @@ static JSON_Value *json_value_init_string_no_copy(char *string, size_t length) { return new_value; } +static JSON_Value *json_value_init_number_as_string_no_copy(double number, char *number_as_string, size_t number_as_string_length) { + JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONNumber; + new_value->value.number.number = number; + new_value->value.number.number_as_string.chars = number_as_string; + new_value->value.number.number_as_string.length = number_as_string_length; + return new_value; +} + /* Parser */ static JSON_Status skip_quotes(const char **string) { if (**string != '\"') { @@ -1098,17 +1117,13 @@ static JSON_Value *parse_boolean_value(const char **string) { static JSON_Value *parse_number_value(const char **string) { char *end; - double number = 0; - errno = 0; - number = strtod(*string, &end); - if (errno == ERANGE && (number <= -HUGE_VAL || number >= HUGE_VAL)) { - return NULL; - } - if ((errno && errno != ERANGE) || !is_decimal(*string, end - *string)) { - return NULL; - } + JSON_Value *value; + size_t length; + strtod(*string, &end); + length = end - *string; + value = json_value_init_number_as_string_with_len(*string, length); *string = end; - return json_value_init_number(number); + return value; } static JSON_Value *parse_null_value(const char **string) { @@ -1202,7 +1217,7 @@ static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int le APPEND_INDENT(level + 1); } /* We do not support key names with embedded \0 chars */ - written = json_serialize_string(key, strlen(key), buf); + written = json_serialize_string(key, strlen(key), PARSON_TRUE, buf); if (written < 0) { return -1; } @@ -1241,7 +1256,7 @@ static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int le return -1; } len = json_value_get_string_len(value); - written = json_serialize_string(string, len, buf); + written = json_serialize_string(string, len, PARSON_TRUE, buf); if (written < 0) { return -1; } @@ -1258,15 +1273,22 @@ static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int le } return written_total; case JSONNumber: - num = json_value_get_number(value); if (buf != NULL) { num_buf = buf; } if (parson_number_serialization_function) { + num = json_value_get_number(value); written = parson_number_serialization_function(num, num_buf); + } else if (parson_float_format) { + num = json_value_get_number(value); + written = sprintf(num_buf, parson_float_format, num); } else { - const char *float_format = parson_float_format ? parson_float_format : PARSON_DEFAULT_FLOAT_FORMAT; - written = parson_sprintf(num_buf, float_format, num); + string = json_value_get_number_as_string(value); + if (string == NULL) { + return -1; + } + len = json_value_get_number_as_string_len(value); + written = json_serialize_string(string, len, PARSON_FALSE, num_buf); } if (written < 0) { return -1; @@ -1286,11 +1308,13 @@ static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int le } } -static int json_serialize_string(const char *string, size_t len, char *buf) { +static int json_serialize_string(const char *string, size_t len, int quotes, char *buf) { size_t i = 0; char c = '\0'; int written = -1, written_total = 0; - APPEND_STRING("\""); + if (quotes) { + APPEND_STRING("\""); + } for (i = 0; i < len; i++) { c = string[i]; switch (c) { @@ -1349,7 +1373,9 @@ static int json_serialize_string(const char *string, size_t len, char *buf) { break; } } - APPEND_STRING("\""); + if (quotes) { + APPEND_STRING("\""); + } return written_total; } @@ -1584,7 +1610,21 @@ size_t json_value_get_string_len(const JSON_Value *value) { } double json_value_get_number(const JSON_Value *value) { - return json_value_get_type(value) == JSONNumber ? value->value.number : 0; + return json_value_get_type(value) == JSONNumber ? value->value.number.number : 0; +} + +static const JSON_String *json_value_get_number_as_string_desc(const JSON_Value *value) { + return json_value_get_type(value) == JSONNumber ? &value->value.number.number_as_string : NULL; +} + +const char *json_value_get_number_as_string(const JSON_Value *value) { + const JSON_String *str = json_value_get_number_as_string_desc(value); + return str ? str->chars : NULL; +} + +size_t json_value_get_number_as_string_len(const JSON_Value *value) { + const JSON_String *str = json_value_get_number_as_string_desc(value); + return str ? str->length : 0; } int json_value_get_boolean(const JSON_Value *value) { @@ -1600,6 +1640,9 @@ void json_value_free(JSON_Value *value) { case JSONObject: json_object_free(value->value.object); break; + case JSONNumber: + parson_free(value->value.number.number_as_string.chars); + break; case JSONString: parson_free(value->value.string.chars); break; @@ -1671,19 +1714,67 @@ JSON_Value *json_value_init_string_with_len(const char *string, size_t length) { JSON_Value *json_value_init_number(double number) { JSON_Value *new_value = NULL; + char number_as_string[PARSON_NUM_BUF_SIZE]; + char *number_as_string_copy = NULL; + size_t number_as_string_length = -1; if (IS_NUMBER_INVALID(number)) { return NULL; } - new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); - if (new_value == NULL) { + if (parson_number_serialization_function) { + number_as_string_length = parson_number_serialization_function(number, number_as_string); + } else if (parson_float_format) { + number_as_string_length = sprintf(number_as_string, parson_float_format, number); + } else { + number_as_string_length = sprintf(number_as_string, PARSON_DEFAULT_FLOAT_FORMAT, number); + } + number_as_string_copy = parson_strndup(number_as_string, number_as_string_length); + if (!number_as_string_copy) { return NULL; } - new_value->parent = NULL; - new_value->type = JSONNumber; - new_value->value.number = number; + new_value = json_value_init_number_as_string_no_copy(number, number_as_string_copy, number_as_string_length); + if (!new_value) { + parson_free(number_as_string_copy); + } return new_value; } +JSON_Value *json_value_init_number_as_string(const char *number) { + if (number == NULL) { + return NULL; + } + return json_value_init_number_as_string_with_len(number, strlen(number)); +} + +JSON_Value *json_value_init_number_as_string_with_len(const char *number_as_string, size_t number_as_string_length) { + double number; + char *number_as_string_copy; + char *end = NULL; + JSON_Value *value = NULL; + errno = 0; + if (number_as_string == NULL) { + return NULL; + } + /* double representation */ + number = strtod(number_as_string, &end); + if (errno == ERANGE && (number <= -HUGE_VAL || number >= HUGE_VAL)) { + return NULL; + } + if ((errno && errno != ERANGE) || !is_decimal(number_as_string, number_as_string_length)) { + return NULL; + } + /* string representation */ + number_as_string_copy = parson_strndup(number_as_string, number_as_string_length); + if (number_as_string_copy == NULL) { + return NULL; + } + /* combined representations */ + value = json_value_init_number_as_string_no_copy(number, number_as_string_copy, number_as_string_length); + if (!value) { + parson_free(number_as_string_copy); + } + return value; +} + JSON_Value *json_value_init_boolean(int boolean) { JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); if (!new_value) { @@ -1771,7 +1862,19 @@ JSON_Value *json_value_deep_copy(const JSON_Value *value) { case JSONBoolean: return json_value_init_boolean(json_value_get_boolean(value)); case JSONNumber: - return json_value_init_number(json_value_get_number(value)); + temp_string = json_value_get_number_as_string_desc(value); + if (temp_string == NULL) { + return NULL; + } + temp_string_copy = parson_strndup(temp_string->chars, temp_string->length); + if (temp_string_copy == NULL) { + return NULL; + } + return_value = json_value_init_number_as_string_no_copy(json_value_get_number(value), temp_string_copy, temp_string->length); + if (return_value == NULL) { + parson_free(temp_string_copy); + } + return return_value; case JSONString: temp_string = json_value_get_string_desc(value); if (temp_string == NULL) { diff --git a/src/scheduler.c b/src/scheduler.c index 3b2fb67..f16fc08 100644 --- a/src/scheduler.c +++ b/src/scheduler.c @@ -1280,6 +1280,7 @@ int main(int argc, char **argv) { ZE_INFO("System starting up: %s\n\n", co_system_uname()); #endif ex_signal_setup(); + json_set_allocation_functions(try_malloc, ZE_FREE); coroutine_create(coroutine_main, NULL, ZE_MAIN_STACK); coroutine_scheduler(); fprintf(stderr, "Coroutine scheduler returned to main, when it shouldn't have!"); diff --git a/src/uv_routine.c b/src/uv_routine.c index fdc3e98..9742367 100644 --- a/src/uv_routine.c +++ b/src/uv_routine.c @@ -774,7 +774,19 @@ string fs_readfile(string_t path) { return file; } - return (string)0; + return NULL; +} + +int fs_write_file(string_t path, string_t text) { + int status; + uv_file fd = fs_open(path, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + if (fd) { + status = fs_write(fd, text, 0); + if (!fs_close(fd)) + return status; + } + + return co_err_code(); } void stream_handler(void (*connected)(uv_stream_t *), uv_stream_t *client) {