From b983d2a94b0f84b4c06889be77fbdacc502f2d7b Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 20 Feb 2021 19:18:28 -0500 Subject: Replace multiple stream callbacks with SerdEvent This makes plumbing easier since everything goes through the same "stream" and only one callback is required to handling everything. It's also more easily extensible in case more event types need to be added in the future. --- include/serd/serd.h | 111 +++++++++++++++++++++++++++------------------- src/env.c | 4 +- src/sink.c | 60 +++++++++++++------------ src/sink.h | 9 ++-- src/writer.c | 26 ++++++++--- test/test_env.c | 24 +++++----- test/test_read_chunk.c | 25 ++++++++--- test/test_reader_writer.c | 45 ++++++++----------- 8 files changed, 176 insertions(+), 128 deletions(-) diff --git a/include/serd/serd.h b/include/serd/serd.h index e32e66cc..d689cc62 100644 --- a/include/serd/serd.h +++ b/include/serd/serd.h @@ -822,42 +822,78 @@ serd_node_equals(const SerdNode* SERD_NULLABLE a, typedef SerdStatus (*SerdErrorFunc)(void* SERD_NULLABLE handle, const SerdError* SERD_NONNULL error); +/// Type of a SerdEvent +typedef enum { + SERD_BASE = 1, ///< Base URI changed + SERD_PREFIX = 2, ///< New URI prefix + SERD_STATEMENT = 3, ///< Statement + SERD_END = 4 ///< End of anonymous node +} SerdEventType; + /** - Sink function for base URI changes + Event for base URI changes - Called whenever the base URI of the serialisation changes. + Emitted whenever the base URI of the serialisation changes. */ -typedef SerdStatus (*SerdBaseFunc)(void* SERD_NULLABLE handle, - const SerdNode* SERD_NONNULL uri); +typedef struct { + SerdEventType type; ///< #SERD_BASE + const SerdNode* SERD_NONNULL uri; ///< Base URI +} SerdBaseEvent; /** - Sink function for namespace definitions + Event for namespace definitions - Called whenever a prefix is defined in the serialisation. + Emitted whenever a prefix is defined in the serialisation. */ -typedef SerdStatus (*SerdPrefixFunc)(void* SERD_NULLABLE handle, - const SerdNode* SERD_NONNULL name, - const SerdNode* SERD_NONNULL uri); +typedef struct { + SerdEventType type; ///< #SERD_PREFIX + const SerdNode* SERD_NONNULL name; ///< Prefix name + const SerdNode* SERD_NONNULL uri; ///< Namespace URI +} SerdPrefixEvent; /** - Sink function for statements + Event for statements - Called for every RDF statement in the serialisation. + Emitted for every RDF statement in the serialisation. */ -typedef SerdStatus (*SerdStatementFunc)(void* SERD_NULLABLE handle, - SerdStatementFlags flags, - const SerdStatement* SERD_NONNULL - statement); +typedef struct { + SerdEventType type; ///< #SERD_STATEMENT + SerdStatementFlags flags; ///< Flags for pretty-printing + const SerdStatement* SERD_NONNULL statement; ///< Statement +} SerdStatementEvent; /** - Sink function for anonymous node end markers + Event for the end of anonymous node descriptions - This is called to indicate that the anonymous node with the given + This is emitted to indicate that the anonymous node with the given `value` will no longer be referred to by any future statements (i.e. the anonymous serialisation of the node is finished). */ -typedef SerdStatus (*SerdEndFunc)(void* SERD_NULLABLE handle, - const SerdNode* SERD_NONNULL node); +typedef struct { + SerdEventType type; ///< #SERD_END + const SerdNode* SERD_NONNULL node; ///< Anonymous node that is finished +} SerdEndEvent; + +/** + An event in a data stream + + Streams of data are represented as a series of events. Events represent + everything that can occur in an RDF document, and are used to plumb together + different components. For example, when parsing a document, a reader emits + a stream of events which can be sent to a writer to serialise a document, or + to an inserter to build a model in memory. +*/ +typedef union { + SerdEventType type; ///< Event type (always set) + SerdBaseEvent base; ///< Base URI changed + SerdPrefixEvent prefix; ///< New namespace prefix + SerdStatementEvent statement; ///< Statement + SerdEndEvent end; ///< End of anonymous node +} SerdEvent; + +/// Function for handling events +typedef SerdStatus (*SerdEventFunc)(void* SERD_NULLABLE handle, + const SerdEvent* SERD_NONNULL event); /** @} @@ -966,12 +1002,11 @@ SerdNode* SERD_ALLOCATED serd_env_expand(const SerdEnv* SERD_NONNULL env, const SerdNode* SERD_NONNULL node); -/// Call `func` for each prefix defined in `env` +/// Write all prefixes in `env` to `sink` SERD_API void -serd_env_foreach(const SerdEnv* SERD_NONNULL env, - SerdPrefixFunc SERD_NONNULL func, - void* SERD_NULLABLE handle); +serd_env_write_prefixes(const SerdEnv* SERD_NONNULL env, + const SerdSink* SERD_NONNULL sink); /** @} @@ -989,41 +1024,25 @@ typedef void (*SerdFreeFunc)(void* SERD_NULLABLE ptr); serd_sink_set_*_func functions to set handlers for various events. @param handle Opaque handle that will be passed to sink functions. + @param event_func Function that will be called for every event @param free_handle Free function to call on handle in serd_sink_free(). */ SERD_API SerdSink* SERD_ALLOCATED -serd_sink_new(void* SERD_NULLABLE handle, - SerdFreeFunc SERD_NULLABLE free_handle); +serd_sink_new(void* SERD_NULLABLE handle, + SerdEventFunc SERD_NULLABLE event_func, + SerdFreeFunc SERD_NULLABLE free_handle); /// Free `sink` SERD_API void serd_sink_free(SerdSink* SERD_NULLABLE sink); -/// Set a function to be called when the base URI changes -SERD_API -SerdStatus -serd_sink_set_base_func(SerdSink* SERD_NONNULL sink, - SerdBaseFunc SERD_NULLABLE base_func); - -/// Set a function to be called when a namespace prefix is defined -SERD_API -SerdStatus -serd_sink_set_prefix_func(SerdSink* SERD_NONNULL sink, - SerdPrefixFunc SERD_NULLABLE prefix_func); - -/// Set a function to be called when a statement is emitted -SERD_API -SerdStatus -serd_sink_set_statement_func(SerdSink* SERD_NONNULL sink, - SerdStatementFunc SERD_NULLABLE statement_func); - -/// Set a function to be called when an anonymous node ends +/// Send an event to the sink SERD_API SerdStatus -serd_sink_set_end_func(SerdSink* SERD_NONNULL sink, - SerdEndFunc SERD_NULLABLE end_func); +serd_sink_write_event(const SerdSink* SERD_NONNULL sink, + const SerdEvent* SERD_NONNULL event); /// Set the base URI SERD_API diff --git a/src/env.c b/src/env.c index f7c8c6ba..6ff8e3f5 100644 --- a/src/env.c +++ b/src/env.c @@ -264,9 +264,9 @@ serd_env_expand(const SerdEnv* env, const SerdNode* node) } void -serd_env_foreach(const SerdEnv* env, SerdPrefixFunc func, void* handle) +serd_env_write_prefixes(const SerdEnv* env, const SerdSink* sink) { for (size_t i = 0; i < env->n_prefixes; ++i) { - func(handle, env->prefixes[i].name, env->prefixes[i].uri); + serd_sink_write_prefix(sink, env->prefixes[i].name, env->prefixes[i].uri); } } diff --git a/src/sink.c b/src/sink.c index 6f8cc734..2cd601ad 100644 --- a/src/sink.c +++ b/src/sink.c @@ -23,11 +23,14 @@ #include SerdSink* -serd_sink_new(void* handle, SerdFreeFunc free_handle) +serd_sink_new(void* const handle, + SerdEventFunc event_func, + SerdFreeFunc free_handle) { SerdSink* sink = (SerdSink*)calloc(1, sizeof(SerdSink)); sink->handle = handle; + sink->on_event = event_func; sink->free_handle = free_handle; return sink; @@ -46,37 +49,30 @@ serd_sink_free(SerdSink* sink) } SerdStatus -serd_sink_set_base_func(SerdSink* sink, SerdBaseFunc base_func) +serd_sink_write_event(const SerdSink* sink, const SerdEvent* event) { - sink->base = base_func; - return SERD_SUCCESS; -} - -SerdStatus -serd_sink_set_prefix_func(SerdSink* sink, SerdPrefixFunc prefix_func) -{ - sink->prefix = prefix_func; - return SERD_SUCCESS; -} - -SerdStatus -serd_sink_set_statement_func(SerdSink* sink, SerdStatementFunc statement_func) -{ - sink->statement = statement_func; - return SERD_SUCCESS; -} + switch (event->type) { + case SERD_BASE: + return serd_sink_write_base(sink, event->base.uri); + case SERD_PREFIX: + return serd_sink_write_prefix(sink, event->prefix.name, event->prefix.uri); + case SERD_STATEMENT: + return serd_sink_write_statement( + sink, event->statement.flags, event->statement.statement); + case SERD_END: + return serd_sink_write_end(sink, event->end.node); + } -SerdStatus -serd_sink_set_end_func(SerdSink* sink, SerdEndFunc end_func) -{ - sink->end = end_func; return SERD_SUCCESS; } SerdStatus serd_sink_write_base(const SerdSink* sink, const SerdNode* uri) { - return sink->base ? sink->base(sink->handle, uri) : SERD_SUCCESS; + const SerdBaseEvent ev = {SERD_BASE, uri}; + + return sink->on_event ? sink->on_event(sink->handle, (const SerdEvent*)&ev) + : SERD_SUCCESS; } SerdStatus @@ -84,7 +80,10 @@ serd_sink_write_prefix(const SerdSink* sink, const SerdNode* name, const SerdNode* uri) { - return sink->prefix ? sink->prefix(sink->handle, name, uri) : SERD_SUCCESS; + const SerdPrefixEvent ev = {SERD_PREFIX, name, uri}; + + return sink->on_event ? sink->on_event(sink->handle, (const SerdEvent*)&ev) + : SERD_SUCCESS; } SerdStatus @@ -92,8 +91,10 @@ serd_sink_write_statement(const SerdSink* sink, const SerdStatementFlags flags, const SerdStatement* statement) { - return sink->statement ? sink->statement(sink->handle, flags, statement) - : SERD_SUCCESS; + const SerdStatementEvent ev = {SERD_STATEMENT, flags, statement}; + + return sink->on_event ? sink->on_event(sink->handle, (const SerdEvent*)&ev) + : SERD_SUCCESS; } SerdStatus @@ -111,5 +112,8 @@ serd_sink_write(const SerdSink* sink, SerdStatus serd_sink_write_end(const SerdSink* sink, const SerdNode* node) { - return sink->end ? sink->end(sink->handle, node) : SERD_SUCCESS; + const SerdEndEvent ev = {SERD_END, node}; + + return sink->on_event ? sink->on_event(sink->handle, (const SerdEvent*)&ev) + : SERD_SUCCESS; } diff --git a/src/sink.h b/src/sink.h index e0f99a53..6e8dffe3 100644 --- a/src/sink.h +++ b/src/sink.h @@ -23,12 +23,9 @@ An interface that receives a stream of RDF data. */ struct SerdSinkImpl { - void* handle; - SerdFreeFunc free_handle; - SerdBaseFunc base; - SerdPrefixFunc prefix; - SerdStatementFunc statement; - SerdEndFunc end; + void* handle; + SerdFreeFunc free_handle; + SerdEventFunc on_event; }; #endif // SERD_SINK_H diff --git a/src/writer.c b/src/writer.c index 4160c09d..9b10848f 100644 --- a/src/writer.c +++ b/src/writer.c @@ -935,6 +935,25 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node) return SERD_SUCCESS; } +static SerdStatus +serd_writer_on_event(SerdWriter* writer, const SerdEvent* event) +{ + switch (event->type) { + case SERD_BASE: + return serd_writer_set_base_uri(writer, event->base.uri); + case SERD_PREFIX: + return serd_writer_set_prefix( + writer, event->prefix.name, event->prefix.uri); + case SERD_STATEMENT: + return serd_writer_write_statement( + writer, event->statement.flags, event->statement.statement); + case SERD_END: + return serd_writer_end_anon(writer, event->end.node); + } + + return SERD_SUCCESS; +} + SerdStatus serd_writer_finish(SerdWriter* writer) { @@ -975,11 +994,8 @@ serd_writer_new(SerdWorld* world, writer->byte_sink = serd_byte_sink_new( ssink, stream, (flags & SERD_WRITE_BULK) ? SERD_PAGE_SIZE : 1); - writer->iface.handle = writer; - writer->iface.base = (SerdBaseFunc)serd_writer_set_base_uri; - writer->iface.prefix = (SerdPrefixFunc)serd_writer_set_prefix; - writer->iface.statement = (SerdStatementFunc)serd_writer_write_statement; - writer->iface.end = (SerdEndFunc)serd_writer_end_anon; + writer->iface.handle = writer; + writer->iface.on_event = (SerdEventFunc)serd_writer_on_event; return writer; } diff --git a/test/test_env.c b/test/test_env.c index 3e38f71d..da53df45 100644 --- a/test/test_env.c +++ b/test/test_env.c @@ -22,20 +22,21 @@ #include static SerdStatus -count_prefixes(void* handle, const SerdNode* name, const SerdNode* uri) +count_prefixes(void* handle, const SerdEvent* event) { - (void)name; - (void)uri; + if (event->type == SERD_PREFIX) { + ++*(int*)handle; + } - ++*(int*)handle; return SERD_SUCCESS; } static void test_env(void) { + static const SerdStringView eg = SERD_STATIC_STRING("http://example.org/"); + SerdNode* hello = serd_new_string(SERD_STATIC_STRING("hello\"")); - SerdNode* eg = serd_new_uri(SERD_STATIC_STRING("http://example.org/")); SerdNode* foo_u = serd_new_uri(SERD_STATIC_STRING("http://example.org/foo")); SerdNode* foo_c = serd_new_curie(SERD_STATIC_STRING("eg.2:foo")); SerdNode* b = serd_new_curie(SERD_STATIC_STRING("invalid")); @@ -43,7 +44,7 @@ test_env(void) const SerdStringView prefix = SERD_STATIC_STRING("eg.2"); SerdEnv* env = serd_env_new(SERD_EMPTY_STRING()); - serd_env_set_prefix(env, prefix, serd_node_string_view(eg)); + serd_env_set_prefix(env, prefix, eg); assert(!serd_env_base_uri(env)); assert(!serd_env_set_base_uri(env, SERD_EMPTY_STRING())); @@ -81,19 +82,22 @@ test_env(void) assert(!serd_env_expand(env, blank)); serd_node_free(blank); - int n_prefixes = 0; - serd_env_set_prefix(env, prefix, serd_node_string_view(eg)); - serd_env_foreach(env, count_prefixes, &n_prefixes); + size_t n_prefixes = 0; + SerdSink* const count_prefixes_sink = + serd_sink_new(&n_prefixes, count_prefixes, NULL); + + serd_env_set_prefix(env, prefix, eg); + serd_env_write_prefixes(env, count_prefixes_sink); assert(n_prefixes == 1); SerdNode* qualified = serd_env_qualify(env, foo_u); assert(serd_node_equals(qualified, foo_c)); serd_node_free(qualified); + serd_sink_free(count_prefixes_sink); serd_node_free(foo_c); serd_node_free(foo_u); serd_node_free(b); - serd_node_free(eg); serd_env_free(env); } diff --git a/test/test_read_chunk.c b/test/test_read_chunk.c index d77c3398..0f24089d 100644 --- a/test/test_read_chunk.c +++ b/test/test_read_chunk.c @@ -70,15 +70,29 @@ on_end(void* handle, const SerdNode* node) return SERD_SUCCESS; } +static SerdStatus +on_event(void* handle, const SerdEvent* event) +{ + switch (event->type) { + case SERD_BASE: + return on_base(handle, event->base.uri); + case SERD_PREFIX: + return on_prefix(handle, event->prefix.name, event->prefix.uri); + case SERD_STATEMENT: + return on_statement( + handle, event->statement.flags, event->statement.statement); + case SERD_END: + return on_end(handle, event->end.node); + } + + return SERD_SUCCESS; +} + int main(void) { SerdWorld* world = serd_world_new(); - SerdSink* sink = serd_sink_new(NULL, NULL); - serd_sink_set_base_func(sink, on_base); - serd_sink_set_prefix_func(sink, on_prefix); - serd_sink_set_statement_func(sink, on_statement); - serd_sink_set_end_func(sink, on_end); + SerdSink* sink = serd_sink_new(NULL, on_event, NULL); SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); assert(reader); @@ -109,5 +123,6 @@ main(void) serd_reader_free(reader); serd_sink_free(sink); serd_world_free(world); + return 0; } diff --git a/test/test_reader_writer.c b/test/test_reader_writer.c index 9ccfa967..b43337f5 100644 --- a/test/test_reader_writer.c +++ b/test/test_reader_writer.c @@ -26,14 +26,11 @@ #include static SerdStatus -test_sink(void* handle, - SerdStatementFlags flags, - const SerdStatement* statement) +count_statements(void* handle, const SerdEvent* event) { - (void)flags; - (void)statement; - - ++*(size_t*)handle; + if (event->type == SERD_STATEMENT) { + ++*(size_t*)handle; + } return SERD_SUCCESS; } @@ -72,13 +69,12 @@ test_read_chunks(void) size_t n_statements = 0; FILE* const f = tmpfile(); static const char null = 0; - SerdSink* sink = serd_sink_new(&n_statements, NULL); - SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); - assert(reader); + SerdSink* const sink = serd_sink_new(&n_statements, count_statements, NULL); assert(sink); - assert(f); - serd_sink_set_statement_func(sink, test_sink); + + SerdReader* const reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); + assert(reader); SerdStatus st = serd_reader_start_stream( reader, (SerdReadFunc)fread, (SerdStreamErrorFunc)ferror, f, NULL, 1); @@ -148,17 +144,14 @@ test_get_blank(void) static void test_read_string(void) { - SerdWorld* world = serd_world_new(); - size_t n_statements = 0; - SerdSink* sink = serd_sink_new(&n_statements, NULL); - SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); + SerdWorld* world = serd_world_new(); + size_t n_statements = 0; - assert(reader); + SerdSink* sink = serd_sink_new(&n_statements, count_statements, NULL); assert(sink); - serd_sink_set_statement_func(sink, test_sink); - - serd_sink_set_statement_func(sink, test_sink); + SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); + assert(reader); // Test reading a string that ends exactly at the end of input (no newline) assert( @@ -281,14 +274,14 @@ test_writer(const char* const path) static void test_reader(const char* path) { - SerdWorld* world = serd_world_new(); - size_t n_statements = 0; - SerdSink* const sink = serd_sink_new(&n_statements, NULL); - SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); + SerdWorld* world = serd_world_new(); + size_t n_statements = 0; - assert(reader); + SerdSink* const sink = serd_sink_new(&n_statements, count_statements, NULL); assert(sink); - serd_sink_set_statement_func(sink, test_sink); + + SerdReader* reader = serd_reader_new(world, SERD_TURTLE, sink, 4096); + assert(reader); serd_reader_add_blank_prefix(reader, "tmp"); -- cgit v1.2.1