diff options
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | include/serd/serd.h | 13 | ||||
-rw-r--r-- | src/n3.c | 44 | ||||
-rw-r--r-- | src/reader.c | 2 | ||||
-rw-r--r-- | src/stack.h | 6 | ||||
-rw-r--r-- | src/writer.c | 384 | ||||
-rw-r--r-- | test/good/base.ttl | 1 | ||||
-rw-r--r-- | test/good/pretty.trig | 98 | ||||
-rw-r--r-- | test/good/qualify-out.ttl | 1 | ||||
-rw-r--r-- | test/test_writer.c | 12 |
10 files changed, 371 insertions, 191 deletions
@@ -9,6 +9,7 @@ serd (1.0.1) unstable; * Remove support for Turtle named inline nodes extension * Remove useless character counting from API * Rename SerdChunk to SerdStringView + * Simplify streaming API and improve pretty printing * Simplify writer style options * Use a fixed-size reader stack * Use char* for strings in public API diff --git a/include/serd/serd.h b/include/serd/serd.h index da4291c6..06e11966 100644 --- a/include/serd/serd.h +++ b/include/serd/serd.h @@ -1246,14 +1246,11 @@ typedef enum { /// Flags indicating inline abbreviation information for a statement typedef enum { - SERD_EMPTY_S = 1u << 1u, ///< Empty blank node subject - SERD_EMPTY_O = 1u << 2u, ///< Empty blank node object - SERD_ANON_S_BEGIN = 1u << 3u, ///< Start of anonymous subject - SERD_ANON_O_BEGIN = 1u << 4u, ///< Start of anonymous object - SERD_ANON_CONT = 1u << 5u, ///< Continuation of anonymous node - SERD_LIST_S_BEGIN = 1u << 6u, ///< Start of list subject - SERD_LIST_O_BEGIN = 1u << 7u, ///< Start of list object - SERD_LIST_CONT = 1u << 8u ///< Continuation of list + SERD_EMPTY_S = 1u << 0u, ///< Empty blank node subject + SERD_ANON_S = 1u << 1u, ///< Start of anonymous subject + SERD_ANON_O = 1u << 2u, ///< Start of anonymous object + SERD_LIST_S = 1u << 3u, ///< Start of list subject + SERD_LIST_O = 1u << 4u ///< Start of list object } SerdStatementFlag; /// Bitwise OR of SerdStatementFlag values @@ -1072,13 +1072,15 @@ read_anon(SerdReader* const reader, const bool subject, SerdNode** const dest) { - const SerdStatementFlags old_flags = *ctx.flags; - bool empty = false; eat_byte_safe(reader, '['); - if ((empty = peek_delim(reader, ']'))) { - *ctx.flags |= (subject) ? SERD_EMPTY_S : SERD_EMPTY_O; + + const SerdStatementFlags old_flags = *ctx.flags; + const bool empty = peek_delim(reader, ']'); + + if (subject) { + *ctx.flags |= empty ? SERD_EMPTY_S : SERD_ANON_S; } else { - *ctx.flags |= (subject) ? SERD_ANON_S_BEGIN : SERD_ANON_O_BEGIN; + *ctx.flags |= SERD_ANON_O; } if (!*dest) { @@ -1087,31 +1089,33 @@ read_anon(SerdReader* const reader, } } + // Emit statement with this anonymous object first SerdStatus st = SERD_SUCCESS; if (ctx.subject) { TRY(st, emit_statement(reader, ctx, *dest)); } + // Switch the subject to the anonymous node and read its description ctx.subject = *dest; if (!empty) { - *ctx.flags &= ~(unsigned)SERD_LIST_CONT; - if (!subject) { - *ctx.flags |= SERD_ANON_CONT; - } - bool ate_dot_in_list = false; - if ((st = read_predicateObjectList(reader, ctx, &ate_dot_in_list))) { + st = read_predicateObjectList(reader, ctx, &ate_dot_in_list); + if (st > SERD_FAILURE) { return st; } if (ate_dot_in_list) { return r_err(reader, SERD_ERR_BAD_SYNTAX, "`.' inside blank\n"); } + read_ws_star(reader); - serd_sink_write_end(reader->sink, *dest); *ctx.flags = old_flags; } + if (!(subject && empty)) { + TRY(st, serd_sink_write_end(reader->sink, *dest)); + } + return eat_byte_check(reader, ']'); } @@ -1293,10 +1297,8 @@ read_predicateObjectList(SerdReader* const reader, } static SerdStatus -end_collection(SerdReader* const reader, ReadContext ctx, const SerdStatus st) +end_collection(SerdReader* const reader, const SerdStatus st) { - *ctx.flags &= ~(unsigned)SERD_LIST_CONT; - return st ? st : eat_byte_check(reader, ')'); } @@ -1316,15 +1318,14 @@ read_collection(SerdReader* const reader, if (ctx.subject) { // subject predicate _:head - *ctx.flags |= (end ? 0 : SERD_LIST_O_BEGIN); + *ctx.flags |= (end ? 0 : SERD_LIST_O); TRY(st, emit_statement(reader, ctx, *dest)); - *ctx.flags |= SERD_LIST_CONT; } else { - *ctx.flags |= (end ? 0 : SERD_LIST_S_BEGIN); + *ctx.flags |= (end ? 0 : SERD_LIST_S); } if (end) { - return end_collection(reader, ctx, st); + return end_collection(reader, st); } /* The order of node allocation here is necessarily not in stack order, @@ -1344,7 +1345,7 @@ read_collection(SerdReader* const reader, ctx.predicate = reader->rdf_first; bool ate_dot = false; if ((st = read_object(reader, &ctx, true, &ate_dot)) || ate_dot) { - return end_collection(reader, ctx, st); + return end_collection(reader, st); } if (!(end = peek_delim(reader, ')'))) { @@ -1359,7 +1360,6 @@ read_collection(SerdReader* const reader, } // _:node rdf:rest _:rest - *ctx.flags |= SERD_LIST_CONT; ctx.predicate = reader->rdf_rest; TRY(st, emit_statement(reader, ctx, (end ? reader->rdf_nil : rest))); @@ -1368,7 +1368,7 @@ read_collection(SerdReader* const reader, node = ctx.subject; // invariant } - return end_collection(reader, ctx, st); + return end_collection(reader, st); } static SerdStatus diff --git a/src/reader.c b/src/reader.c index 0f97d237..e88234ac 100644 --- a/src/reader.c +++ b/src/reader.c @@ -134,7 +134,7 @@ emit_statement(SerdReader* const reader, const SerdStatus st = serd_sink_write_statement(reader->sink, *ctx.flags, &statement); - *ctx.flags &= SERD_ANON_CONT | SERD_LIST_CONT; // Preserve only cont flags + *ctx.flags = 0; return st; } diff --git a/src/stack.h b/src/stack.h index 49f77843..074d43b0 100644 --- a/src/stack.h +++ b/src/stack.h @@ -46,6 +46,12 @@ serd_stack_new(size_t size) return stack; } +static inline void +serd_stack_clear(SerdStack* stack) +{ + stack->size = SERD_STACK_BOTTOM; +} + static inline bool serd_stack_is_empty(const SerdStack* stack) { diff --git a/src/writer.c b/src/writer.c index 6a4dda34..27960f1e 100644 --- a/src/writer.c +++ b/src/writer.c @@ -32,56 +32,73 @@ #include <stdlib.h> #include <string.h> +typedef enum { + CTX_NAMED, ///< Normal non-anonymous context + CTX_BLANK, ///< Anonymous blank node + CTX_LIST ///< Anonymous list +} ContextType; + typedef struct { - SerdNode* graph; - SerdNode* subject; - SerdNode* predicate; + ContextType type; + SerdNode* graph; + SerdNode* subject; + SerdNode* predicate; + bool indented_object; } WriteContext; -static const WriteContext WRITE_CONTEXT_NULL = {NULL, NULL, NULL}; +static const WriteContext WRITE_CONTEXT_NULL = {CTX_NAMED, + NULL, + NULL, + NULL, + false}; typedef enum { - SEP_NONE, + SEP_NONE, ///< Placeholder for nodes or nothing SEP_END_S, ///< End of a subject ('.') SEP_END_P, ///< End of a predicate (';') SEP_END_O, ///< End of an object (',') SEP_S_P, ///< Between a subject and predicate (whitespace) SEP_P_O, ///< Between a predicate and object (whitespace) SEP_ANON_BEGIN, ///< Start of anonymous node ('[') + SEP_ANON_S_P, ///< Between start of anonymous node and predicate SEP_ANON_END, ///< End of anonymous node (']') SEP_LIST_BEGIN, ///< Start of list ('(') SEP_LIST_SEP, ///< List separator (whitespace) SEP_LIST_END, ///< End of list (')') SEP_GRAPH_BEGIN, ///< Start of graph ('{') SEP_GRAPH_END, ///< End of graph ('}') - SEP_URI_BEGIN, ///< URI start quote ('<') - SEP_URI_END ///< URI end quote ('>') } Sep; +typedef uint32_t SepMask; ///< Bitfield of separator flags + +#define SEP_ALL ((SepMask)-1) +#define M(s) (1U << (s)) + typedef struct { - const char* str; ///< Sep string - uint8_t len; ///< Length of sep string - uint8_t space_before; ///< Newline before sep - uint8_t space_after_node; ///< Newline after sep if after node - uint8_t space_after_sep; ///< Newline after sep if after sep + const char* str; ///< Sep string + size_t len; ///< Length of sep string + int indent; ///< Indent delta + SepMask pre_space_after; ///< Leading space if after given seps + SepMask pre_line_after; ///< Leading newline if after given seps + SepMask post_line_after; ///< Trailing newline if after given seps } SepRule; -static const SepRule rules[] = {{NULL, 0, 0, 0, 0}, - {" .\n\n", 4, 0, 0, 0}, - {" ;", 2, 0, 1, 1}, - {" ,", 2, 0, 1, 0}, - {NULL, 0, 0, 1, 0}, - {" ", 1, 0, 0, 0}, - {"[", 1, 0, 1, 1}, - {"]", 1, 1, 0, 0}, - {"(", 1, 0, 0, 0}, - {NULL, 0, 0, 1, 0}, - {")", 1, 1, 0, 0}, - {" {", 2, 0, 1, 1}, - {" }", 2, 0, 1, 1}, - {"<", 1, 0, 0, 0}, - {">", 1, 0, 0, 0}, - {"\n", 1, 0, 1, 0}}; +static const SepRule rules[] = { + {"", 0, +0, SEP_NONE, SEP_NONE, SEP_NONE}, + {".\n", 2, -1, SEP_ALL, SEP_NONE, SEP_NONE}, + {";", 1, +0, SEP_ALL, SEP_NONE, SEP_ALL}, + {",", 1, +0, SEP_ALL, SEP_NONE, ~(M(SEP_ANON_END) | M(SEP_LIST_END))}, + {"", 0, +1, SEP_NONE, SEP_NONE, SEP_ALL}, + {" ", 1, +0, SEP_NONE, SEP_NONE, SEP_NONE}, + {"[", 1, +1, M(SEP_END_O), SEP_NONE, SEP_NONE}, + {"", 0, +0, SEP_NONE, SEP_ALL, SEP_NONE}, + {"]", 1, -1, SEP_NONE, ~M(SEP_ANON_BEGIN), SEP_NONE}, + {"(", 1, +1, M(SEP_END_O), SEP_NONE, SEP_ALL}, + {"", 0, +0, SEP_NONE, SEP_ALL, SEP_NONE}, + {")", 1, -1, SEP_NONE, SEP_ALL, SEP_NONE}, + {"{", 1, +1, SEP_ALL, SEP_NONE, SEP_NONE}, + {"}", 1, -1, SEP_NONE, SEP_NONE, SEP_ALL}, +}; struct SerdWriterImpl { SerdWorld* world; @@ -95,12 +112,10 @@ struct SerdWriterImpl { SerdWriteFunc write_func; void* stream; WriteContext context; - SerdNode* list_subj; - unsigned list_depth; - unsigned indent; char* bprefix; size_t bprefix_len; Sep last_sep; + int indent; bool empty; }; @@ -129,13 +144,41 @@ supports_uriref(const SerdWriter* writer) return writer->syntax == SERD_TURTLE || writer->syntax == SERD_TRIG; } -SERD_PURE_FUNC -static WriteContext* -anon_stack_top(SerdWriter* const writer) +static SerdStatus +free_context(SerdWriter* writer) +{ + serd_node_free(writer->context.graph); + serd_node_free(writer->context.subject); + serd_node_free(writer->context.predicate); + return SERD_SUCCESS; +} + +static SerdStatus +push_context(SerdWriter* writer, const WriteContext new_context) +{ + WriteContext* top = + (WriteContext*)serd_stack_push(&writer->anon_stack, sizeof(WriteContext)); + + if (!top) { + return SERD_ERR_OVERFLOW; + } + + *top = writer->context; + writer->context = new_context; + return SERD_SUCCESS; +} + +static void +pop_context(SerdWriter* writer) { assert(!serd_stack_is_empty(&writer->anon_stack)); - return (WriteContext*)(writer->anon_stack.buf + writer->anon_stack.size - - sizeof(WriteContext)); + + free_context(writer); + writer->context = + *(WriteContext*)(writer->anon_stack.buf + writer->anon_stack.size - + sizeof(WriteContext)); + + serd_stack_pop(&writer->anon_stack, sizeof(WriteContext)); } static SerdNode* @@ -432,7 +475,7 @@ static void write_newline(SerdWriter* writer) { sink("\n", 1, writer); - for (unsigned i = 0; i < writer->indent; ++i) { + for (int i = 0; i < writer->indent; ++i) { sink("\t", 1, writer); } } @@ -441,47 +484,65 @@ static bool write_sep(SerdWriter* writer, const Sep sep) { const SepRule* rule = &rules[sep]; - if (rule->space_before) { - write_newline(writer); - } - if (rule->str) { - sink(rule->str, rule->len, writer); + // Adjust indent, but tolerate if it would become negative + if ((rule->pre_line_after & (1u << writer->last_sep)) || + (rule->post_line_after & (1u << writer->last_sep))) { + writer->indent = ((rule->indent >= 0 || writer->indent >= -rule->indent) + ? writer->indent + rule->indent + : 0); } - if ((writer->last_sep && rule->space_after_sep) || - (!writer->last_sep && rule->space_after_node)) { + // Write newline or space before separator if necessary + if (rule->pre_line_after & (1u << writer->last_sep)) { write_newline(writer); - } else if (writer->last_sep && rule->space_after_node) { + } else if (rule->pre_space_after & (1u << writer->last_sep)) { sink(" ", 1, writer); } - writer->last_sep = sep; + // Write actual separator string + sink(rule->str, rule->len, writer); + + // Write newline after separator if necessary + if (rule->post_line_after & (1u << writer->last_sep)) { + write_newline(writer); + writer->last_sep = SEP_NONE; + } else { + writer->last_sep = sep; + } + + if (sep == SEP_END_S) { + writer->indent = 0; + } + return true; } static SerdStatus reset_context(SerdWriter* writer, bool graph) { + // Free any lingering contexts in case there was an error + while (!serd_stack_is_empty(&writer->anon_stack)) { + pop_context(writer); + } + if (graph && writer->context.graph) { memset(writer->context.graph, 0, sizeof(SerdNode)); } + if (writer->context.subject) { memset(writer->context.subject, 0, sizeof(SerdNode)); } + if (writer->context.predicate) { memset(writer->context.predicate, 0, sizeof(SerdNode)); } - writer->empty = false; - return SERD_SUCCESS; -} -static SerdStatus -free_context(const WriteContext* const ctx) -{ - serd_node_free(ctx->graph); - serd_node_free(ctx->subject); - serd_node_free(ctx->predicate); + writer->context.indented_object = false; + writer->empty = false; + + serd_stack_clear(&writer->anon_stack); + return SERD_SUCCESS; } @@ -491,8 +552,8 @@ is_inline_start(const SerdWriter* writer, SerdStatementFlags flags) { return (supports_abbrev(writer) && - ((field == SERD_SUBJECT && (flags & SERD_ANON_S_BEGIN)) || - (field == SERD_OBJECT && (flags & SERD_ANON_O_BEGIN)))); + ((field == SERD_SUBJECT && (flags & SERD_ANON_S)) || + (field == SERD_OBJECT && (flags & SERD_ANON_O)))); } static bool @@ -500,6 +561,8 @@ write_literal(SerdWriter* const writer, const SerdNode* const node, const SerdStatementFlags flags) { + writer->last_sep = SEP_NONE; + const SerdNode* datatype = serd_node_datatype(node); const SerdNode* lang = serd_node_language(node); const char* node_str = serd_node_string(node); @@ -561,11 +624,10 @@ write_uri_node(SerdWriter* const writer, const SerdNode* const node, const SerdField field) { - const SerdNode* prefix = NULL; - SerdStringView suffix = {NULL, 0}; - - const char* node_str = serd_node_string(node); - const bool has_scheme = serd_uri_string_has_scheme(node_str); + const SerdNode* prefix = NULL; + SerdStringView suffix = {NULL, 0}; + const char* node_str = serd_node_string(node); + const bool has_scheme = serd_uri_string_has_scheme(node_str); if (supports_abbrev(writer)) { if (field == SERD_PREDICATE && serd_node_equals(node, writer->world->rdf_type)) { @@ -596,7 +658,7 @@ write_uri_node(SerdWriter* const writer, return false; } - write_sep(writer, SEP_URI_BEGIN); + sink("<", 1, writer); if (!(writer->flags & SERD_WRITE_UNRESOLVED) && serd_env_base_uri(writer->env)) { const SerdURIView base_uri = serd_env_base_uri_view(writer->env); @@ -614,14 +676,15 @@ write_uri_node(SerdWriter* const writer, } else { write_uri_from_node(writer, node); } - write_sep(writer, SEP_URI_END); + + sink(">", 1, writer); return true; } static bool write_curie(SerdWriter* const writer, const SerdNode* const node) { - const char* node_str = serd_node_string(node); + writer->last_sep = SEP_NONE; SerdStringView prefix = {NULL, 0}; SerdStringView suffix = {NULL, 0}; @@ -630,18 +693,20 @@ write_curie(SerdWriter* const writer, const SerdNode* const node) case SERD_NTRIPLES: case SERD_NQUADS: if ((st = serd_env_expand_in_place(writer->env, node, &prefix, &suffix))) { - serd_world_errorf( - writer->world, st, "undefined namespace prefix `%s'\n", node_str); + serd_world_errorf(writer->world, + st, + "undefined namespace prefix `%s'\n", + serd_node_string(node)); return false; } - write_sep(writer, SEP_URI_BEGIN); + sink("<", 1, writer); write_uri(writer, prefix.buf, prefix.len); write_uri(writer, suffix.buf, suffix.len); - write_sep(writer, SEP_URI_END); + sink(">", 1, writer); break; case SERD_TURTLE: case SERD_TRIG: - write_lname(writer, node_str, node->length); + write_lname(writer, serd_node_string(node), node->length); break; } @@ -657,26 +722,16 @@ write_blank(SerdWriter* const writer, const char* node_str = serd_node_string(node); if (supports_abbrev(writer)) { if (is_inline_start(writer, field, flags)) { - ++writer->indent; return write_sep(writer, SEP_ANON_BEGIN); } - if (field == SERD_SUBJECT && (flags & SERD_LIST_S_BEGIN)) { - assert(writer->list_depth == 0); - serd_node_set(&writer->list_subj, node); - ++writer->list_depth; - ++writer->indent; - return write_sep(writer, SEP_LIST_BEGIN); - } - - if (field == SERD_OBJECT && (flags & SERD_LIST_O_BEGIN)) { - ++writer->indent; - ++writer->list_depth; + if ((field == SERD_SUBJECT && (flags & SERD_LIST_S)) || + (field == SERD_OBJECT && (flags & SERD_LIST_O))) { return write_sep(writer, SEP_LIST_BEGIN); } - if ((field == SERD_SUBJECT && (flags & SERD_EMPTY_S)) || - (field == SERD_OBJECT && (flags & SERD_EMPTY_O))) { + if (field == SERD_SUBJECT && (flags & SERD_EMPTY_S)) { + writer->last_sep = SEP_NONE; // Treat "[]" like a node return sink("[]", 2, writer) == 2; } } @@ -691,6 +746,7 @@ write_blank(SerdWriter* const writer, sink(node_str, node->length, writer); } + writer->last_sep = SEP_NONE; return true; } @@ -700,23 +756,18 @@ write_node(SerdWriter* const writer, const SerdField field, const SerdStatementFlags flags) { - bool ret = false; switch (node->type) { case SERD_LITERAL: - ret = write_literal(writer, node, flags); - break; + return write_literal(writer, node, flags); case SERD_URI: - ret = write_uri_node(writer, node, field); - break; + return write_uri_node(writer, node, field); case SERD_CURIE: - ret = write_curie(writer, node); - break; + return write_curie(writer, node); case SERD_BLANK: - ret = write_blank(writer, node, field, flags); - break; + return write_blank(writer, node, field, flags); } - writer->last_sep = SEP_NONE; - return ret; + + return false; } static bool @@ -740,14 +791,14 @@ write_list_obj(SerdWriter* const writer, const SerdNode* const object) { if (serd_node_equals(object, writer->world->rdf_nil)) { - --writer->indent; write_sep(writer, SEP_LIST_END); return true; } if (serd_node_equals(predicate, writer->world->rdf_first)) { - write_sep(writer, SEP_LIST_SEP); write_node(writer, object, SERD_OBJECT, flags); + } else { + write_sep(writer, SEP_LIST_SEP); } return false; @@ -758,6 +809,10 @@ serd_writer_write_statement(SerdWriter* const writer, const SerdStatementFlags flags, const SerdStatement* const statement) { + assert(!((flags & SERD_ANON_S) && (flags & SERD_LIST_S))); + assert(!((flags & SERD_ANON_O) && (flags & SERD_LIST_O))); + + SerdStatus st = SERD_SUCCESS; const SerdNode* const subject = serd_statement_subject(statement); const SerdNode* const predicate = serd_statement_predicate(statement); const SerdNode* const object = serd_statement_object(statement); @@ -790,47 +845,51 @@ serd_writer_write_statement(SerdWriter* const writer, if ((graph && !serd_node_equals(graph, writer->context.graph)) || (!graph && ctx(writer, SERD_GRAPH))) { - writer->indent = 0; if (ctx(writer, SERD_SUBJECT)) { write_sep(writer, SEP_END_S); } + if (ctx(writer, SERD_GRAPH)) { write_sep(writer, SEP_GRAPH_END); } + if (!writer->empty) { + write_newline(writer); // Blank line between top level items + } + reset_context(writer, true); + if (graph) { TRY(write_node(writer, graph, SERD_GRAPH, flags)); - ++writer->indent; write_sep(writer, SEP_GRAPH_BEGIN); serd_node_set(&writer->context.graph, graph); } } - if ((flags & SERD_LIST_CONT)) { + if (writer->context.type == CTX_LIST) { if (write_list_obj(writer, flags, predicate, object)) { // Reached end of list - if (--writer->list_depth == 0 && writer->list_subj) { - reset_context(writer, false); - serd_node_free(writer->context.subject); - writer->context.subject = writer->list_subj; - writer->list_subj = NULL; - } + pop_context(writer); return SERD_SUCCESS; } } else if (serd_node_equals(subject, writer->context.subject)) { if (serd_node_equals(predicate, writer->context.predicate)) { // Abbreviate S P - if (!(flags & SERD_ANON_O_BEGIN)) { + if (!writer->context.indented_object && + !(flags & (SERD_ANON_O | SERD_LIST_O))) { ++writer->indent; + writer->context.indented_object = true; } + write_sep(writer, SEP_END_O); write_node(writer, object, SERD_OBJECT, flags); - if (!(flags & SERD_ANON_O_BEGIN)) { - --writer->indent; - } } else { // Abbreviate S + if (writer->context.indented_object && writer->indent > 0) { + --writer->indent; + writer->context.indented_object = false; + } + Sep sep = ctx(writer, SERD_PREDICATE) ? SEP_END_P : SEP_S_P; write_sep(writer, sep); write_pred(writer, flags, predicate); @@ -838,49 +897,73 @@ serd_writer_write_statement(SerdWriter* const writer, } } else { // No abbreviation - if (ctx(writer, SERD_SUBJECT)) { - assert(writer->indent > 0); + if (writer->context.indented_object && writer->indent > 0) { --writer->indent; - if (serd_stack_is_empty(&writer->anon_stack)) { - write_sep(writer, SEP_END_S); + writer->context.indented_object = false; + } + + if (serd_stack_is_empty(&writer->anon_stack)) { + if (ctx(writer, SERD_SUBJECT)) { + write_sep(writer, SEP_END_S); // Terminate last subject + } + if (!writer->empty) { + write_newline(writer); // Blank line between top level items } - } else if (!writer->empty) { - write_sep(writer, SEP_S_P); } - if (!(flags & SERD_ANON_CONT)) { + if (serd_stack_is_empty(&writer->anon_stack)) { write_node(writer, subject, SERD_SUBJECT, flags); - ++writer->indent; - write_sep(writer, SEP_S_P); + if (!(flags & (SERD_ANON_S | SERD_LIST_S))) { + write_sep(writer, SEP_S_P); + } else if (flags & SERD_ANON_S) { + write_sep(writer, SEP_ANON_S_P); + } } else { - ++writer->indent; + write_sep(writer, SEP_ANON_S_P); } reset_context(writer, false); serd_node_set(&writer->context.subject, subject); - if (!(flags & SERD_LIST_S_BEGIN)) { + if (!(flags & SERD_LIST_S)) { write_pred(writer, flags, predicate); } write_node(writer, object, SERD_OBJECT, flags); } - if (flags & (SERD_ANON_S_BEGIN | SERD_ANON_O_BEGIN)) { - WriteContext* ctx = - (WriteContext*)serd_stack_push(&writer->anon_stack, sizeof(WriteContext)); - if (!ctx) { - return SERD_ERR_OVERFLOW; + // Push context for anonymous or list subject if necessary + if (flags & (SERD_ANON_S | SERD_LIST_S)) { + const bool is_list = (flags & SERD_LIST_S); + + const WriteContext ctx = {is_list ? CTX_LIST : CTX_BLANK, + serd_node_copy(graph), + serd_node_copy(subject), + is_list ? NULL : serd_node_copy(predicate), + false}; + if ((st = push_context(writer, ctx))) { + return st; } + } - *ctx = writer->context; - WriteContext new_context = { - serd_node_copy(graph), serd_node_copy(subject), NULL}; - if ((flags & SERD_ANON_S_BEGIN)) { - new_context.predicate = serd_node_copy(predicate); + // Push context for anonymous or list object if necessary + if (flags & (SERD_ANON_O | SERD_LIST_O)) { + const bool is_list = (flags & SERD_LIST_O); + + const WriteContext ctx = {is_list ? CTX_LIST : CTX_BLANK, + serd_node_copy(graph), + serd_node_copy(object), + NULL, + false}; + if ((st = push_context(writer, ctx))) { + serd_node_free(ctx.graph); + serd_node_free(ctx.subject); + return st; } - writer->context = new_context; - } else { + } + + if (!(flags & (SERD_ANON_S | SERD_LIST_S | SERD_ANON_O | SERD_LIST_O))) { + // Update current context to this statement serd_node_set(&writer->context.graph, graph); serd_node_set(&writer->context.subject, subject); serd_node_set(&writer->context.predicate, predicate); @@ -896,20 +979,18 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node) return SERD_SUCCESS; } - if (serd_stack_is_empty(&writer->anon_stack) || writer->indent == 0) { - return serd_world_errorf( - writer->world, SERD_ERR_UNKNOWN, "unexpected end of anonymous node\n"); + if (serd_stack_is_empty(&writer->anon_stack)) { + return serd_world_errorf(writer->world, + SERD_ERR_UNKNOWN, + "unexpected end of anonymous node `%s'\n", + serd_node_string(node)); } - --writer->indent; write_sep(writer, SEP_ANON_END); - free_context(&writer->context); - writer->context = *anon_stack_top(writer); - serd_stack_pop(&writer->anon_stack, sizeof(WriteContext)); + pop_context(writer); - const bool is_subject = serd_node_equals(node, writer->context.subject); - if (is_subject) { - serd_node_set(&writer->context.subject, node); + if (serd_node_equals(node, writer->context.subject)) { + // Now-finished anonymous node is the new subject with no other context memset(writer->context.predicate, 0, sizeof(SerdNode)); } @@ -941,10 +1022,17 @@ serd_writer_finish(SerdWriter* writer) if (ctx(writer, SERD_SUBJECT)) { write_sep(writer, SEP_END_S); } + if (ctx(writer, SERD_GRAPH)) { write_sep(writer, SEP_GRAPH_END); } - free_context(&writer->context); + + // Free any lingering contexts in case there was an error + while (!serd_stack_is_empty(&writer->anon_stack)) { + pop_context(writer); + } + + free_context(writer); writer->indent = 0; writer->context = WRITE_CONTEXT_NULL; return SERD_SUCCESS; @@ -971,7 +1059,6 @@ serd_writer_new(SerdWorld* world, writer->write_func = write_func; writer->stream = stream; writer->context = context; - writer->list_subj = NULL; writer->empty = true; writer->iface.handle = writer; @@ -1074,13 +1161,6 @@ serd_writer_free(SerdWriter* writer) } serd_writer_finish(writer); - - // Free any leaked entries in the anonymous context stack - while (!serd_stack_is_empty(&writer->anon_stack)) { - free_context(anon_stack_top(writer)); - serd_stack_pop(&writer->anon_stack, sizeof(WriteContext)); - } - serd_stack_free(&writer->anon_stack); free(writer->bprefix); serd_node_free(writer->root_node); diff --git a/test/good/base.ttl b/test/good/base.ttl index 4c437937..755c1d09 100644 --- a/test/good/base.ttl +++ b/test/good/base.ttl @@ -1,3 +1,2 @@ <foo> a <Bar> . - diff --git a/test/good/pretty.trig b/test/good/pretty.trig new file mode 100644 index 00000000..140769ce --- /dev/null +++ b/test/good/pretty.trig @@ -0,0 +1,98 @@ +@prefix : <http://example.org/> . +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . + +:a + :b :c , + :d , + :e ; + :f :g , + :h . + +( + 1 +) + :isA :List . + +[] + :isA :Blank . + +( + 2 +) + :sameAs ( + 2 + ) . + +[] + :sameAs [] . + +( + 1 + 2 +) + a :List ; + rdf:value 3 . + +( + ( + 3 + ) + ( + 4 + ) +) + a :NestedList ; + :sum 7 . + +[ + a :BlankSubject +] + a rdf:Resource . + +[ + a :BlankSubject +] . + +[] + :blank [ + :nestedEmptyBlank [] ; + :nestedNonEmptyBlanks [ + rdf:value 1 + ] , [ + rdf:value 2 + ] + ] ; + :lists ( + 3 + 4 + ) , ( + 5 + 6 + ) , ( + [ + rdf:value 7 + ] + [ + rdf:value 8 + ] + ) . + +:s + a :Thing ; + :predicate1 :object1 , + [ + a :SubThing ; + :predicate2 :object2 + ] , [ + a :OtherSubThing ; + :p3 :o3 + ] ; + :p4 :o4 . + +eg:graph { + :a + :b :c ; + :d [ + :e :f + ] . +} diff --git a/test/good/qualify-out.ttl b/test/good/qualify-out.ttl index 97f67a53..42e4091a 100644 --- a/test/good/qualify-out.ttl +++ b/test/good/qualify-out.ttl @@ -2,4 +2,3 @@ eg:s eg:p eg:o . - diff --git a/test/test_writer.c b/test/test_writer.c index d7c4eac8..0611a40d 100644 --- a/test/test_writer.c +++ b/test/test_writer.c @@ -103,7 +103,7 @@ test_write_long_literal(void) static const char* const expected = "<http://example.org/s>\n" - "\t<http://example.org/p> \"\"\"hello \"\"\\\"world\"\"\\\"!\"\"\" .\n\n"; + "\t<http://example.org/p> \"\"\"hello \"\"\\\"world\"\"\\\"!\"\"\" .\n"; assert(!strcmp((char*)out, expected)); serd_free(out); @@ -141,10 +141,9 @@ test_writer_stack_overflow(void) const SerdNode* const p = serd_nodes_uri(nodes, SERD_STRING("http://example.org/p")); - const SerdNode* o = - serd_nodes_blank(nodes, SERD_STRING("http://example.org/o")); + const SerdNode* o = serd_nodes_blank(nodes, SERD_STRING("blank")); - SerdStatus st = serd_sink_write(sink, SERD_ANON_O_BEGIN, s, p, o, NULL); + SerdStatus st = serd_sink_write(sink, SERD_ANON_O, s, p, o, NULL); assert(!st); // Repeatedly write nested anonymous objects until the writer stack overflows @@ -154,8 +153,9 @@ test_writer_stack_overflow(void) const SerdNode* next_o = serd_nodes_blank(nodes, SERD_STRING(buf)); - st = serd_sink_write(sink, SERD_ANON_O_BEGIN, o, p, next_o, NULL); - o = next_o; + st = serd_sink_write(sink, SERD_ANON_O, o, p, next_o, NULL); + + o = next_o; if (st) { assert(st == SERD_ERR_OVERFLOW); |