aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2021-04-15 18:34:17 -0400
committerDavid Robillard <d@drobilla.net>2022-01-13 23:03:41 -0500
commitc90c662f85ca1a36794a0404b2101a72de020ca3 (patch)
tree9e4e8abb5a70e955f0236aa9ebeccdbcba02c33f
parentf074a83f20e04e360704a190ab5f4e646f4272b7 (diff)
downloadserd-c90c662f85ca1a36794a0404b2101a72de020ca3.tar.gz
serd-c90c662f85ca1a36794a0404b2101a72de020ca3.tar.bz2
serd-c90c662f85ca1a36794a0404b2101a72de020ca3.zip
Use exess for reading and writing numeric and binary literals
-rw-r--r--.gitlab-ci.yml22
-rw-r--r--.gitmodules3
-rw-r--r--.includes.imp4
-rw-r--r--include/serd/serd.h47
-rw-r--r--meson.build16
-rw-r--r--src/base64.c114
-rw-r--r--src/node.c188
-rw-r--r--src/string.c67
-rw-r--r--src/string_utils.h6
m---------subprojects/exess0
-rw-r--r--subprojects/exess.wrap5
-rw-r--r--test/meson.build32
-rw-r--r--test/test_node.c85
13 files changed, 221 insertions, 368 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a3998eea..6e9fc1d9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,6 +2,9 @@ stages:
- build
- deploy
+variables:
+ GIT_SUBMODULE_STRATEGY: normal
+
.build_template: &build_definition
stage: build
@@ -63,7 +66,7 @@ x64_static:
<<: *build_definition
image: lv2plugin/debian-x64
script:
- - meson setup build -Dstatic=true -Ddefault_library=static -Ddocs=disabled -Dstrict=true -Dwerror=true
+ - meson setup build -Dstatic=true -Ddefault_library=static -Dexess:default_library=static -Ddocs=disabled -Dstrict=true -Dwerror=true
- ninja -C build test
@@ -74,11 +77,14 @@ x64_sanitize:
- meson setup build -Db_lundef=false -Dbuildtype=plain -Ddocs=disabled -Dstrict=true -Dwerror=true
- ninja -C build test
variables:
+ # TODO: Figure out how to enable address and undefined sanitizers on CI.
+ # ASan fails because it needs docker run --privileged, and UBSan fails with
+ # clang7 in buster because it incorrectly(?) flags FLT_MAX as out of range.
CC: "clang"
CXX: "clang++"
- CFLAGS: "-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability"
- CXXFLAGS: "-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability"
- LDFLAGS: "-fno-sanitize-recover=all -fsanitize=address -fsanitize=undefined -fsanitize=float-divide-by-zero -fsanitize=unsigned-integer-overflow -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability"
+ CFLAGS: "-fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability"
+ CXXFLAGS: "-fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability"
+ LDFLAGS: "-fno-sanitize-recover=all -fsanitize=float-divide-by-zero -fsanitize=implicit-conversion -fsanitize=local-bounds -fsanitize=nullability"
mingw32_dbg:
@@ -89,7 +95,7 @@ mingw32_dbg:
- ninja -C build test
variables:
MESON_TESTTHREADS: "1"
- WINEPATH: "Z:\\usr\\lib\\gcc\\i686-w64-mingw32\\8.3-win32"
+ WINEPATH: "Z:\\usr\\lib\\gcc\\i686-w64-mingw32\\8.3-win32;Z:\\builds\\drobilla\\serd\\build\\subprojects\\exess"
mingw32_rel:
<<: *build_definition
@@ -99,7 +105,7 @@ mingw32_rel:
- ninja -C build test
variables:
MESON_TESTTHREADS: "1"
- WINEPATH: "Z:\\usr\\lib\\gcc\\i686-w64-mingw32\\8.3-win32"
+ WINEPATH: "Z:\\usr\\lib\\gcc\\i686-w64-mingw32\\8.3-win32;Z:\\builds\\drobilla\\serd\\build\\subprojects\\exess"
mingw64_dbg:
@@ -110,7 +116,7 @@ mingw64_dbg:
- ninja -C build test
variables:
MESON_TESTTHREADS: "1"
- WINEPATH: "Z:\\usr\\lib\\gcc\\x86_64-w64-mingw32\\8.3-win32"
+ WINEPATH: "Z:\\usr\\lib\\gcc\\x86_64-w64-mingw32\\8.3-win32;Z:\\builds\\drobilla\\serd\\build\\subprojects\\exess"
mingw64_rel:
<<: *build_definition
@@ -120,7 +126,7 @@ mingw64_rel:
- ninja -C build test
variables:
MESON_TESTTHREADS: "1"
- WINEPATH: "Z:\\usr\\lib\\gcc\\x86_64-w64-mingw32\\8.3-win32"
+ WINEPATH: "Z:\\usr\\lib\\gcc\\x86_64-w64-mingw32\\8.3-win32;Z:\\builds\\drobilla\\serd\\build\\subprojects\\exess"
mac_dbg:
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..17929c01
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "subprojects/exess"]
+ path = subprojects/exess
+ url = ../exess.git
diff --git a/.includes.imp b/.includes.imp
index 07b654dd..9ff42384 100644
--- a/.includes.imp
+++ b/.includes.imp
@@ -1,5 +1,9 @@
[
+ { "symbol": [ "int16_t", "private", "<stdint.h>", "public" ] },
+ { "symbol": [ "int32_t", "private", "<stdint.h>", "public" ] },
{ "symbol": [ "int64_t", "private", "<stdint.h>", "public" ] },
+ { "symbol": [ "int8_t", "private", "<stdint.h>", "public" ] },
+ { "symbol": [ "uint16_t", "private", "<stdint.h>", "public" ] },
{ "symbol": [ "uint32_t", "private", "<stdint.h>", "public" ] },
{ "symbol": [ "uint64_t", "private", "<stdint.h>", "public" ] },
{ "symbol": [ "uint8_t", "private", "<stdint.h>", "public" ] },
diff --git a/include/serd/serd.h b/include/serd/serd.h
index 1d1ae70a..2dd551fd 100644
--- a/include/serd/serd.h
+++ b/include/serd/serd.h
@@ -231,13 +231,12 @@ serd_strlen(const char* SERD_NONNULL str, SerdNodeFlags* SERD_NULLABLE flags);
SERD_API
double
serd_strtod(const char* SERD_NONNULL str,
- char* SERD_NONNULL* SERD_NULLABLE endptr);
+ const char* SERD_NONNULL* SERD_NULLABLE endptr);
/**
Decode a base64 string.
- This function can be used to deserialise a blob node created with
- serd_new_blob().
+ This function can be used to decode a node created with serd_new_base64().
@param str Base64 string to decode.
@param len The length of `str`.
@@ -600,31 +599,23 @@ SerdNode* SERD_ALLOCATED
serd_new_file_uri(SerdStringView path, SerdStringView hostname);
/**
- Create a new node by serialising `d` into an xsd:decimal string.
+ Create a new canonical xsd:decimal literal.
- The resulting node will always contain a `.', start with a digit, and end
- with a digit (i.e. will have a leading and/or trailing `0' if necessary).
- It will never be in scientific notation. A maximum of `frac_digits` digits
- will be written after the decimal point, but trailing zeros will
- automatically be omitted (except one if `d` is a round integer).
-
- Note that about 16 and 8 fractional digits are required to precisely
- represent a double and float, respectively.
+ The resulting node will always contain a '.', start with a digit, and end
+ with a digit (a leading and/or trailing '0' will be added if necessary), for
+ example, "1.0". It will never be in scientific notation.
@param d The value for the new node.
- @param frac_digits The maximum number of digits after the decimal place.
@param datatype Datatype of node, or NULL for xsd:decimal.
*/
SERD_API
SerdNode* SERD_ALLOCATED
-serd_new_decimal(double d,
- unsigned frac_digits,
- const SerdNode* SERD_NULLABLE datatype);
+serd_new_decimal(double d, const SerdNode* SERD_NULLABLE datatype);
/**
- Create a new node by serialising `i` into an xsd:integer string.
+ Create a new canonical xsd:integer literal.
- @param i Integer value to serialise.
+ @param i Integer value of literal.
@param datatype Datatype of node, or NULL for xsd:integer.
*/
SERD_API
@@ -632,22 +623,20 @@ SerdNode* SERD_ALLOCATED
serd_new_integer(int64_t i, const SerdNode* SERD_NULLABLE datatype);
/**
- Create a node by serialising `buf` into an xsd:base64Binary string.
+ Create a new canonical xsd:base64Binary literal.
- This function can be used to make a serialisable node out of arbitrary
- binary data, which can be decoded using serd_base64_decode().
+ This function can be used to make a node out of arbitrary binary data, which
+ can be decoded using serd_base64_decode().
- @param buf Raw binary input data.
- @param size Size of `buf`.
- @param wrap_lines Wrap lines at 76 characters to conform to RFC 2045.
- @param datatype Datatype of node, or NULL for xsd:base64Binary.
+ @param buf Raw binary data to encode in node.
+ @param size Size of `buf` in bytes.
+ @param datatype Datatype of node, or null for xsd:base64Binary.
*/
SERD_API
SerdNode* SERD_ALLOCATED
-serd_new_blob(const void* SERD_NONNULL buf,
- size_t size,
- bool wrap_lines,
- const SerdNode* SERD_NULLABLE datatype);
+serd_new_base64(const void* SERD_NONNULL buf,
+ size_t size,
+ const SerdNode* SERD_NULLABLE datatype);
/// Return a deep copy of `node`
SERD_API
diff --git a/meson.build b/meson.build
index 2708b5a7..301b65ac 100644
--- a/meson.build
+++ b/meson.build
@@ -30,9 +30,6 @@ if get_option('strict')
if cc.get_id() == 'clang'
c_suppressions += [
'-Wno-cast-align',
- '-Wno-cast-qual',
- '-Wno-conversion',
- '-Wno-double-promotion',
'-Wno-format-nonliteral',
'-Wno-nullability-extension',
'-Wno-nullable-to-nonnull-conversion',
@@ -43,7 +40,6 @@ if get_option('strict')
elif cc.get_id() == 'gcc'
c_suppressions += [
'-Wno-cast-align',
- '-Wno-cast-qual',
'-Wno-float-conversion', # MinGW
'-Wno-format-nonliteral',
'-Wno-inline',
@@ -135,9 +131,17 @@ elif get_option('default_library') == 'shared'
else
prog_args = ['-DSERD_STATIC']
library_type = 'static_library'
- library_args = ['-DSERD_INTERNAL', '-DSERD_STATIC']
+ library_args = [
+ '-DEXESS_STATIC',
+ '-DSERD_INTERNAL',
+ '-DSERD_STATIC',
+ ]
endif
+exess_dep = dependency('exess-0',
+ version: '>= 0.0.1',
+ fallback: ['exess', 'exess_dep'])
+
# Build shared and/or static library/libraries
libserd = build_target(
library_name,
@@ -145,7 +149,7 @@ libserd = build_target(
version: meson.project_version(),
include_directories: include_directories(['include']),
c_args: c_warnings + platform_args + library_args,
- dependencies: m_dep,
+ dependencies: [exess_dep, m_dep],
gnu_symbol_visibility: 'hidden',
install: true,
target_type: library_type)
diff --git a/src/base64.c b/src/base64.c
index 0d79fd88..9dac9979 100644
--- a/src/base64.c
+++ b/src/base64.c
@@ -14,115 +14,25 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include "base64.h"
-
-#include "serd_internal.h"
-#include "string_utils.h"
-
+#include "exess/exess.h"
#include "serd/serd.h"
-#include <stdbool.h>
-#include <stdint.h>
#include <stdlib.h>
-#include <string.h>
-
-/**
- Base64 encoding table.
-
- @see <a href="http://tools.ietf.org/html/rfc3548#section-3">RFC3548 S3</a>.
-*/
-static const uint8_t b64_map[] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-/**
- Base64 decoding table.
-
- This is indexed by encoded characters and returns the numeric value used
- for decoding, shifted up by 47 to be in the range of printable ASCII.
- A '$' is a placeholder for characters not in the base64 alphabet.
-*/
-static const char b64_unmap[] =
- "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$m$$$ncdefghijkl$$$$$$"
- "$/0123456789:;<=>?@ABCDEFGH$$$$$$IJKLMNOPQRSTUVWXYZ[\\]^_`ab$$$$"
- "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"
- "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$";
-
-/** Encode 3 raw bytes to 4 base64 characters. */
-static void
-encode_chunk(uint8_t out[4], const uint8_t in[3], const size_t n_in)
-{
- out[0] = b64_map[in[0] >> 2];
- out[1] = b64_map[((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4)];
-
- out[2] = (n_in > 1) ? (b64_map[((in[1] & 0x0F) << 2) | ((in[2] & 0xC0) >> 6)])
- : (uint8_t)'=';
-
- out[3] = ((n_in > 2) ? b64_map[in[2] & 0x3F] : (uint8_t)'=');
-}
-
-size_t
-serd_base64_get_length(const size_t size, const bool wrap_lines)
-{
- return (size + 2) / 3 * 4 + (wrap_lines * ((size - 1) / 57));
-}
-
-bool
-serd_base64_encode(uint8_t* const str,
- const void* const buf,
- const size_t size,
- const bool wrap_lines)
-{
- bool has_newline = false;
- for (size_t i = 0, j = 0; i < size; i += 3, j += 4) {
- uint8_t in[4] = {0, 0, 0, 0};
- size_t n_in = MIN(3, size - i);
- memcpy(in, (const uint8_t*)buf + i, n_in);
-
- if (wrap_lines && i > 0 && (i % 57) == 0) {
- str[j++] = '\n';
- has_newline = true;
- }
-
- encode_chunk(str + j, in, n_in);
- }
-
- return has_newline;
-}
-
-static uint8_t
-unmap(const uint8_t in)
-{
- return (uint8_t)(b64_unmap[in] - 47);
-}
-
-/** Decode 4 base64 characters to 3 raw bytes. */
-static size_t
-decode_chunk(const uint8_t in[4], uint8_t out[3])
-{
- out[0] = (uint8_t)(((unmap(in[0]) << 2)) | unmap(in[1]) >> 4);
- out[1] = (uint8_t)(((unmap(in[1]) << 4) & 0xF0) | unmap(in[2]) >> 2);
- out[2] = (uint8_t)(((unmap(in[2]) << 6) & 0xC0) | unmap(in[3]));
- return 1 + (in[2] != '=') + ((in[2] != '=') && (in[3] != '='));
-}
void*
serd_base64_decode(const char* const str, const size_t len, size_t* const size)
{
- const uint8_t* const ustr = (const uint8_t*)str;
-
- void* buf = malloc((len * 3) / 4 + 2);
- *size = 0;
- for (size_t i = 0, j = 0; i < len; j += 3) {
- uint8_t in[] = "====";
- size_t n_in = 0;
- for (; i < len && n_in < 4; ++n_in) {
- for (; i < len && !is_base64(ustr[i]); ++i) {
- } // Skip junk
- in[n_in] = ustr[i++];
- }
- if (n_in > 1) {
- *size += decode_chunk(in, (uint8_t*)buf + j);
- }
+ const size_t max_size = exess_base64_decoded_size(len);
+
+ void* const buf = malloc(max_size);
+ const ExessVariableResult r = exess_read_base64(max_size, buf, str);
+ if (r.status) {
+ *size = 0;
+ free(buf);
+ return NULL;
}
+
+ *size = r.write_count;
+
return buf;
}
diff --git a/src/node.c b/src/node.c
index cf5f7e46..6ba00048 100644
--- a/src/node.c
+++ b/src/node.c
@@ -1,5 +1,5 @@
/*
- Copyright 2011-2020 David Robillard <d@drobilla.net>
+ Copyright 2011-2021 David Robillard <d@drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -16,31 +16,25 @@
#include "node.h"
-#include "base64.h"
#include "serd_internal.h"
#include "static_nodes.h"
#include "string_utils.h"
#include "system.h"
+#include "exess/exess.h"
#include "serd/serd.h"
#include <assert.h>
-#include <float.h>
-#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#ifdef _WIN32
-# ifndef isnan
-# define isnan(x) _isnan(x)
-# endif
-# ifndef isinf
-# define isinf(x) (!_finite(x))
-# endif
-#endif
+typedef struct {
+ const void* SERD_NULLABLE buf;
+ size_t len;
+} SerdConstBuffer;
static const SerdNodeFlags meta_mask = (SERD_HAS_DATATYPE | SERD_HAS_LANGUAGE);
@@ -490,138 +484,112 @@ serd_new_file_uri(const SerdStringView path, const SerdStringView hostname)
return node;
}
-static unsigned
-serd_digits(const double abs)
-{
- const double lg = ceil(log10(floor(abs) + 1.0));
- return lg < 1.0 ? 1U : (unsigned)lg;
-}
+typedef size_t (*SerdWriteLiteralFunc)(const void* user_data,
+ size_t buf_size,
+ char* buf);
-SerdNode*
-serd_new_decimal(const double d,
- const unsigned frac_digits,
- const SerdNode* const datatype)
+static SerdNode*
+serd_new_custom_literal(const void* const user_data,
+ const size_t len,
+ const SerdWriteLiteralFunc write,
+ const SerdNode* const datatype)
{
- if (isnan(d) || isinf(d)) {
+ if (len == 0 || !write) {
return NULL;
}
- const SerdNode* type = datatype ? datatype : &serd_xsd_decimal.node;
- const double abs_d = fabs(d);
- const unsigned int_digits = serd_digits(abs_d);
- const size_t len = int_digits + frac_digits + 3;
- const size_t type_len = serd_node_total_size(type);
- const size_t total_len = len + type_len;
-
- SerdNode* const node =
- serd_node_malloc(total_len, SERD_HAS_DATATYPE, SERD_LITERAL);
-
- // Point s to decimal point location
- char* const buf = serd_node_buffer(node);
- const double int_part = floor(abs_d);
- char* s = buf + int_digits;
- if (d < 0.0) {
- *buf = '-';
- ++s;
- }
+ const size_t datatype_size = serd_node_total_size(datatype);
+ const size_t total_size = serd_node_pad_size(len + 1) + datatype_size;
- // Write integer part (right to left)
- char* t = s - 1;
- uint64_t dec = (uint64_t)int_part;
- do {
- *t-- = (char)('0' + dec % 10);
- } while ((dec /= 10) > 0);
-
- *s++ = '.';
-
- // Write fractional part (right to left)
- double frac_part = fabs(d - int_part);
- if (frac_part < DBL_EPSILON) {
- *s++ = '0';
- node->length = (size_t)(s - buf);
- } else {
- uint64_t frac = (uint64_t)llround(frac_part * pow(10.0, (int)frac_digits));
- s += frac_digits - 1;
- unsigned i = 0;
-
- // Skip trailing zeros
- for (; i < frac_digits - 1 && !(frac % 10); ++i, --s, frac /= 10) {
- }
+ SerdNode* const node = serd_node_malloc(
+ total_size, datatype ? SERD_HAS_DATATYPE : 0u, SERD_LITERAL);
- node->length = (size_t)(s - buf) + 1u;
+ node->length = write(user_data, len + 1, serd_node_buffer(node));
- // Write digits from last trailing zero to decimal point
- for (; i < frac_digits; ++i) {
- *s-- = (char)('0' + (frac % 10));
- frac /= 10;
- }
+ if (datatype) {
+ memcpy(serd_node_meta(node), datatype, datatype_size);
}
- memcpy(serd_node_meta(node), type, type_len);
serd_node_check_padding(node);
return node;
}
SerdNode*
-serd_new_integer(const int64_t i, const SerdNode* const datatype)
+serd_new_decimal(const double d, const SerdNode* const datatype)
{
- const SerdNode* type = datatype ? datatype : &serd_xsd_integer.node;
- uint64_t abs_i = (uint64_t)((i < 0) ? -i : i);
- const unsigned digits = serd_digits((double)abs_i);
- const size_t type_len = serd_node_total_size(type);
- const size_t total_len = digits + 2 + type_len;
-
- SerdNode* node = serd_node_malloc(total_len, SERD_HAS_DATATYPE, SERD_LITERAL);
-
- // Point s to the end
- char* buf = serd_node_buffer(node);
- char* s = buf + digits - 1;
- if (i < 0) {
- *buf = '-';
- ++s;
- }
+ // Use given datatype, or xsd:decimal as a default if it is null
+ const SerdNode* type = datatype ? datatype : &serd_xsd_decimal.node;
+ const size_t type_size = serd_node_total_size(type);
+
+ // Measure integer string to know how much space the node will need
+ ExessResult r = exess_write_decimal(d, 0, NULL);
+ assert(!r.status);
- node->length = (size_t)(s - buf) + 1u;
+ // Allocate node with enough space for value and datatype URI
+ SerdNode* const node =
+ serd_node_malloc(serd_node_pad_size(r.count + 1) + type_size,
+ SERD_HAS_DATATYPE,
+ SERD_LITERAL);
- // Write integer part (right to left)
- do {
- *s-- = (char)('0' + (abs_i % 10));
- } while ((abs_i /= 10) > 0);
+ // Write string directly into node
+ r = exess_write_decimal(d, r.count + 1, serd_node_buffer(node));
+ assert(!r.status);
- memcpy(serd_node_meta(node), type, type_len);
+ node->length = r.count;
+ memcpy(serd_node_meta(node), type, type_size);
serd_node_check_padding(node);
return node;
}
SerdNode*
-serd_new_blob(const void* const buf,
- const size_t size,
- const bool wrap_lines,
- const SerdNode* const datatype)
+serd_new_integer(const int64_t i, const SerdNode* const datatype)
{
- if (!buf || !size) {
- return NULL;
- }
+ // Use given datatype, or xsd:integer as a default if it is null
+ const SerdNode* type = datatype ? datatype : &serd_xsd_integer.node;
+ const size_t type_size = serd_node_total_size(type);
- const SerdNode* type = datatype ? datatype : &serd_xsd_base64Binary.node;
- const size_t len = serd_base64_get_length(size, wrap_lines);
- const size_t type_len = serd_node_total_size(type);
- const size_t total_len = len + 1 + type_len;
+ // Measure integer string to know how much space the node will need
+ ExessResult r = exess_write_long(i, 0, NULL);
+ assert(!r.status);
+ // Allocate node with enough space for value and datatype URI
SerdNode* const node =
- serd_node_malloc(total_len, SERD_HAS_DATATYPE, SERD_LITERAL);
+ serd_node_malloc(serd_node_pad_size(r.count + 1) + type_size,
+ SERD_HAS_DATATYPE,
+ SERD_LITERAL);
- uint8_t* str = (uint8_t*)serd_node_buffer(node);
- if (serd_base64_encode(str, buf, size, wrap_lines)) {
- node->flags |= SERD_HAS_NEWLINE;
- }
+ // Write string directly into node
+ r = exess_write_long(i, r.count + 1, serd_node_buffer(node));
+ assert(!r.status);
- node->length = len;
- memcpy(serd_node_meta(node), type, type_len);
+ node->length = r.count;
+ memcpy(serd_node_meta(node), type, type_size);
serd_node_check_padding(node);
return node;
}
+static size_t
+write_base64_literal(const void* const user_data,
+ const size_t buf_size,
+ char* const buf)
+{
+ const SerdConstBuffer blob = *(const SerdConstBuffer*)user_data;
+
+ const ExessResult r = exess_write_base64(blob.len, blob.buf, buf_size, buf);
+
+ return r.status ? 0 : r.count;
+}
+
+SerdNode*
+serd_new_base64(const void* buf, size_t size, const SerdNode* datatype)
+{
+ const size_t len = exess_write_base64(size, buf, 0, NULL).count;
+ const SerdNode* type = datatype ? datatype : &serd_xsd_base64Binary.node;
+ SerdConstBuffer blob = {buf, size};
+
+ return serd_new_custom_literal(&blob, len, write_base64_literal, type);
+}
+
SerdNodeType
serd_node_type(const SerdNode* const node)
{
diff --git a/src/string.c b/src/string.c
index 3a750c4f..c5b1d6f7 100644
--- a/src/string.c
+++ b/src/string.c
@@ -16,6 +16,7 @@
#include "string_utils.h"
+#include "exess/exess.h"
#include "serd/serd.h"
#include <assert.h>
@@ -105,68 +106,14 @@ serd_strlen(const char* const str, SerdNodeFlags* const flags)
return strlen(str);
}
-static double
-read_sign(const char** const sptr)
-{
- double sign = 1.0;
-
- switch (**sptr) {
- case '-':
- sign = -1.0;
- ++(*sptr);
- break;
- case '+':
- ++(*sptr);
- break;
- default:
- break;
- }
-
- return sign;
-}
-
double
-serd_strtod(const char* const str, char** const endptr)
+serd_strtod(const char* const str, const char** const end)
{
- double result = 0.0;
-
- // Point s at the first non-whitespace character
- const char* s = str;
- while (is_space(*s)) {
- ++s;
- }
-
- // Read leading sign if necessary
- const double sign = read_sign(&s);
-
- // Parse integer part
- for (; is_digit(*s); ++s) {
- result = (result * 10.0) + (*s - '0');
- }
-
- // Parse fractional part
- if (*s == '.') {
- double denom = 10.0;
- for (++s; is_digit(*s); ++s) {
- result += (*s - '0') / denom;
- denom *= 10.0;
- }
- }
-
- // Parse exponent
- if (*s == 'e' || *s == 'E') {
- ++s;
- double expt = 0.0;
- double expt_sign = read_sign(&s);
- for (; is_digit(*s); ++s) {
- expt = (expt * 10.0) + (*s - '0');
- }
- result *= pow(10, expt * expt_sign);
- }
-
- if (endptr) {
- *endptr = (char*)s;
+ double value = (double)NAN;
+ const ExessResult r = exess_read_double(&value, str);
+ if (end) {
+ *end = str + r.count;
}
- return result * sign;
+ return r.status ? (double)NAN : value;
}
diff --git a/src/string_utils.h b/src/string_utils.h
index 0e9eee43..07941325 100644
--- a/src/string_utils.h
+++ b/src/string_utils.h
@@ -84,12 +84,6 @@ is_print(const int c)
}
static inline bool
-is_base64(const int c)
-{
- return is_alpha(c) || is_digit(c) || c == '+' || c == '/' || c == '=';
-}
-
-static inline bool
is_windows_path(const char* path)
{
return is_alpha(path[0]) && (path[1] == ':' || path[1] == '|') &&
diff --git a/subprojects/exess b/subprojects/exess
new file mode 160000
+Subproject 1230075217e3ab4481a83961c7ae811226ee3c7
diff --git a/subprojects/exess.wrap b/subprojects/exess.wrap
new file mode 100644
index 00000000..dde2e29f
--- /dev/null
+++ b/subprojects/exess.wrap
@@ -0,0 +1,5 @@
+[wrap-git]
+url = https://gitlab.com/drobilla/exess.git
+push-url = git@gitlab.com:drobilla/exess.git
+revision = main
+depth = 1
diff --git a/test/meson.build b/test/meson.build
index a57e2cf2..978bc779 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -17,12 +17,20 @@ unit_tests = [
'writer',
]
+test_env = []
+if build_machine.system() == 'windows' and host_machine.system() == 'windows'
+ # For Windows, we need to add to PATH so that DLLs are found
+ test_env = ['PATH=@0@;@1@'.format('subprojects' / 'exess',
+ 'subprojects' / 'rerex')]
+endif
+
foreach unit : unit_tests
test(unit,
executable('test_@0@'.format(unit),
'test_@0@.c'.format(unit),
c_args: c_warnings + platform_args + prog_args,
dependencies: serd_dep),
+ env: test_env,
suite: 'unit')
endforeach
@@ -40,7 +48,7 @@ if get_option('utils')
serd_ttl = files('../serd.ttl')[0]
- test('serd.ttl', serdi, args: [serd_ttl], suite: 'data')
+ test('serd.ttl', serdi, args: [serd_ttl], env: test_env, suite: 'data')
# Command line options
@@ -51,7 +59,7 @@ if get_option('utils')
]
foreach args : good_args
- test(args[0], serdi, args: args, suite: ['serdi', 'options'])
+ test(args[0], serdi, args: args, env: test_env, suite: ['serdi', 'options'])
endforeach
bad_args = [
@@ -78,39 +86,50 @@ if get_option('utils')
name = ' '.join(args).underscorify()
test(name, serdi,
args: args,
+ env: test_env,
should_fail: true,
suite: ['serdi', 'options'])
endforeach
- test('none', serdi, should_fail: true, suite: ['serdi', 'options'])
+ test('none',
+ serdi,
+ env: test_env,
+ should_fail: true,
+ suite: ['serdi', 'options'])
test('quiet', files('test_quiet.py'),
args: script_args + files('bad/bad-base.ttl'),
+ env: test_env,
suite: ['serdi', 'options'])
# Inputs
test('stdin', files('test_stdin.py'),
args: script_args,
+ env: test_env,
suite: ['serdi', 'input'])
test('string', serdi,
args: ['-s', '<foo> a <Bar> .'],
+ env: test_env,
should_fail: true,
suite: ['serdi', 'input'])
test('missing', serdi,
args: ['-i', 'turtle'],
+ env: test_env,
should_fail: true,
suite: ['serdi', 'input'])
test('no_such_file', serdi,
args: ['no_such_file'],
+ env: test_env,
should_fail: true,
suite: ['serdi', 'input'])
test('remote', serdi,
args: ['ftp://example.org/unsupported.ttl'],
+ env: test_env,
should_fail: true,
suite: ['serdi', 'input'])
@@ -118,16 +137,19 @@ if get_option('utils')
test('read_dir', serdi,
args: ['-e', 'file://@0@/'.format(meson.source_root())],
+ env: test_env,
should_fail: true,
suite: 'io_errors')
test('bulk_read_dir', serdi,
args: ['file://@0@/'.format(meson.source_root())],
+ env: test_env,
should_fail: true,
suite: 'io_errors')
test('write_error', files('test_write_error.py'),
args: script_args + [serd_ttl],
+ env: test_env,
suite: 'io_errors')
# RDF test suites
@@ -143,6 +165,7 @@ if get_option('utils')
base_uri = serd_base + name + '/'
test(name, run_test_suite,
args: script_args + [manifest, base_uri],
+ env: test_env,
suite: ['rdf', 'serd'],
timeout: 240)
endforeach
@@ -154,6 +177,7 @@ if get_option('utils')
### ... once with strict parsing to test the hard errors
test('lax.strict', run_test_suite,
args: script_args + [lax_manifest, lax_base_uri],
+ env: test_env,
is_parallel: false,
suite: ['rdf', 'serd'],
timeout: 240)
@@ -161,6 +185,7 @@ if get_option('utils')
### ... and once with lax parsing to tolerate them
test('lax.lax', run_test_suite,
args: script_args + [lax_manifest, lax_base_uri, '--', '-l'],
+ env: test_env,
is_parallel: false,
suite: ['rdf', 'serd'],
timeout: 240)
@@ -182,6 +207,7 @@ if get_option('utils')
test(syntax, run_test_suite,
args: script_args + args,
+ env: test_env,
suite: ['rdf', 'w3c'],
timeout: 240)
diff --git a/test/test_node.c b/test/test_node.c
index 095d8755..968d4ada 100644
--- a/test/test_node.c
+++ b/test/test_node.c
@@ -21,29 +21,21 @@
#include <assert.h>
#include <float.h>
#include <math.h>
-#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#ifndef INFINITY
-# define INFINITY (DBL_MAX + DBL_MAX)
-#endif
-#ifndef NAN
-# define NAN (INFINITY - INFINITY)
-#endif
-
#define NS_XSD "http://www.w3.org/2001/XMLSchema#"
#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
static void
-test_strtod(double dbl, double max_delta)
+check_strtod(const double dbl, const double max_delta)
{
char buf[1024];
snprintf(buf, sizeof(buf), "%f", dbl);
- char* endptr = NULL;
+ const char* endptr = NULL;
const double out = serd_strtod(buf, &endptr);
const double diff = fabs(out - dbl);
@@ -51,7 +43,7 @@ test_strtod(double dbl, double max_delta)
}
static void
-test_string_to_double(void)
+test_strtod(void)
{
const double expt_test_nums[] = {
2.0E18, -5e19, +8e20, 2e+24, -5e-5, 8e0, 9e-0, 2e+0};
@@ -64,42 +56,31 @@ test_string_to_double(void)
const double delta = fabs(num - expt_test_nums[i]);
assert(delta <= DBL_EPSILON);
- test_strtod(expt_test_nums[i], DBL_EPSILON);
+ check_strtod(expt_test_nums[i], DBL_EPSILON);
}
}
static void
-test_double_to_node(void)
+test_new_decimal(void)
{
- const double dbl_test_nums[] = {0.0,
- 9.0,
- 10.0,
- .01,
- 2.05,
- -16.00001,
- 5.000000005,
- 0.0000000001,
- NAN,
- INFINITY};
-
- const char* dbl_test_strs[] = {"0.0",
- "9.0",
- "10.0",
- "0.01",
- "2.05",
- "-16.00001",
- "5.00000001",
- "0.0",
- NULL,
- NULL};
+ static const double dbl_test_nums[] = {
+ 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"};
for (size_t i = 0; i < sizeof(dbl_test_nums) / sizeof(double); ++i) {
- SerdNode* node = serd_new_decimal(dbl_test_nums[i], 8, NULL);
- const char* node_str = node ? serd_node_string(node) : NULL;
- const bool pass = (node_str && dbl_test_strs[i])
- ? !strcmp(node_str, dbl_test_strs[i])
- : (node_str == dbl_test_strs[i]);
- assert(pass);
+ SerdNode* node = serd_new_decimal(dbl_test_nums[i], NULL);
+ assert(node);
+
+ const char* node_str = serd_node_string(node);
+ assert(!strcmp(node_str, dbl_test_strs[i]));
const size_t len = node_str ? strlen(node_str) : 0;
assert((!node && len == 0) || serd_node_length(node) == len);
@@ -139,8 +120,9 @@ test_integer_to_node(void)
static void
test_blob_to_node(void)
{
- assert(!serd_new_blob(&SERD_URI_NULL, 0, false, NULL));
+ assert(!serd_new_base64(&SERD_URI_NULL, 0, NULL));
+ // Test valid base64 blobs with a range of sizes
for (size_t size = 1; size < 256; ++size) {
uint8_t* const data = (uint8_t*)malloc(size);
for (size_t i = 0; i < size; ++i) {
@@ -148,7 +130,7 @@ test_blob_to_node(void)
}
size_t out_size = 0;
- SerdNode* blob = serd_new_blob(data, size, size % 5, NULL);
+ SerdNode* blob = serd_new_base64(data, size, NULL);
const char* blob_str = serd_node_string(blob);
uint8_t* out =
(uint8_t*)serd_base64_decode(blob_str, serd_node_length(blob), &out_size);
@@ -168,6 +150,21 @@ test_blob_to_node(void)
serd_free(out);
free(data);
}
+
+ // Test invalid base64 blob
+
+ SerdNode* const blob = serd_new_typed_literal(
+ SERD_STRING("!nval!d$"), SERD_STRING(NS_XSD "base64Binary"));
+
+ const char* const blob_str = serd_node_string(blob);
+ size_t out_size = 42;
+ uint8_t* out =
+ (uint8_t*)serd_base64_decode(blob_str, serd_node_length(blob), &out_size);
+
+ assert(!out);
+ assert(out_size == 0);
+
+ serd_node_free(blob);
}
static void
@@ -301,8 +298,8 @@ test_blank(void)
int
main(void)
{
- test_string_to_double();
- test_double_to_node();
+ test_strtod();
+ test_new_decimal();
test_integer_to_node();
test_blob_to_node();
test_node_equals();