diff options
-rw-r--r-- | .clang-tidy | 5 | ||||
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | include/serd/node.h | 69 | ||||
-rw-r--r-- | include/serd/string.h | 11 | ||||
-rw-r--r-- | src/node.c | 104 | ||||
-rw-r--r-- | src/node.h | 7 | ||||
-rw-r--r-- | src/string.c | 14 | ||||
-rw-r--r-- | test/.clang-tidy | 3 | ||||
-rw-r--r-- | test/test_node.c | 329 |
9 files changed, 450 insertions, 93 deletions
diff --git a/.clang-tidy b/.clang-tidy index 39e09c64..0c240213 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -10,6 +10,11 @@ Checks: > -llvmlibc-*, -modernize-macro-to-enum, -readability-identifier-length, +CheckOptions: + - key: hicpp-uppercase-literal-suffix.NewSuffixes + value: 'L;U;UL;ULL' + - key: readability-uppercase-literal-suffix.NewSuffixes + value: 'L;U;UL;ULL' FormatStyle: file HeaderFilterRegex: '.*/serd/.*\.h' WarningsAsErrors: '*' @@ -1,6 +1,7 @@ serd (1.1.1) unstable; urgency=medium * Add SerdBuffer for mutable buffers to keep SerdChunk const-correct + * Add support for xsd:float and xsd:double literals * Bring read/write interface closer to C standard * Make nodes opaque * Remove SERD_DISABLE_DEPRECATED and SERD_DEPRECATED_BY diff --git a/include/serd/node.h b/include/serd/node.h index 79c9ffe1..427bbdd1 100644 --- a/include/serd/node.h +++ b/include/serd/node.h @@ -189,6 +189,34 @@ SERD_API SerdNode* SERD_ALLOCATED serd_new_decimal(double d, const SerdNode* SERD_NULLABLE datatype); /** + Create a new canonical xsd:double literal. + + The node will be in scientific notation, like "1.23E4", except for NaN and + negative/positive infinity, which are "NaN", "-INF", and "INF", + respectively. + + Uses the shortest possible representation that precisely describes the + value, which has at most 17 significant digits (under 24 characters total). + + @param d Double value to write. + @return A literal node with datatype xsd:double. +*/ +SERD_API SerdNode* SERD_ALLOCATED +serd_new_double(double d); + +/** + Create a new canonical xsd:float literal. + + Uses identical formatting to serd_new_double(), except with at most 9 + significant digits (under 14 characters total). + + @param f Float value of literal. + @return A literal node with datatype xsd:float. +*/ +SERD_API SerdNode* SERD_ALLOCATED +serd_new_float(float f); + +/** Create a new canonical xsd:integer literal. The node will be an xsd:integer literal like "1234", with datatype @@ -291,6 +319,47 @@ SERD_PURE_API const SerdNode* SERD_NULLABLE serd_node_language(const SerdNode* SERD_NONNULL node); /** + 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); + +/** @} @defgroup serd_node_operators Operators @{ diff --git a/include/serd/string.h b/include/serd/string.h index b5ba7154..f0b8e591 100644 --- a/include/serd/string.h +++ b/include/serd/string.h @@ -28,17 +28,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. This function can be used to decode a node created with serd_new_base64(). @@ -16,6 +16,7 @@ #include "serd/uri.h" #include <assert.h> +#include <math.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> @@ -306,6 +307,85 @@ serd_new_curie(const SerdStringView str) return serd_new_token(SERD_CURIE, str); } +ExessResult +serd_node_get_value_as(const SerdNode* const node, + const ExessDatatype value_type, + const size_t value_size, + void* const value) +{ + 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 == EXESS_NOTHING || + (node_type == EXESS_HEX && value_type == EXESS_BASE64) || + (node_type == EXESS_BASE64 && value_type == EXESS_HEX)) { + // Try to read the large or untyped node string directly into the result + const ExessVariableResult vr = + exess_read_value(value_type, value_size, value, serd_node_string(node)); + + const ExessResult r = {vr.status, vr.write_count}; + return r; + } + + // Read the (smallish) value from the node + ExessValue node_value = {false}; + const ExessVariableResult vr = exess_read_value( + node_type, sizeof(node_value), &node_value, serd_node_string(node)); + + if (vr.status) { + const ExessResult r = {vr.status, 0U}; + return r; + } + + // Coerce value to the desired type if possible + return exess_value_coerce(EXESS_REDUCE_PRECISION, + node_type, + vr.write_count, + &node_value, + value_type, + value_size, + value); +} + +bool +serd_get_boolean(const SerdNode* const node) +{ + bool value = false; + serd_node_get_value_as(node, EXESS_BOOLEAN, sizeof(value), &value); + + return value; +} + +double +serd_get_double(const SerdNode* const node) +{ + double value = (double)NAN; // NOLINT(google-readability-casting) + serd_node_get_value_as(node, EXESS_DOUBLE, sizeof(value), &value); + + return value; +} + +float +serd_get_float(const SerdNode* const node) +{ + float value = (float)NAN; // NOLINT(google-readability-casting) + serd_node_get_value_as(node, EXESS_FLOAT, sizeof(value), &value); + + return value; +} + +int64_t +serd_get_integer(const SerdNode* const node) +{ + int64_t value = 0; + serd_node_get_value_as(node, EXESS_LONG, sizeof(value), &value); + + return value; +} + SerdNode* serd_node_copy(const SerdNode* node) { @@ -534,6 +614,30 @@ serd_new_custom_literal(const void* const user_data, } SerdNode* +serd_new_double(const double d) +{ + char buf[EXESS_MAX_DOUBLE_LENGTH + 1] = {0}; + + const ExessResult r = exess_write_double(d, sizeof(buf), buf); + + return r.status ? NULL + : serd_new_typed_literal(serd_substring(buf, r.count), + serd_string(EXESS_XSD_URI "double")); +} + +SerdNode* +serd_new_float(const float f) +{ + char buf[EXESS_MAX_FLOAT_LENGTH + 1] = {0}; + + const ExessResult r = exess_write_float(f, sizeof(buf), buf); + + return r.status ? NULL + : serd_new_typed_literal(serd_substring(buf, r.count), + serd_string(EXESS_XSD_URI "float")); +} + +SerdNode* serd_new_decimal(const double d, const SerdNode* const datatype) { // Use given datatype, or xsd:decimal as a default if it is null @@ -4,6 +4,7 @@ #ifndef SERD_SRC_NODE_H #define SERD_SRC_NODE_H +#include "exess/exess.h" #include "serd/attributes.h" #include "serd/node.h" #include "serd/string_view.h" @@ -46,4 +47,10 @@ serd_node_zero_pad(SerdNode* SERD_NONNULL node); SerdNode* SERD_ALLOCATED serd_new_resolved_uri(SerdStringView string, SerdURIView base_uri); +ExessResult +serd_node_get_value_as(const SerdNode* SERD_NONNULL node, + ExessDatatype value_type, + size_t value_size, + void* SERD_NONNULL value); + #endif // SERD_SRC_NODE_H diff --git a/src/string.c b/src/string.c index ec608f25..e13c0c01 100644 --- a/src/string.c +++ b/src/string.c @@ -3,14 +3,12 @@ #include "string_utils.h" -#include "exess/exess.h" #include "serd/memory.h" #include "serd/node.h" #include "serd/status.h" #include "serd/string.h" #include <assert.h> -#include <math.h> #include <stdlib.h> #include <string.h> @@ -115,15 +113,3 @@ serd_strlen(const char* const str, SerdNodeFlags* const flags) return strlen(str); } - -double -serd_strtod(const char* const str, const char** const 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/.clang-tidy b/test/.clang-tidy index 9710cb77..811dfa30 100644 --- a/test/.clang-tidy +++ b/test/.clang-tidy @@ -5,7 +5,10 @@ Checks: > -*-magic-numbers, -android-cloexec-fopen, -bugprone-easily-swappable-parameters, + -bugprone-suspicious-memory-comparison, -cert-err33-c, + -cert-exp42-c, + -cert-flp37-c, -clang-analyzer-nullability.NullableDereferenced, -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, -concurrency-mt-unsafe, diff --git a/test/test_node.c b/test/test_node.c index 9660f43e..f0092553 100644 --- a/test/test_node.c +++ b/test/test_node.c @@ -10,7 +10,6 @@ #include "serd/uri.h" #include <assert.h> -#include <float.h> #include <math.h> #include <stdbool.h> #include <stdint.h> @@ -21,116 +20,305 @@ #define NS_XSD "http://www.w3.org/2001/XMLSchema#" #define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +#if defined(__clang__) + +# define SERD_DISABLE_CONVERSION_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wconversion\"") \ + _Pragma("clang diagnostic ignored \"-Wdouble-promotion\"") \ + _Pragma("clang diagnostic ignored \"-Wc11-extensions\"") + +# define SERD_RESTORE_WARNINGS _Pragma("clang diagnostic pop") + +#elif defined(__GNUC__) && __GNUC__ >= 8 + +# define SERD_DISABLE_CONVERSION_WARNINGS \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wconversion\"") \ + _Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"") \ + _Pragma("GCC diagnostic ignored \"-Wdouble-promotion\"") + +# define SERD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop") + +#else + +# define SERD_DISABLE_CONVERSION_WARNINGS +# define SERD_RESTORE_WARNINGS + +#endif + static void -check_strtod(const double dbl, const double max_delta) +test_boolean(void) { - char buf[1024]; - snprintf(buf, sizeof(buf), "%f", dbl); + SerdNode* const true_node = serd_new_boolean(true); + assert(!strcmp(serd_node_string(true_node), "true")); + assert(serd_get_boolean(true_node)); - const char* endptr = NULL; - const double out = serd_strtod(buf, &endptr); - const double diff = fabs(out - dbl); + 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)); - assert(diff <= max_delta); + 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_strtod(void) +check_get_boolean(const char* string, + const char* datatype_uri, + const bool expected) { - const double expt_test_nums[] = { - 2.0E18, -5e19, +8e20, 2e+22, -5e-5, 8e0, 9e-0, 2e+0}; + SerdNode* const node = + serd_new_typed_literal(serd_string(string), serd_string(datatype_uri)); - const char* expt_test_strs[] = { - "02e18", "-5e019", "+8e20", "2E+22", "-5E-5", "8E0", "9e-0", " 2e+0"}; + assert(node); + assert(serd_get_boolean(node) == expected); - for (size_t i = 0; i < sizeof(expt_test_nums) / sizeof(double); ++i) { - const double num = serd_strtod(expt_test_strs[i], NULL); - const double delta = fabs(num - expt_test_nums[i]); - assert(delta <= DBL_EPSILON); + serd_node_free(node); +} - check_strtod(expt_test_nums[i], DBL_EPSILON); - } +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); + check_get_boolean("!invalid", NS_XSD "long", false); } static void -test_new_decimal(void) +test_decimal(void) { - static const double dbl_test_nums[] = { + const double test_values[] = { 0.0, 9.0, 10.0, .01, 2.05, -16.00001, 5.000000005, 0.0000000001}; - static const char* const dbl_test_strs[] = {"0.0", - "9.0", - "10.0", - "0.01", - "2.05", - "-16.00001", - "5.000000005", - "0.0000000001"}; + static const char* const test_strings[] = {"0.0", + "9.0", + "10.0", + "0.01", + "2.05", + "-16.00001", + "5.000000005", + "0.0000000001"}; + + for (size_t i = 0; i < sizeof(test_values) / sizeof(double); ++i) { + SerdNode* node = serd_new_decimal(test_values[i], NULL); + 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 "decimal")); - for (size_t i = 0; i < sizeof(dbl_test_nums) / sizeof(double); ++i) { - SerdNode* node = serd_new_decimal(dbl_test_nums[i], NULL); - assert(node); + const double value = serd_get_double(node); + assert(!memcmp(&value, &test_values[i], sizeof(value))); + serd_node_free(node); + } +} +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, dbl_test_strs[i])); + assert(!strcmp(node_str, test_strings[i])); - const size_t len = node_str ? strlen(node_str) : 0; - assert((!node && len == 0) || serd_node_length(node) == len); + const size_t len = strlen(node_str); + assert(serd_node_length(node) == len); - if (node) { - const SerdNode* const datatype = serd_node_datatype(node); - assert(datatype); - assert(!dbl_test_strs[i] || - !strcmp(serd_node_string(datatype), NS_XSD "decimal")); - serd_node_free(node); - } + 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_string(string), serd_string(datatype_uri)); + + assert(node); + + const double value = serd_get_double(node); + assert(!memcmp(&value, &expected, sizeof(value))); + + serd_node_free(node); +} + +static void +test_get_double(void) +{ + 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); + + static const uint8_t blob[] = {1U, 2U, 3U, 4U}; + + SERD_DISABLE_CONVERSION_WARNINGS + + SerdNode* const nan = serd_new_string(serd_string("unknown")); + assert(isnan(serd_get_double(nan))); + serd_node_free(nan); + + SerdNode* const invalid = + serd_new_typed_literal(serd_string("!invalid"), serd_string(NS_XSD "long")); + + assert(isnan(serd_get_double(invalid))); + serd_node_free(invalid); + + SerdNode* const base64 = serd_new_base64(blob, sizeof(blob), NULL); + + assert(isnan(serd_get_double(base64))); + serd_node_free(base64); + + SERD_RESTORE_WARNINGS +} + +static void +test_float(void) +{ + const float test_values[] = {0.0f, -0.0f, 1.5f, -2.5f, 4567890.0f}; + 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 float value = serd_get_float(node); + assert(!memcmp(&value, &test_values[i], sizeof(value))); + serd_node_free(node); } } static void -test_integer_to_node(void) +check_get_float(const char* string, + const char* datatype_uri, + const float expected) +{ + SerdNode* const node = + serd_new_typed_literal(serd_string(string), serd_string(datatype_uri)); + + assert(node); + + const float value = serd_get_float(node); + assert(!memcmp(&value, &expected, sizeof(value))); + + serd_node_free(node); +} + +static void +test_get_float(void) { -#define N_TEST_NUMS 7U + 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); + + SERD_DISABLE_CONVERSION_WARNINGS + + SerdNode* const nan = serd_new_string(serd_string("unknown")); + assert(isnan(serd_get_float(nan))); + serd_node_free(nan); + + SerdNode* const invalid = + serd_new_typed_literal(serd_string("!invalid"), serd_string(NS_XSD "long")); - const long int_test_nums[N_TEST_NUMS] = {0, -0, -23, 23, -12340, 1000, -1000}; + assert(isnan(serd_get_double(invalid))); - const char* int_test_strs[N_TEST_NUMS] = { + SERD_RESTORE_WARNINGS + + serd_node_free(invalid); +} + +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 < N_TEST_NUMS; ++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); } - -#undef N_TEST_NUMS } 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")); + SerdNode* const node = + serd_new_typed_literal(serd_string(string), serd_string(datatype_uri)); - 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); + assert(node); + assert(serd_get_integer(node) == expected); - SerdNode* const false_node = serd_new_boolean(false); - assert(!strcmp(serd_node_string(false_node), "false")); + serd_node_free(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 +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); + check_get_integer("!invalid", NS_XSD "long", 0); } static void @@ -317,11 +505,16 @@ test_blank(void) int main(void) { - test_strtod(); - test_new_decimal(); - test_integer_to_node(); - test_blob_to_node(); test_boolean(); + test_get_boolean(); + test_decimal(); + 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(); |