From 95a0110e966835eb9d8e69817be094db7540c912 Mon Sep 17 00:00:00 2001 From: Sander Mertens Date: Fri, 21 Jan 2022 22:16:20 -0800 Subject: [PATCH] #608 support NaN and Inf values in serializers --- flecs.c | 42 ++++++++++-- flecs.h | 3 +- include/flecs/private/strbuf.h | 3 +- src/addons/expr/serialize.c | 4 +- src/addons/json/json.c | 2 +- src/addons/json/serialize.c | 8 +++ src/datastructures/strbuf.c | 28 +++++++- test/collections/project.json | 6 +- test/collections/src/Strbuf.c | 65 ++++++++++++++++++- test/collections/src/main.c | 22 ++++++- test/meta/project.json | 8 +++ test/meta/src/SerializeToExpr.c | 73 +++++++++++++++++++++ test/meta/src/SerializeToJson.c | 111 ++++++++++++++++++++++++++++++++ test/meta/src/main.c | 44 ++++++++++++- 14 files changed, 400 insertions(+), 19 deletions(-) diff --git a/flecs.c b/flecs.c index af52225e7..770fa2f5f 100644 --- a/flecs.c +++ b/flecs.c @@ -11077,6 +11077,7 @@ void flecs_bitset_swap( } #include +#include /** * stm32tpl -- STM32 C++ Template Peripheral Library @@ -11105,7 +11106,8 @@ static int ecs_strbuf_ftoa( ecs_strbuf_t *out, double f, - int precision) + int precision, + char nan_delim) { char buf[64]; char * ptr = buf; @@ -11113,6 +11115,25 @@ int ecs_strbuf_ftoa( char c; int64_t intPart; + if (isnan(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendstr(out, "NaN"); + return ecs_strbuf_appendch(out, nan_delim); + } else { + return ecs_strbuf_appendstr(out, "NaN"); + } + } + if (isinf(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendstr(out, "Inf"); + return ecs_strbuf_appendch(out, nan_delim); + } else { + return ecs_strbuf_appendstr(out, "Inf"); + } + } + if (precision > MAX_PRECISION) { precision = MAX_PRECISION; } @@ -11469,10 +11490,11 @@ bool ecs_strbuf_appendch( bool ecs_strbuf_appendflt( ecs_strbuf_t *b, - double flt) + double flt, + char nan_delim) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_strbuf_ftoa(b, flt, 2); + return ecs_strbuf_ftoa(b, flt, 2, nan_delim); } bool ecs_strbuf_appendstr_zerocpy( @@ -23089,10 +23111,10 @@ int expr_ser_primitive( ecs_strbuf_append(str, "%lld", *(int64_t*)base); break; case EcsF32: - ecs_strbuf_appendflt(str, (double)*(float*)base); + ecs_strbuf_appendflt(str, (double)*(float*)base, 0); break; case EcsF64: - ecs_strbuf_appendflt(str, *(double*)base); + ecs_strbuf_appendflt(str, *(double*)base, 0); break; case EcsIPtr: ecs_strbuf_append(str, "%i", *(intptr_t*)base); @@ -25328,7 +25350,7 @@ void json_number( ecs_strbuf_t *buf, double value) { - ecs_strbuf_appendflt(buf, value); + ecs_strbuf_appendflt(buf, value, '"'); } void json_true( @@ -25621,6 +25643,14 @@ int json_ser_type_op( /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; + case EcsOpF32: + ecs_strbuf_appendflt(str, + (ecs_f64_t)*(ecs_f32_t*)ECS_OFFSET(ptr, op->offset), '"'); + break; + case EcsOpF64: + ecs_strbuf_appendflt(str, + *(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"'); + break; case EcsOpEnum: if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; diff --git a/flecs.h b/flecs.h index 3870f39d9..487d78082 100644 --- a/flecs.h +++ b/flecs.h @@ -1710,7 +1710,8 @@ bool ecs_strbuf_appendch( FLECS_API bool ecs_strbuf_appendflt( ecs_strbuf_t *buffer, - double v); + double v, + char nan_delim); /* Append source buffer to destination buffer. * Returns false when max is reached, true when there is still space */ diff --git a/include/flecs/private/strbuf.h b/include/flecs/private/strbuf.h index ac33cd69d..390bf7920 100644 --- a/include/flecs/private/strbuf.h +++ b/include/flecs/private/strbuf.h @@ -115,7 +115,8 @@ bool ecs_strbuf_appendch( FLECS_API bool ecs_strbuf_appendflt( ecs_strbuf_t *buffer, - double v); + double v, + char nan_delim); /* Append source buffer to destination buffer. * Returns false when max is reached, true when there is still space */ diff --git a/src/addons/expr/serialize.c b/src/addons/expr/serialize.c index 57c645765..eb11d86a3 100644 --- a/src/addons/expr/serialize.c +++ b/src/addons/expr/serialize.c @@ -85,10 +85,10 @@ int expr_ser_primitive( ecs_strbuf_append(str, "%lld", *(int64_t*)base); break; case EcsF32: - ecs_strbuf_appendflt(str, (double)*(float*)base); + ecs_strbuf_appendflt(str, (double)*(float*)base, 0); break; case EcsF64: - ecs_strbuf_appendflt(str, *(double*)base); + ecs_strbuf_appendflt(str, *(double*)base, 0); break; case EcsIPtr: ecs_strbuf_append(str, "%i", *(intptr_t*)base); diff --git a/src/addons/json/json.c b/src/addons/json/json.c index eb66e6146..3c7f012a4 100644 --- a/src/addons/json/json.c +++ b/src/addons/json/json.c @@ -19,7 +19,7 @@ void json_number( ecs_strbuf_t *buf, double value) { - ecs_strbuf_appendflt(buf, value); + ecs_strbuf_appendflt(buf, value, '"'); } void json_true( diff --git a/src/addons/json/serialize.c b/src/addons/json/serialize.c index 8a75da14b..279dda12c 100644 --- a/src/addons/json/serialize.c +++ b/src/addons/json/serialize.c @@ -207,6 +207,14 @@ int json_ser_type_op( /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; + case EcsOpF32: + ecs_strbuf_appendflt(str, + (ecs_f64_t)*(ecs_f32_t*)ECS_OFFSET(ptr, op->offset), '"'); + break; + case EcsOpF64: + ecs_strbuf_appendflt(str, + *(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"'); + break; case EcsOpEnum: if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; diff --git a/src/datastructures/strbuf.c b/src/datastructures/strbuf.c index 86291e5e8..6c609517b 100644 --- a/src/datastructures/strbuf.c +++ b/src/datastructures/strbuf.c @@ -1,5 +1,6 @@ #include "../private_api.h" #include +#include /** * stm32tpl -- STM32 C++ Template Peripheral Library @@ -28,7 +29,8 @@ static int ecs_strbuf_ftoa( ecs_strbuf_t *out, double f, - int precision) + int precision, + char nan_delim) { char buf[64]; char * ptr = buf; @@ -36,6 +38,25 @@ int ecs_strbuf_ftoa( char c; int64_t intPart; + if (isnan(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendstr(out, "NaN"); + return ecs_strbuf_appendch(out, nan_delim); + } else { + return ecs_strbuf_appendstr(out, "NaN"); + } + } + if (isinf(f)) { + if (nan_delim) { + ecs_strbuf_appendch(out, nan_delim); + ecs_strbuf_appendstr(out, "Inf"); + return ecs_strbuf_appendch(out, nan_delim); + } else { + return ecs_strbuf_appendstr(out, "Inf"); + } + } + if (precision > MAX_PRECISION) { precision = MAX_PRECISION; } @@ -392,10 +413,11 @@ bool ecs_strbuf_appendch( bool ecs_strbuf_appendflt( ecs_strbuf_t *b, - double flt) + double flt, + char nan_delim) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_strbuf_ftoa(b, flt, 2); + return ecs_strbuf_ftoa(b, flt, 2, nan_delim); } bool ecs_strbuf_appendstr_zerocpy( diff --git a/test/collections/project.json b/test/collections/project.json index 4cfb85c60..1bf03dccb 100644 --- a/test/collections/project.json +++ b/test/collections/project.json @@ -121,7 +121,11 @@ "append_511_chars", "append_512_chars", "append_513_chars", - "append_flt" + "append_flt", + "append_nan", + "append_inf", + "append_nan_delim", + "append_inf_delim" ] }] } diff --git a/test/collections/src/Strbuf.c b/test/collections/src/Strbuf.c index e41e0a02b..29ad35acc 100644 --- a/test/collections/src/Strbuf.c +++ b/test/collections/src/Strbuf.c @@ -1,4 +1,5 @@ #include +#include void Strbuf_setup() { ecs_os_set_api_defaults(); @@ -234,10 +235,72 @@ void Strbuf_append_513_chars() { void Strbuf_append_flt() { ecs_strbuf_t b = ECS_STRBUF_INIT; - ecs_strbuf_appendflt(&b, 10.5); + ecs_strbuf_appendflt(&b, 10.5, 0); char *str = ecs_strbuf_get(&b); test_assert(str != NULL); test_str(str, "10.50"); ecs_os_free(str); } + +void Strbuf_append_nan() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendflt(&b, NAN, 0); + + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "NaN"); + ecs_os_free(str); +} + +void Strbuf_append_inf() { + { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendflt(&b, INFINITY, 0); + + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "Inf"); + ecs_os_free(str); + } + { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendflt(&b, -INFINITY, 0); + + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "Inf"); + ecs_os_free(str); + } +} + +void Strbuf_append_nan_delim() { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendflt(&b, NAN, '"'); + + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "\"NaN\""); + ecs_os_free(str); +} + +void Strbuf_append_inf_delim() { + { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendflt(&b, INFINITY, '"'); + + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "\"Inf\""); + ecs_os_free(str); + } + { + ecs_strbuf_t b = ECS_STRBUF_INIT; + ecs_strbuf_appendflt(&b, -INFINITY, '"'); + + char *str = ecs_strbuf_get(&b); + test_assert(str != NULL); + test_str(str, "\"Inf\""); + ecs_os_free(str); + } +} diff --git a/test/collections/src/main.c b/test/collections/src/main.c index 3148eda2a..f5a9aeb93 100644 --- a/test/collections/src/main.c +++ b/test/collections/src/main.c @@ -111,6 +111,10 @@ void Strbuf_append_511_chars(void); void Strbuf_append_512_chars(void); void Strbuf_append_513_chars(void); void Strbuf_append_flt(void); +void Strbuf_append_nan(void); +void Strbuf_append_inf(void); +void Strbuf_append_nan_delim(void); +void Strbuf_append_inf_delim(void); bake_test_case Vector_testcases[] = { { @@ -489,6 +493,22 @@ bake_test_case Strbuf_testcases[] = { { "append_flt", Strbuf_append_flt + }, + { + "append_nan", + Strbuf_append_nan + }, + { + "append_inf", + Strbuf_append_inf + }, + { + "append_nan_delim", + Strbuf_append_nan_delim + }, + { + "append_inf_delim", + Strbuf_append_inf_delim } }; @@ -518,7 +538,7 @@ static bake_test_suite suites[] = { "Strbuf", Strbuf_setup, NULL, - 19, + 23, Strbuf_testcases } }; diff --git a/test/meta/project.json b/test/meta/project.json index f1d914adf..a7faa63f7 100644 --- a/test/meta/project.json +++ b/test/meta/project.json @@ -358,6 +358,10 @@ "entity", "enum", "bitmask", + "float_nan", + "float_inf", + "double_nan", + "double_inf", "struct_enum", "struct_bitmask", "struct_i32", @@ -436,6 +440,10 @@ "struct_string", "struct_entity", "struct_entity_after_float", + "struct_float_nan", + "struct_float_inf", + "struct_double_nan", + "struct_double_inf", "struct_enum", "struct_bitmask", "struct_i32_i32", diff --git a/test/meta/src/SerializeToExpr.c b/test/meta/src/SerializeToExpr.c index d44279162..c8d66d123 100644 --- a/test/meta/src/SerializeToExpr.c +++ b/test/meta/src/SerializeToExpr.c @@ -1,4 +1,5 @@ #include +#include void SerializeToExpr_bool() { ecs_world_t *world = ecs_init(); @@ -410,6 +411,78 @@ void SerializeToExpr_double() { ecs_fini(world); } +void SerializeToExpr_float_nan() { + ecs_world_t *world = ecs_init(); + + { + ecs_f32_t value = NAN; + char *expr = ecs_ptr_to_expr(world, ecs_id(ecs_f32_t), &value); + test_assert(expr != NULL); + test_str(expr, "NaN"); + ecs_os_free(expr); + } + + ecs_fini(world); +} + +void SerializeToExpr_float_inf() { + ecs_world_t *world = ecs_init(); + + { + ecs_f32_t value = INFINITY; + char *expr = ecs_ptr_to_expr(world, ecs_id(ecs_f32_t), &value); + test_assert(expr != NULL); + test_str(expr, "Inf"); + ecs_os_free(expr); + } + + { + ecs_f32_t value = -INFINITY; + char *expr = ecs_ptr_to_expr(world, ecs_id(ecs_f32_t), &value); + test_assert(expr != NULL); + test_str(expr, "Inf"); + ecs_os_free(expr); + } + + ecs_fini(world); +} + +void SerializeToExpr_double_nan() { + ecs_world_t *world = ecs_init(); + + { + ecs_f64_t value = NAN; + char *expr = ecs_ptr_to_expr(world, ecs_id(ecs_f64_t), &value); + test_assert(expr != NULL); + test_str(expr, "NaN"); + ecs_os_free(expr); + } + + ecs_fini(world); +} + +void SerializeToExpr_double_inf() { + ecs_world_t *world = ecs_init(); + + { + ecs_f64_t value = INFINITY; + char *expr = ecs_ptr_to_expr(world, ecs_id(ecs_f64_t), &value); + test_assert(expr != NULL); + test_str(expr, "Inf"); + ecs_os_free(expr); + } + + { + ecs_f64_t value = -INFINITY; + char *expr = ecs_ptr_to_expr(world, ecs_id(ecs_f64_t), &value); + test_assert(expr != NULL); + test_str(expr, "Inf"); + ecs_os_free(expr); + } + + ecs_fini(world); +} + void SerializeToExpr_string() { ecs_world_t *world = ecs_init(); diff --git a/test/meta/src/SerializeToJson.c b/test/meta/src/SerializeToJson.c index e211b0cab..c5c0b7371 100644 --- a/test/meta/src/SerializeToJson.c +++ b/test/meta/src/SerializeToJson.c @@ -1,6 +1,7 @@ #include #include #include +#include void SerializeToJson_struct_bool() { typedef struct { @@ -357,6 +358,116 @@ void SerializeToJson_struct_double() { ecs_fini(world); } +void SerializeToJson_struct_float_nan() { + typedef struct { + ecs_f32_t x; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.name = "T", + .members = { + {"x", ecs_id(ecs_f32_t)} + } + }); + + T value = {NAN}; + char *expr = ecs_ptr_to_json(world, t, &value); + test_assert(expr != NULL); + test_str(expr, "{\"x\":\"NaN\"}"); + ecs_os_free(expr); + + ecs_fini(world); +} + +void SerializeToJson_struct_float_inf() { + typedef struct { + ecs_f32_t x; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.name = "T", + .members = { + {"x", ecs_id(ecs_f32_t)} + } + }); + + { + T value = {INFINITY}; + char *expr = ecs_ptr_to_json(world, t, &value); + test_assert(expr != NULL); + test_str(expr, "{\"x\":\"Inf\"}"); + ecs_os_free(expr); + } + { + T value = {-INFINITY}; + char *expr = ecs_ptr_to_json(world, t, &value); + test_assert(expr != NULL); + test_str(expr, "{\"x\":\"Inf\"}"); + ecs_os_free(expr); + } + + ecs_fini(world); +} + +void SerializeToJson_struct_double_nan() { + typedef struct { + ecs_f64_t x; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.name = "T", + .members = { + {"x", ecs_id(ecs_f64_t)} + } + }); + + T value = {NAN}; + char *expr = ecs_ptr_to_json(world, t, &value); + test_assert(expr != NULL); + test_str(expr, "{\"x\":\"NaN\"}"); + ecs_os_free(expr); + + ecs_fini(world); +} + +void SerializeToJson_struct_double_inf() { + typedef struct { + ecs_f64_t x; + } T; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t) { + .entity.name = "T", + .members = { + {"x", ecs_id(ecs_f64_t)} + } + }); + + { + T value = {INFINITY}; + char *expr = ecs_ptr_to_json(world, t, &value); + test_assert(expr != NULL); + test_str(expr, "{\"x\":\"Inf\"}"); + ecs_os_free(expr); + } + { + T value = {-INFINITY}; + char *expr = ecs_ptr_to_json(world, t, &value); + test_assert(expr != NULL); + test_str(expr, "{\"x\":\"Inf\"}"); + ecs_os_free(expr); + } + + ecs_fini(world); +} + void SerializeToJson_struct_string() { typedef struct { char* x; diff --git a/test/meta/src/main.c b/test/meta/src/main.c index bc2771a68..61e23eac8 100644 --- a/test/meta/src/main.c +++ b/test/meta/src/main.c @@ -333,6 +333,10 @@ void SerializeToExpr_string(void); void SerializeToExpr_entity(void); void SerializeToExpr_enum(void); void SerializeToExpr_bitmask(void); +void SerializeToExpr_float_nan(void); +void SerializeToExpr_float_inf(void); +void SerializeToExpr_double_nan(void); +void SerializeToExpr_double_inf(void); void SerializeToExpr_struct_enum(void); void SerializeToExpr_struct_bitmask(void); void SerializeToExpr_struct_i32(void); @@ -407,6 +411,10 @@ void SerializeToJson_struct_double(void); void SerializeToJson_struct_string(void); void SerializeToJson_struct_entity(void); void SerializeToJson_struct_entity_after_float(void); +void SerializeToJson_struct_float_nan(void); +void SerializeToJson_struct_float_inf(void); +void SerializeToJson_struct_double_nan(void); +void SerializeToJson_struct_double_inf(void); void SerializeToJson_struct_enum(void); void SerializeToJson_struct_bitmask(void); void SerializeToJson_struct_i32_i32(void); @@ -1735,6 +1743,22 @@ bake_test_case SerializeToExpr_testcases[] = { "bitmask", SerializeToExpr_bitmask }, + { + "float_nan", + SerializeToExpr_float_nan + }, + { + "float_inf", + SerializeToExpr_float_inf + }, + { + "double_nan", + SerializeToExpr_double_nan + }, + { + "double_inf", + SerializeToExpr_double_inf + }, { "struct_enum", SerializeToExpr_struct_enum @@ -2021,6 +2045,22 @@ bake_test_case SerializeToJson_testcases[] = { "struct_entity_after_float", SerializeToJson_struct_entity_after_float }, + { + "struct_float_nan", + SerializeToJson_struct_float_nan + }, + { + "struct_float_inf", + SerializeToJson_struct_float_inf + }, + { + "struct_double_nan", + SerializeToJson_struct_double_nan + }, + { + "struct_double_inf", + SerializeToJson_struct_double_inf + }, { "struct_enum", SerializeToJson_struct_enum @@ -2408,7 +2448,7 @@ static bake_test_suite suites[] = { "SerializeToExpr", NULL, NULL, - 40, + 44, SerializeToExpr_testcases }, { @@ -2422,7 +2462,7 @@ static bake_test_suite suites[] = { "SerializeToJson", NULL, NULL, - 60, + 64, SerializeToJson_testcases }, {