diff options
author | David Robillard <d@drobilla.net> | 2018-10-21 20:57:33 +0200 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2022-01-13 23:04:05 -0500 |
commit | 89be71eabefcc55a3d0a56d7bed7124c4fdafbd8 (patch) | |
tree | 6c2b019b462999d1178441943d1cf90c4bd7b17c | |
parent | aca4640882aab7da0193ee2f5767ecd6d9506b0c (diff) | |
download | serd-89be71eabefcc55a3d0a56d7bed7124c4fdafbd8.tar.gz serd-89be71eabefcc55a3d0a56d7bed7124c4fdafbd8.tar.bz2 serd-89be71eabefcc55a3d0a56d7bed7124c4fdafbd8.zip |
Simplify streaming API and improve pretty printing
This removes the obligation from the caller to correctly maintain flags to
describe the current anonymous context, instead making the writer handle this
itself as much as possible. Flags remain for the cases the writer can not
infer from context: the start of anonymous subject and object nodes.
-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); |