From 01a76c7d703650df7f39d21c704f5b7c4f41ca14 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 25 Feb 2021 20:08:20 -0500 Subject: Add numeric node construction and access API --- include/serd/serd.h | 63 ++++++++++++---- src/node.c | 64 +++++++++++++++- src/node.h | 5 ++ src/string.c | 12 --- test/test_node.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 307 insertions(+), 50 deletions(-) diff --git a/include/serd/serd.h b/include/serd/serd.h index e1a28fdf..2f349e90 100644 --- a/include/serd/serd.h +++ b/include/serd/serd.h @@ -314,18 +314,6 @@ SERD_API size_t serd_strlen(const char* SERD_NONNULL str, SerdNodeFlags* SERD_NULLABLE flags); -/** - Parse a string to a double. - - The API of this function is identical to the standard C strtod function, - except this function is locale-independent and always matches the lexical - format used in the Turtle grammar (the decimal point is always "."). -*/ -SERD_API -double -serd_strtod(const char* SERD_NONNULL str, - const char* SERD_NONNULL* SERD_NULLABLE endptr); - /** Decode a base64 string. @@ -649,7 +637,7 @@ serd_new_double(double d); Uses identical formatting to serd_new_double(), except with at most 9 significant digits (under 14 characters total). - @param f Float value to serialise. + @param f Float value of literal. @return A literal node with datatype xsd:float. */ SERD_API @@ -657,9 +645,9 @@ SerdNode* SERD_ALLOCATED serd_new_float(float f); /** - Create a new node by serialising `i` into an xsd:integer string + Create a new node by writing `i` as an xsd:integer string. - @param i Integer value to serialise. + @param i Integer value of literal. @param datatype Datatype of node, or NULL for xsd:integer. */ SERD_API @@ -682,6 +670,51 @@ serd_new_blob(const void* SERD_NONNULL buf, size_t size, const SerdNode* SERD_NULLABLE datatype); +/** + Return the value of `node` as a boolean. + + This will work for booleans, and numbers of any datatype if they are 0 or + 1. + + @return The value of `node` as a `bool`, or `false` on error. +*/ +SERD_API +bool +serd_get_boolean(const SerdNode* SERD_NONNULL node); + +/** + Return the value of `node` as a double. + + This will coerce numbers of any datatype to double, if the value fits. + + @return The value of `node` as a `double`, or NaN on error. +*/ +SERD_API +double +serd_get_double(const SerdNode* SERD_NONNULL node); + +/** + Return the value of `node` as a float. + + This will coerce numbers of any datatype to float, if the value fits. + + @return The value of `node` as a `float`, or NaN on error. +*/ +SERD_API +float +serd_get_float(const SerdNode* SERD_NONNULL node); + +/** + Return the value of `node` as a long (signed 64-bit integer). + + This will coerce numbers of any datatype to long, if the value fits. + + @return The value of `node` as a `int64_t`, or 0 on error. +*/ +SERD_API +int64_t +serd_get_integer(const SerdNode* SERD_NONNULL node); + /// Return a deep copy of `node` SERD_API SerdNode* SERD_ALLOCATED diff --git a/src/node.c b/src/node.c index db183fc0..f071fc97 100644 --- a/src/node.c +++ b/src/node.c @@ -276,6 +276,68 @@ serd_new_curie(const SerdStringView str) return serd_new_simple_node(SERD_CURIE, str); } +ExessVariant +serd_node_get_value_as(const SerdNode* const node, + const ExessDatatype value_type) +{ + const SerdNode* const datatype_node = serd_node_datatype(node); + + const ExessDatatype node_type = + datatype_node ? exess_datatype_from_uri(serd_node_string(datatype_node)) + : EXESS_NOTHING; + + if (!node_type) { + // No datatype, assume it matches and try reading the value directly + ExessVariant v = exess_make_nothing(EXESS_SUCCESS); + const ExessResult r = + exess_read_variant(&v, value_type, serd_node_string(node)); + + return r.status ? exess_make_nothing(r.status) : v; + } + + // Read the value from the node + ExessVariant v = exess_make_nothing(EXESS_SUCCESS); + const ExessResult r = + exess_read_variant(&v, node_type, serd_node_string(node)); + + // Coerce value to the desired type if possible + return r.status ? exess_make_nothing(r.status) + : exess_coerce(v, value_type, EXESS_REDUCE_PRECISION); +} + +bool +serd_get_boolean(const SerdNode* const node) +{ + const ExessVariant value = serd_node_get_value_as(node, EXESS_BOOLEAN); + + return (value.datatype == EXESS_BOOLEAN) ? *exess_get_boolean(&value) : false; +} + +double +serd_get_double(const SerdNode* const node) +{ + const ExessVariant value = serd_node_get_value_as(node, EXESS_DOUBLE); + + return (value.datatype == EXESS_DOUBLE) ? *exess_get_double(&value) + : (double)NAN; +} + +float +serd_get_float(const SerdNode* const node) +{ + const ExessVariant value = serd_node_get_value_as(node, EXESS_FLOAT); + + return (value.datatype == EXESS_FLOAT) ? *exess_get_float(&value) : NAN; +} + +int64_t +serd_get_integer(const SerdNode* const node) +{ + const ExessVariant value = serd_node_get_value_as(node, EXESS_LONG); + + return (value.datatype == EXESS_LONG) ? *exess_get_long(&value) : 0; +} + SerdNode* serd_node_copy(const SerdNode* node) { @@ -585,7 +647,7 @@ serd_new_decimal(const double d, const SerdNode* const datatype) } SerdNode* -serd_new_integer(int64_t i, const SerdNode* datatype) +serd_new_integer(const int64_t i, const SerdNode* datatype) { const ExessVariant variant = exess_make_long(i); const size_t len = exess_write_variant(variant, 0, NULL).count; diff --git a/src/node.h b/src/node.h index 6bebdb6d..3d01bae7 100644 --- a/src/node.h +++ b/src/node.h @@ -17,6 +17,7 @@ #ifndef SERD_NODE_H #define SERD_NODE_H +#include "exess/exess.h" #include "serd/serd.h" #include @@ -53,4 +54,8 @@ serd_node_zero_pad(SerdNode* SERD_NONNULL node); SerdNode* SERD_ALLOCATED serd_new_resolved_uri(SerdStringView string, SerdURIView base_uri); +ExessVariant +serd_node_get_value_as(const SerdNode* SERD_NONNULL node, + ExessDatatype value_type); + #endif // SERD_NODE_H diff --git a/src/string.c b/src/string.c index 6e28778b..f59a5fd6 100644 --- a/src/string.c +++ b/src/string.c @@ -107,15 +107,3 @@ serd_strlen(const char* str, SerdNodeFlags* flags) return strlen(str); } - -double -serd_strtod(const char* const str, const char** end) -{ - double value = (double)NAN; - const ExessResult r = exess_read_double(&value, str); - if (end) { - *end = str + r.count; - } - - return r.status ? (double)NAN : value; -} diff --git a/test/test_node.c b/test/test_node.c index 1e38ee9b..80c45b10 100644 --- a/test/test_node.c +++ b/test/test_node.c @@ -19,6 +19,7 @@ #include "serd/serd.h" #include +#include #include #include #include @@ -28,45 +29,206 @@ #define NS_XSD "http://www.w3.org/2001/XMLSchema#" static void -test_integer_to_node(void) +test_boolean(void) +{ + SerdNode* const true_node = serd_new_boolean(true); + assert(!strcmp(serd_node_string(true_node), "true")); + assert(serd_get_boolean(true_node)); + + const SerdNode* const true_datatype = serd_node_datatype(true_node); + assert(true_datatype); + assert(!strcmp(serd_node_string(true_datatype), NS_XSD "boolean")); + serd_node_free(true_node); + + SerdNode* const false_node = serd_new_boolean(false); + assert(!strcmp(serd_node_string(false_node), "false")); + assert(!serd_get_boolean(false_node)); + + const SerdNode* const false_datatype = serd_node_datatype(false_node); + assert(false_datatype); + assert(!strcmp(serd_node_string(false_datatype), NS_XSD "boolean")); + serd_node_free(false_node); +} + +static void +check_get_boolean(const char* string, + const char* datatype_uri, + const bool expected) +{ + SerdNode* const node = serd_new_typed_literal( + SERD_MEASURE_STRING(string), SERD_MEASURE_STRING(datatype_uri)); + + assert(node); + assert(serd_get_boolean(node) == expected); +} + +static void +test_get_boolean(void) +{ + check_get_boolean("false", NS_XSD "boolean", false); + check_get_boolean("true", NS_XSD "boolean", true); + check_get_boolean("0", NS_XSD "boolean", false); + check_get_boolean("1", NS_XSD "boolean", true); + check_get_boolean("0", NS_XSD "integer", false); + check_get_boolean("1", NS_XSD "integer", true); + check_get_boolean("0.0", NS_XSD "double", false); + check_get_boolean("1.0", NS_XSD "double", true); + check_get_boolean("unknown", NS_XSD "string", false); +} + +static void +test_double(void) +{ + const double test_values[] = {0.0, -0.0, 1.2, -2.3, 4567890}; + const char* test_strings[] = { + "0.0E0", "-0.0E0", "1.2E0", "-2.3E0", "4.56789E6"}; + + for (size_t i = 0; i < sizeof(test_values) / sizeof(double); ++i) { + SerdNode* node = serd_new_double(test_values[i]); + const char* node_str = serd_node_string(node); + assert(!strcmp(node_str, test_strings[i])); + + const size_t len = strlen(node_str); + assert(serd_node_length(node) == len); + + const SerdNode* const datatype = serd_node_datatype(node); + assert(datatype); + assert(!strcmp(serd_node_string(datatype), NS_XSD "double")); + + const double value = serd_get_double(node); + assert(!memcmp(&value, &test_values[i], sizeof(value))); + serd_node_free(node); + } +} + +static void +check_get_double(const char* string, + const char* datatype_uri, + const double expected) +{ + SerdNode* const node = serd_new_typed_literal( + SERD_MEASURE_STRING(string), SERD_MEASURE_STRING(datatype_uri)); + + assert(node); + + const double value = serd_get_double(node); + assert(!memcmp(&value, &expected, sizeof(value))); +} + +static void +test_get_double(void) { - const long int_test_nums[] = {0, -0, -23, 23, -12340, 1000, -1000}; + check_get_double("1.2", NS_XSD "double", 1.2); + check_get_double("-.5", NS_XSD "float", -0.5); + check_get_double("-67", NS_XSD "long", -67.0); + check_get_double("8.9", NS_XSD "decimal", 8.9); + check_get_double("false", NS_XSD "boolean", 0.0); + check_get_double("true", NS_XSD "boolean", 1.0); + + SerdNode* const nan = serd_new_string(SERD_MEASURE_STRING("unknown")); + assert(isnan(serd_get_double(nan))); + serd_node_free(nan); +} + +static void +test_float(void) +{ + const float test_values[] = {0.0, -0.0, 1.5, -2.5, 4567890}; + const char* test_strings[] = { + "0.0E0", "-0.0E0", "1.5E0", "-2.5E0", "4.56789E6"}; + + for (size_t i = 0; i < sizeof(test_values) / sizeof(float); ++i) { + SerdNode* node = serd_new_float(test_values[i]); + const char* node_str = serd_node_string(node); + assert(!strcmp(node_str, test_strings[i])); + + const size_t len = strlen(node_str); + assert(serd_node_length(node) == len); + + const SerdNode* const datatype = serd_node_datatype(node); + assert(datatype); + assert(!strcmp(serd_node_string(datatype), NS_XSD "float")); - const char* int_test_strs[] = { + const float value = serd_get_float(node); + assert(!memcmp(&value, &test_values[i], sizeof(value))); + serd_node_free(node); + } +} + +static void +check_get_float(const char* string, + const char* datatype_uri, + const float expected) +{ + SerdNode* const node = serd_new_typed_literal( + SERD_MEASURE_STRING(string), SERD_MEASURE_STRING(datatype_uri)); + + assert(node); + + const float value = serd_get_float(node); + assert(!memcmp(&value, &expected, sizeof(value))); +} + +static void +test_get_float(void) +{ + check_get_float("1.2", NS_XSD "float", 1.2f); + check_get_float("-.5", NS_XSD "float", -0.5f); + check_get_float("-67", NS_XSD "long", -67.0f); + check_get_float("1.5", NS_XSD "decimal", 1.5f); + check_get_float("false", NS_XSD "boolean", 0.0f); + check_get_float("true", NS_XSD "boolean", 1.0f); + + SerdNode* const nan = serd_new_string(SERD_MEASURE_STRING("unknown")); + assert(isnan(serd_get_float(nan))); + serd_node_free(nan); +} + +static void +test_integer(void) +{ + const int64_t test_values[] = {0, -0, -23, 23, -12340, 1000, -1000}; + const char* test_strings[] = { "0", "0", "-23", "23", "-12340", "1000", "-1000"}; - for (size_t i = 0; i < sizeof(int_test_nums) / sizeof(double); ++i) { - SerdNode* node = serd_new_integer(int_test_nums[i], NULL); + for (size_t i = 0; i < sizeof(test_values) / sizeof(double); ++i) { + SerdNode* node = serd_new_integer(test_values[i], NULL); const char* node_str = serd_node_string(node); - assert(!strcmp(node_str, int_test_strs[i])); + assert(!strcmp(node_str, test_strings[i])); const size_t len = strlen(node_str); assert(serd_node_length(node) == len); const SerdNode* const datatype = serd_node_datatype(node); assert(datatype); assert(!strcmp(serd_node_string(datatype), NS_XSD "integer")); + + assert(serd_get_integer(node) == test_values[i]); serd_node_free(node); } } static void -test_boolean(void) +check_get_integer(const char* string, + const char* datatype_uri, + const int64_t expected) { - SerdNode* const true_node = serd_new_boolean(true); - assert(!strcmp(serd_node_string(true_node), "true")); - - const SerdNode* const true_datatype = serd_node_datatype(true_node); - assert(true_datatype); - assert(!strcmp(serd_node_string(true_datatype), NS_XSD "boolean")); - serd_node_free(true_node); + SerdNode* const node = serd_new_typed_literal( + SERD_MEASURE_STRING(string), SERD_MEASURE_STRING(datatype_uri)); - SerdNode* const false_node = serd_new_boolean(false); - assert(!strcmp(serd_node_string(false_node), "false")); + assert(node); + assert(serd_get_integer(node) == expected); +} - const SerdNode* const false_datatype = serd_node_datatype(false_node); - assert(false_datatype); - assert(!strcmp(serd_node_string(false_datatype), NS_XSD "boolean")); - serd_node_free(false_node); +static void +test_get_integer(void) +{ + check_get_integer("12", NS_XSD "long", 12); + check_get_integer("-34", NS_XSD "long", -34); + check_get_integer("56", NS_XSD "integer", 56); + check_get_integer("false", NS_XSD "boolean", 0); + check_get_integer("true", NS_XSD "boolean", 1); + check_get_integer("78.0", NS_XSD "decimal", 78); + check_get_integer("unknown", NS_XSD "string", 0); } static void @@ -114,6 +276,7 @@ test_node_equals(void) SerdNode* lhs = serd_new_string(replacement_char); SerdNode* rhs = serd_new_string(SERD_STATIC_STRING("123")); + assert(serd_node_equals(lhs, lhs)); assert(!serd_node_equals(lhs, rhs)); SerdNode* const qnode = serd_new_curie(SERD_STATIC_STRING("foo:bar")); @@ -219,9 +382,15 @@ test_blank(void) int main(void) { - test_integer_to_node(); - test_blob_to_node(); test_boolean(); + test_get_boolean(); + test_double(); + test_get_double(); + test_float(); + test_get_float(); + test_integer(); + test_get_integer(); + test_blob_to_node(); test_node_equals(); test_node_from_string(); test_node_from_substring(); -- cgit v1.2.1