From f43066a36f98b89b4d853d3168ff0fe2edeb41d7 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 5 Apr 2023 16:30:39 -0400 Subject: Improve pretty-printing of lists and inline subjects --- NEWS | 1 + src/attributes.h | 6 + src/n3.c | 23 +- src/reader.h | 6 - src/writer.c | 373 +++++++++++++-------- test/extra/pretty/abbreviation.ttl | 3 +- test/extra/pretty/anonymous-in-list-object.ttl | 3 +- test/extra/pretty/graph-abbreviation.trig | 3 +- test/extra/pretty/inline-blank-subject.ttl | 3 +- test/extra/pretty/inline-blanks-and-lists.ttl | 17 +- test/extra/pretty/inline-list-subject.ttl | 3 +- test/extra/pretty/list-subject-with-extras.ttl | 9 + .../extra/pretty/list-subject-with-list-extras.ttl | 12 + test/extra/pretty/list-subject.ttl | 3 +- test/extra/pretty/manifest.ttl | 21 ++ .../pretty/nested-list-object-with-empty-lists.ttl | 11 + test/extra/pretty/nested-list-object.ttl | 3 +- test/extra/pretty/nested-list-subject.ttl | 6 +- 18 files changed, 335 insertions(+), 171 deletions(-) create mode 100644 test/extra/pretty/list-subject-with-extras.ttl create mode 100644 test/extra/pretty/list-subject-with-list-extras.ttl create mode 100644 test/extra/pretty/nested-list-object-with-empty-lists.ttl diff --git a/NEWS b/NEWS index 7a105e1c..364140a3 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ serd (0.31.1) unstable; urgency=medium * Gracefully handle bad characters in Turtle blank node syntax * Gracefully handle bad characters in Turtle datatype syntax * Improve TriG pretty-printing and remove trailing newlines + * Improve pretty-printing of lists and inline subjects * Improve serdi man page * Improve writer error handling * Make URI writing stricter by default diff --git a/src/attributes.h b/src/attributes.h index bf4e2658..11c699f3 100644 --- a/src/attributes.h +++ b/src/attributes.h @@ -4,6 +4,12 @@ #ifndef SERD_SRC_ATTRIBUTES_H #define SERD_SRC_ATTRIBUTES_H +#if defined(__GNUC__) +# define SERD_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) +#else +# define SERD_LOG_FUNC(fmt, arg1) +#endif + #ifdef __GNUC__ # define SERD_MALLOC_FUNC __attribute__((malloc)) #else diff --git a/src/n3.c b/src/n3.c index 56b4a625..ebeb6e17 100644 --- a/src/n3.c +++ b/src/n3.c @@ -1065,13 +1065,15 @@ read_anon(SerdReader* const reader, const bool subject, Ref* const dest) { - const SerdStatementFlags old_flags = *ctx.flags; - bool empty = false; skip_byte(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_BEGIN; } else { - *ctx.flags |= (subject) ? SERD_ANON_S_BEGIN : SERD_ANON_O_BEGIN; + *ctx.flags |= empty ? SERD_EMPTY_O : SERD_ANON_O_BEGIN; if (peek_delim(reader, '=')) { if (!(*dest = read_blankName(reader)) || !eat_delim(reader, ';')) { return SERD_ERR_BAD_SYNTAX; @@ -1083,11 +1085,13 @@ read_anon(SerdReader* const reader, *dest = blank_id(reader); } + // Emit statement with this anonymous object first SerdStatus st = SERD_SUCCESS; if (ctx.subject) { TRY(st, emit_statement(reader, ctx, *dest, 0, 0)); } + // Switch the subject to the anonymous node and read its description ctx.subject = *dest; if (!empty) { *ctx.flags &= ~(unsigned)SERD_LIST_CONT; @@ -1096,7 +1100,8 @@ read_anon(SerdReader* const reader, } bool ate_dot_in_list = false; - read_predicateObjectList(reader, ctx, &ate_dot_in_list); + TRY_FAILING(st, read_predicateObjectList(reader, ctx, &ate_dot_in_list)); + if (ate_dot_in_list) { return r_err(reader, SERD_ERR_BAD_SYNTAX, "'.' inside blank\n"); } @@ -1316,12 +1321,12 @@ read_collection(SerdReader* const reader, ReadContext ctx, Ref* const dest) bool end = peek_delim(reader, ')'); *dest = end ? reader->rdf_nil : blank_id(reader); - if (ctx.subject) { - // subject predicate _:head + if (ctx.subject) { // Reading a collection object *ctx.flags |= (end ? 0 : SERD_LIST_O_BEGIN); TRY(st, emit_statement(reader, ctx, *dest, 0, 0)); + *ctx.flags &= SERD_LIST_O_BEGIN; *ctx.flags |= SERD_LIST_CONT; - } else { + } else { // Reading a collection subject *ctx.flags |= (end ? 0 : SERD_LIST_S_BEGIN); } diff --git a/src/reader.h b/src/reader.h index a869e56e..54914ee5 100644 --- a/src/reader.h +++ b/src/reader.h @@ -15,12 +15,6 @@ #include #include -#if defined(__GNUC__) -# define SERD_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) -#else -# define SERD_LOG_FUNC(fmt, arg1) -#endif - #ifdef SERD_STACK_CHECK # define SERD_STACK_ASSERT_TOP(reader, ref) \ assert(ref == reader->allocs[reader->n_allocs - 1]); diff --git a/src/writer.c b/src/writer.c index cdd925d7..656cc7bc 100644 --- a/src/writer.c +++ b/src/writer.c @@ -11,7 +11,6 @@ #include "serd/serd.h" -#include #include #include #include @@ -19,6 +18,12 @@ #include #include +typedef enum { + CTX_NAMED, ///< Normal non-anonymous context + CTX_BLANK, ///< Anonymous blank node + CTX_LIST, ///< Anonymous list +} ContextType; + typedef enum { FIELD_NONE, FIELD_SUBJECT, @@ -28,22 +33,32 @@ typedef enum { } Field; typedef struct { - SerdNode graph; - SerdNode subject; - SerdNode predicate; + ContextType type; + SerdNode graph; + SerdNode subject; + SerdNode predicate; + bool predicates; + bool comma_indented; } WriteContext; -static const WriteContext WRITE_CONTEXT_NULL = {{0, 0, 0, 0, SERD_NOTHING}, +static const WriteContext WRITE_CONTEXT_NULL = {CTX_NAMED, + {0, 0, 0, 0, SERD_NOTHING}, + {0, 0, 0, 0, SERD_NOTHING}, {0, 0, 0, 0, SERD_NOTHING}, - {0, 0, 0, 0, SERD_NOTHING}}; + 0U, + 0U}; typedef enum { - SEP_NOTHING, ///< Sentinel before the start of a document + SEP_NONE, ///< Sentinel before the start of a document SEP_NODE, ///< Sentinel after a node + SEP_NEWLINE, ///< Sentinel after a node SEP_END_DIRECT, ///< End of a directive (like "@prefix") SEP_END_S, ///< End of a subject ('.') SEP_END_P, ///< End of a predicate (';') - SEP_END_O, ///< End of an object (',') + SEP_END_O, ///< End of a named object (',') + SEP_JOIN_O_AN, ///< End of anonymous object (',') before a named one + SEP_JOIN_O_NA, ///< End of named object (',') before an anonymous one + SEP_JOIN_O_AA, ///< End of anonymous object (',') before another 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 ('[') @@ -56,30 +71,46 @@ typedef enum { SEP_GRAPH_END, ///< End of graph ('}') } Sep; +typedef uint32_t SepMask; ///< Bitfield of separator flags + 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 + char sep; ///< Sep character + 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}, - {NULL, 0, 0, 0, 0}, - {" .", 2, 0, 1, 1}, - {" .\n", 3, 0, 0, 0}, - {" ;", 2, 0, 1, 1}, - {" ,", 2, 0, 1, 0}, - {NULL, 0, 0, 1, 0}, - {" ", 1, 0, 0, 0}, - {"[", 1, 0, 1, 1}, - {NULL, 0, 0, 0, 0}, - {"]", 1, 1, 0, 0}, - {"(", 1, 0, 0, 0}, - {NULL, 0, 0, 1, 0}, - {")", 1, 1, 0, 0}, - {" {", 2, 0, 1, 1}, - {"}\n", 2, 0, 0, 0}}; +#define SEP_EACH (~(SepMask)0) +#define M(s) (1U << (s)) +#define NIL '\0' + +static const SepRule rules[] = { + {NIL, +0, SEP_NONE, SEP_NONE, SEP_NONE}, + {NIL, +0, SEP_NONE, SEP_NONE, SEP_NONE}, + {'\n', 0, SEP_NONE, SEP_NONE, SEP_NONE}, + {'.', +0, SEP_EACH, SEP_NONE, SEP_EACH}, + {'.', +0, SEP_EACH, SEP_NONE, SEP_NONE}, + {';', +0, SEP_EACH, SEP_NONE, SEP_EACH}, + {',', +0, SEP_EACH, SEP_NONE, SEP_EACH}, + {',', +0, SEP_EACH, SEP_NONE, SEP_EACH}, + {',', +0, SEP_EACH, SEP_NONE, SEP_EACH}, + {',', +0, SEP_EACH, SEP_NONE, SEP_NONE}, + {NIL, +1, SEP_NONE, SEP_NONE, SEP_EACH}, + {' ', +0, SEP_NONE, SEP_NONE, SEP_NONE}, + {'[', +1, M(SEP_JOIN_O_AA), SEP_NONE, SEP_NONE}, + {NIL, +1, SEP_NONE, SEP_NONE, M(SEP_ANON_BEGIN)}, + {']', -1, SEP_NONE, ~M(SEP_ANON_BEGIN), SEP_NONE}, + {'(', +1, M(SEP_JOIN_O_AA), SEP_NONE, SEP_EACH}, + {NIL, +0, SEP_NONE, SEP_EACH, SEP_NONE}, + {')', -1, SEP_NONE, SEP_EACH, SEP_NONE}, + {'{', +1, SEP_EACH, SEP_NONE, SEP_EACH}, + {'}', -1, SEP_NONE, SEP_NONE, SEP_EACH}, +}; + +#undef NIL +#undef M +#undef SEP_EACH struct SerdWriterImpl { SerdSyntax syntax; @@ -93,12 +124,10 @@ struct SerdWriterImpl { SerdErrorSink error_sink; void* error_handle; WriteContext context; - SerdNode list_subj; - unsigned list_depth; - unsigned indent; uint8_t* bprefix; size_t bprefix_len; Sep last_sep; + int indent; }; typedef enum { WRITE_STRING, WRITE_LONG_STRING } TextContext; @@ -124,14 +153,19 @@ supports_uriref(const SerdWriter* writer) return writer->syntax == SERD_TURTLE || writer->syntax == SERD_TRIG; } -static void -deindent(SerdWriter* writer) +static SerdStatus +free_context(WriteContext* const ctx) { - if (writer->indent) { - --writer->indent; - } + serd_node_free(&ctx->graph); + serd_node_free(&ctx->subject); + serd_node_free(&ctx->predicate); + ctx->graph.type = SERD_NOTHING; + ctx->subject.type = SERD_NOTHING; + ctx->predicate.type = SERD_NOTHING; + return SERD_SUCCESS; } +SERD_LOG_FUNC(3, 4) static SerdStatus w_err(SerdWriter* writer, SerdStatus st, const char* fmt, ...) { @@ -149,29 +183,50 @@ w_err(SerdWriter* writer, SerdStatus st, const char* fmt, ...) return st; } -SERD_PURE_FUNC static WriteContext* -anon_stack_top(SerdWriter* writer) -{ - assert(!serd_stack_is_empty(&writer->anon_stack)); - return (WriteContext*)(writer->anon_stack.buf + writer->anon_stack.size - - sizeof(WriteContext)); -} - static void copy_node(SerdNode* dst, const SerdNode* src) { - if (src) { - dst->buf = (uint8_t*)realloc((char*)dst->buf, src->n_bytes + 1); + const size_t new_size = src->n_bytes + 1U; + uint8_t* const new_buf = (uint8_t*)realloc((char*)dst->buf, new_size); + if (new_buf) { + dst->buf = new_buf; dst->n_bytes = src->n_bytes; dst->n_chars = src->n_chars; dst->flags = src->flags; dst->type = src->type; - memcpy((char*)dst->buf, src->buf, src->n_bytes + 1); - } else { - dst->type = SERD_NOTHING; + memcpy((char*)dst->buf, src->buf, new_size); } } +static void +push_context(SerdWriter* const writer, + const ContextType type, + const SerdNode graph, + const SerdNode subject, + const SerdNode predicate) +{ + // Push the current context to the stack + void* const top = serd_stack_push(&writer->anon_stack, sizeof(WriteContext)); + *(WriteContext*)top = writer->context; + + // Update the current context + const WriteContext current = {type, graph, subject, predicate, 0U, 0U}; + writer->context = current; +} + +static void +pop_context(SerdWriter* writer) +{ + // Replace the current context with the top of the stack + free_context(&writer->context); + writer->context = + *(WriteContext*)(writer->anon_stack.buf + writer->anon_stack.size - + sizeof(WriteContext)); + + // Pop the top of the stack away + serd_stack_pop(&writer->anon_stack, sizeof(WriteContext)); +} + static size_t sink(const void* buf, size_t len, SerdWriter* writer) { @@ -451,7 +506,7 @@ write_newline(SerdWriter* writer) SerdStatus st = SERD_SUCCESS; TRY(st, esink("\n", 1, writer)); - for (unsigned i = 0; i < writer->indent; ++i) { + for (int i = 0; i < writer->indent; ++i) { TRY(st, esink("\t", 1, writer)); } @@ -461,52 +516,70 @@ write_newline(SerdWriter* writer) SERD_NODISCARD static SerdStatus write_sep(SerdWriter* writer, const Sep sep) { - SerdStatus st = SERD_SUCCESS; - const SepRule* rule = &rules[sep]; - if (rule->space_before) { - TRY(st, write_newline(writer)); + SerdStatus st = SERD_SUCCESS; + const SepRule* const rule = &rules[sep]; + + const bool pre_line = (rule->pre_line_after & (1U << writer->last_sep)); + const bool post_line = (rule->post_line_after & (1U << writer->last_sep)); + + // Adjust indent, but tolerate if it would become negative + if (rule->indent && (pre_line || post_line)) { + writer->indent = ((rule->indent >= 0 || writer->indent >= -rule->indent) + ? writer->indent + rule->indent + : 0); } - if (rule->str) { - TRY(st, esink(rule->str, rule->len, writer)); + // If this is the first comma, bump the increment for the following object + if (sep == SEP_END_O && !writer->context.comma_indented) { + ++writer->indent; + writer->context.comma_indented = true; } - if (rule->space_after_sep || - (writer->last_sep == SEP_NODE && rule->space_after_node)) { + // Write newline or space before separator if necessary + if (pre_line) { TRY(st, write_newline(writer)); - } else if (writer->last_sep && writer->last_sep != SEP_GRAPH_BEGIN && - rule->space_after_node) { + } else if (rule->pre_space_after & (1U << writer->last_sep)) { TRY(st, esink(" ", 1, writer)); } + // Write actual separator string + if (rule->sep) { + TRY(st, esink(&rule->sep, 1, writer)); + } + + // Write newline after separator if necessary + if (post_line) { + TRY(st, write_newline(writer)); + if (rule->post_line_after != ~(SepMask)0U) { + writer->last_sep = SEP_NEWLINE; + } + } + + // Reset context and write a blank line after ends of subjects + if (sep == SEP_END_S) { + writer->indent = writer->context.graph.type ? 1 : 0; + writer->context.predicates = false; + writer->context.comma_indented = false; + TRY(st, esink("\n", 1, writer)); + } + writer->last_sep = sep; return st; } -static SerdStatus -free_context(WriteContext* const ctx) -{ - serd_node_free(&ctx->graph); - serd_node_free(&ctx->subject); - serd_node_free(&ctx->predicate); - ctx->graph.type = SERD_NOTHING; - ctx->subject.type = SERD_NOTHING; - ctx->predicate.type = SERD_NOTHING; - return SERD_SUCCESS; -} - static void free_anon_stack(SerdWriter* writer) { while (!serd_stack_is_empty(&writer->anon_stack)) { - free_context(anon_stack_top(writer)); - serd_stack_pop(&writer->anon_stack, sizeof(WriteContext)); + pop_context(writer); } } static SerdStatus reset_context(SerdWriter* writer, const unsigned flags) { + free_anon_stack(writer); + if (flags & RESET_GRAPH) { writer->context.graph.type = SERD_NOTHING; } @@ -515,8 +588,11 @@ reset_context(SerdWriter* writer, const unsigned flags) writer->indent = 0; } + writer->context.type = CTX_NAMED; writer->context.subject.type = SERD_NOTHING; writer->context.predicate.type = SERD_NOTHING; + writer->context.predicates = false; + writer->context.comma_indented = false; return SERD_SUCCESS; } @@ -694,20 +770,11 @@ write_blank(SerdWriter* const writer, if (supports_abbrev(writer)) { if (is_inline_start(writer, field, flags)) { - ++writer->indent; return write_sep(writer, SEP_ANON_BEGIN); } - if (field == FIELD_SUBJECT && (flags & SERD_LIST_S_BEGIN)) { - assert(writer->list_depth == 0); - copy_node(&writer->list_subj, node); - ++writer->list_depth; - return write_sep(writer, SEP_LIST_BEGIN); - } - - if (field == FIELD_OBJECT && (flags & SERD_LIST_O_BEGIN)) { - ++writer->indent; - ++writer->list_depth; + if ((field == FIELD_SUBJECT && (flags & SERD_LIST_S_BEGIN)) || + (field == FIELD_OBJECT && (flags & SERD_LIST_O_BEGIN))) { return write_sep(writer, SEP_LIST_BEGIN); } @@ -758,7 +825,10 @@ write_node(SerdWriter* writer, break; } - writer->last_sep = SEP_NODE; + if (node->type != SERD_BLANK) { + writer->last_sep = SEP_NODE; + } + return st; } @@ -777,6 +847,8 @@ write_pred(SerdWriter* writer, SerdStatementFlags flags, const SerdNode* pred) TRY(st, write_sep(writer, SEP_P_O)); copy_node(&writer->context.predicate, pred); + writer->context.predicates = true; + writer->context.comma_indented = false; return st; } @@ -791,14 +863,14 @@ write_list_next(SerdWriter* writer, SerdStatus st = SERD_SUCCESS; if (!strcmp((const char*)object->buf, NS_RDF "nil")) { - deindent(writer); TRY(st, write_sep(writer, SEP_LIST_END)); return SERD_FAILURE; } if (!strcmp((const char*)predicate->buf, NS_RDF "first")) { - TRY(st, write_sep(writer, SEP_LIST_SEP)); TRY(st, write_node(writer, object, datatype, lang, FIELD_OBJECT, flags)); + } else { + TRY(st, write_sep(writer, SEP_LIST_SEP)); } return st; @@ -837,6 +909,7 @@ serd_writer_write_statement(SerdWriter* writer, return SERD_ERR_BAD_ARG; } + // Simple case: write a line of NTriples or NQuads if (writer->syntax == SERD_NTRIPLES || writer->syntax == SERD_NQUADS) { TRY(st, write_node(writer, subject, NULL, NULL, FIELD_SUBJECT, flags)); TRY(st, esink(" ", 1, writer)); @@ -851,6 +924,7 @@ serd_writer_write_statement(SerdWriter* writer, return SERD_SUCCESS; } + // Separate graphs if necessary if ((graph && !serd_node_equals(graph, &writer->context.graph)) || (!graph && writer->context.graph.type)) { TRY(st, terminate_context(writer)); @@ -858,69 +932,76 @@ serd_writer_write_statement(SerdWriter* writer, if (graph) { TRY(st, write_newline(writer)); TRY(st, write_node(writer, graph, datatype, lang, FIELD_GRAPH, flags)); - ++writer->indent; TRY(st, write_sep(writer, SEP_GRAPH_BEGIN)); copy_node(&writer->context.graph, graph); } } if ((flags & SERD_LIST_CONT)) { + // Continue a list + if (!strcmp((const char*)predicate->buf, NS_RDF "first") && + !strcmp((const char*)object->buf, NS_RDF "nil")) { + return esink("()", 2, writer); + } + TRY_FAILING( st, write_list_next(writer, flags, predicate, object, datatype, lang)); - if (st == SERD_FAILURE) { - // Reached end of list - if (--writer->list_depth == 0 && writer->list_subj.type) { - reset_context(writer, 0U); - serd_node_free(&writer->context.subject); - writer->context.subject = writer->list_subj; - writer->list_subj = SERD_NODE_NULL; - } + if (st == SERD_FAILURE) { // Reached end of list + 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)) { - ++writer->indent; - } - TRY(st, write_sep(writer, SEP_END_O)); - TRY(st, write_node(writer, object, datatype, lang, FIELD_OBJECT, flags)); - if (!(flags & SERD_ANON_O_BEGIN)) { - deindent(writer); - } + // Elide S P (write O) + + const Sep last = writer->last_sep; + const bool anon_o = flags & SERD_ANON_O_BEGIN; + const bool list_o = flags & SERD_LIST_O_BEGIN; + const bool open_o = anon_o || list_o; + const bool after_end = (last == SEP_ANON_END) || (last == SEP_LIST_END); + + TRY(st, + write_sep(writer, + after_end ? (open_o ? SEP_JOIN_O_AA : SEP_JOIN_O_AN) + : (open_o ? SEP_JOIN_O_NA : SEP_END_O))); + } else { - // Abbreviate S - Sep sep = writer->context.predicate.type ? SEP_END_P : SEP_S_P; - TRY(st, write_sep(writer, sep)); + // Elide S (write P and O) + + if (writer->context.comma_indented) { + --writer->indent; + writer->context.comma_indented = false; + } + + const bool first = !writer->context.predicate.type; + TRY(st, write_sep(writer, first ? SEP_S_P : SEP_END_P)); TRY(st, write_pred(writer, flags, predicate)); - TRY(st, write_node(writer, object, datatype, lang, FIELD_OBJECT, flags)); } + + TRY(st, write_node(writer, object, datatype, lang, FIELD_OBJECT, flags)); + } else { // No abbreviation - if (writer->context.subject.type) { - deindent(writer); - if (serd_stack_is_empty(&writer->anon_stack)) { + if (serd_stack_is_empty(&writer->anon_stack)) { + if (writer->context.subject.type) { TRY(st, write_sep(writer, SEP_END_S)); } - } - if (!(flags & SERD_ANON_CONT)) { if (writer->last_sep == SEP_END_S || writer->last_sep == SEP_END_DIRECT) { TRY(st, write_newline(writer)); } TRY(st, write_node(writer, subject, NULL, NULL, FIELD_SUBJECT, flags)); - if ((flags & SERD_ANON_S_BEGIN)) { + if ((flags & (SERD_ANON_S_BEGIN | SERD_LIST_S_BEGIN))) { TRY(st, write_sep(writer, SEP_ANON_S_P)); } else { - ++writer->indent; TRY(st, write_sep(writer, SEP_S_P)); } } else { - ++writer->indent; + TRY(st, write_sep(writer, SEP_ANON_S_P)); } reset_context(writer, 0U); @@ -933,23 +1014,26 @@ serd_writer_write_statement(SerdWriter* writer, TRY(st, write_node(writer, object, datatype, lang, FIELD_OBJECT, flags)); } - if (flags & (SERD_ANON_S_BEGIN | SERD_ANON_O_BEGIN)) { - WriteContext* ctx = - (WriteContext*)serd_stack_push(&writer->anon_stack, sizeof(WriteContext)); - *ctx = writer->context; - WriteContext new_context = { - serd_node_copy(graph), serd_node_copy(subject), SERD_NODE_NULL}; - if ((flags & SERD_ANON_S_BEGIN)) { - new_context.predicate = serd_node_copy(predicate); - } - writer->context = new_context; - } else { - copy_node(&writer->context.graph, graph); - copy_node(&writer->context.subject, subject); - copy_node(&writer->context.predicate, predicate); + if (flags & (SERD_ANON_S_BEGIN | SERD_LIST_S_BEGIN)) { + // Push context for anonymous or list subject + const bool is_list = (flags & SERD_LIST_S_BEGIN); + push_context(writer, + is_list ? CTX_LIST : CTX_BLANK, + serd_node_copy(graph), + serd_node_copy(subject), + is_list ? SERD_NODE_NULL : serd_node_copy(predicate)); } - return SERD_SUCCESS; + if (flags & (SERD_ANON_O_BEGIN | SERD_LIST_O_BEGIN)) { + // Push context for anonymous or list object if necessary + push_context(writer, + (flags & SERD_LIST_O_BEGIN) ? CTX_LIST : CTX_BLANK, + serd_node_copy(graph), + serd_node_copy(object), + SERD_NODE_NULL); + } + + return st; } SerdStatus @@ -961,19 +1045,17 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node) return SERD_SUCCESS; } - if (serd_stack_is_empty(&writer->anon_stack) || writer->indent == 0) { + if (serd_stack_is_empty(&writer->anon_stack)) { return w_err( writer, SERD_ERR_UNKNOWN, "unexpected end of anonymous node\n"); } - deindent(writer); + // Write the end separator ']' and pop the context TRY(st, write_sep(writer, SEP_ANON_END)); - free_context(&writer->context); - writer->context = *anon_stack_top(writer); - serd_stack_pop(&writer->anon_stack, sizeof(WriteContext)); - const bool is_subject = serd_node_equals(node, &writer->context.subject); - if (is_subject) { - copy_node(&writer->context.subject, node); + pop_context(writer); + + if (serd_node_equals(node, &writer->context.subject)) { + // Now-finished anonymous node is the new subject with no other context writer->context.predicate.type = SERD_NOTHING; } @@ -985,9 +1067,8 @@ serd_writer_finish(SerdWriter* writer) { const SerdStatus st0 = terminate_context(writer); const SerdStatus st1 = serd_byte_sink_flush(&writer->byte_sink); - free_context(&writer->context); - writer->indent = 0; - writer->context = WRITE_CONTEXT_NULL; + free_anon_stack(writer); + reset_context(writer, RESET_GRAPH | RESET_INDENT); return st0 ? st0 : st1; } @@ -1010,7 +1091,6 @@ serd_writer_new(SerdSyntax syntax, writer->base_uri = base_uri ? *base_uri : SERD_URI_NULL; writer->anon_stack = serd_stack_new(SERD_PAGE_SIZE); writer->context = context; - writer->list_subj = SERD_NODE_NULL; writer->byte_sink = serd_byte_sink_new( ssink, stream, (style & SERD_STYLE_BULK) ? SERD_PAGE_SIZE : 1); @@ -1111,6 +1191,7 @@ serd_writer_free(SerdWriter* writer) } serd_writer_finish(writer); + free_context(&writer->context); free_anon_stack(writer); serd_stack_free(&writer->anon_stack); free(writer->bprefix); diff --git a/test/extra/pretty/abbreviation.ttl b/test/extra/pretty/abbreviation.ttl index bf46f6d5..95f43c88 100644 --- a/test/extra/pretty/abbreviation.ttl +++ b/test/extra/pretty/abbreviation.ttl @@ -51,7 +51,8 @@ eg:s7 eg:listOfResources ( [ eg:value 7 - ] [ + ] + [ eg:value 8 ] ) . diff --git a/test/extra/pretty/anonymous-in-list-object.ttl b/test/extra/pretty/anonymous-in-list-object.ttl index 4f135e29..a6b202f4 100644 --- a/test/extra/pretty/anonymous-in-list-object.ttl +++ b/test/extra/pretty/anonymous-in-list-object.ttl @@ -4,7 +4,8 @@ eg:s eg:p ( [ a eg:Spy - ] [ + ] + [ a eg:Ninja ] ) . diff --git a/test/extra/pretty/graph-abbreviation.trig b/test/extra/pretty/graph-abbreviation.trig index 8ec75f6e..c6d5e32e 100644 --- a/test/extra/pretty/graph-abbreviation.trig +++ b/test/extra/pretty/graph-abbreviation.trig @@ -53,7 +53,8 @@ :lists ( [ rdf:value 7 - ] [ + ] + [ rdf:value 8 ] ) . diff --git a/test/extra/pretty/inline-blank-subject.ttl b/test/extra/pretty/inline-blank-subject.ttl index 8f5de39c..87ea8051 100644 --- a/test/extra/pretty/inline-blank-subject.ttl +++ b/test/extra/pretty/inline-blank-subject.ttl @@ -2,4 +2,5 @@ [ a eg:BlankSubject -] eg:isA eg:Blank . +] + eg:isA eg:Blank . diff --git a/test/extra/pretty/inline-blanks-and-lists.ttl b/test/extra/pretty/inline-blanks-and-lists.ttl index c57482d3..3fdfa4ee 100644 --- a/test/extra/pretty/inline-blanks-and-lists.ttl +++ b/test/extra/pretty/inline-blanks-and-lists.ttl @@ -46,7 +46,22 @@ eg:s2 [] , eg:o22 , () , - eg:o23 . + eg:o23 , + [] , + eg:o24 , + [ + a eg:Child + ] , + [] , + eg:o25 , + [ + a eg:FirstChild + ] , [ + a eg:SecondChild + ] , + () , + eg:o26 , + eg:o27 . eg:s3 a eg:Thing ; diff --git a/test/extra/pretty/inline-list-subject.ttl b/test/extra/pretty/inline-list-subject.ttl index a3f8ac18..7591f0e1 100644 --- a/test/extra/pretty/inline-list-subject.ttl +++ b/test/extra/pretty/inline-list-subject.ttl @@ -3,4 +3,5 @@ ( eg:item1 eg:item2 -) eg:isA eg:List . +) + eg:isA eg:List . diff --git a/test/extra/pretty/list-subject-with-extras.ttl b/test/extra/pretty/list-subject-with-extras.ttl new file mode 100644 index 00000000..dcfb8753 --- /dev/null +++ b/test/extra/pretty/list-subject-with-extras.ttl @@ -0,0 +1,9 @@ +@prefix eg: . + +( + "apple" + "banana" + "cherry" +) + eg:with eg:someProperties ; + a eg:ExampleList . diff --git a/test/extra/pretty/list-subject-with-list-extras.ttl b/test/extra/pretty/list-subject-with-list-extras.ttl new file mode 100644 index 00000000..0a0cd0e0 --- /dev/null +++ b/test/extra/pretty/list-subject-with-list-extras.ttl @@ -0,0 +1,12 @@ +@prefix eg: . + +( + "apple" + "banana" + "cherry" +) + eg:list ( + "asparagus" + "beet" + "carrot" + ) . diff --git a/test/extra/pretty/list-subject.ttl b/test/extra/pretty/list-subject.ttl index 927f56f3..eb1c7a2e 100644 --- a/test/extra/pretty/list-subject.ttl +++ b/test/extra/pretty/list-subject.ttl @@ -4,4 +4,5 @@ "apple" "banana" "cherry" -) a eg:ExampleList . +) + a eg:ExampleList . diff --git a/test/extra/pretty/manifest.ttl b/test/extra/pretty/manifest.ttl index 6921b741..6422c9e0 100644 --- a/test/extra/pretty/manifest.ttl +++ b/test/extra/pretty/manifest.ttl @@ -25,12 +25,15 @@ <#list-in-object> <#list-object> <#list-subject> + <#list-subject-with-extras> + <#list-subject-with-list-extras> <#local-name-escapes> <#long-string-escapes> <#long-string-quotes> <#many-objects> <#named-graph> <#nested-list-object> + <#nested-list-object-with-empty-lists> <#relative-uris> <#short-string-escapes> <#uri-escapes> @@ -145,6 +148,18 @@ mf:name "list-subject" ; mf:result . +<#list-subject-with-extras> + a rdft:TestTurtleEval ; + mf:action ; + mf:name "list-subject-with-extras" ; + mf:result . + +<#list-subject-with-list-extras> + a rdft:TestTurtleEval ; + mf:action ; + mf:name "list-subject-with-list-extras" ; + mf:result . + <#local-name-escapes> a rdft:TestTurtleEval ; mf:action ; @@ -181,6 +196,12 @@ mf:name "nested-list-object" ; mf:result . +<#nested-list-object-with-empty-lists> + a rdft:TestTurtleEval ; + mf:action ; + mf:name "nested-list-object-with-empty-lists" ; + mf:result . + <#nested-list-subject> a rdft:TestTurtleEval ; mf:action ; diff --git a/test/extra/pretty/nested-list-object-with-empty-lists.ttl b/test/extra/pretty/nested-list-object-with-empty-lists.ttl new file mode 100644 index 00000000..4760fd2b --- /dev/null +++ b/test/extra/pretty/nested-list-object-with-empty-lists.ttl @@ -0,0 +1,11 @@ +@prefix eg: . + +eg:s + eg:list ( + ( + () + ) + ( + () + ) + ) . diff --git a/test/extra/pretty/nested-list-object.ttl b/test/extra/pretty/nested-list-object.ttl index d2177ba3..c4b1898f 100644 --- a/test/extra/pretty/nested-list-object.ttl +++ b/test/extra/pretty/nested-list-object.ttl @@ -5,7 +5,8 @@ eg:s ( eg:l1e1 eg:l1e2 - ) ( + ) + ( eg:l2e1 eg:l2e2 ) diff --git a/test/extra/pretty/nested-list-subject.ttl b/test/extra/pretty/nested-list-subject.ttl index 128197c0..b32aa133 100644 --- a/test/extra/pretty/nested-list-subject.ttl +++ b/test/extra/pretty/nested-list-subject.ttl @@ -4,8 +4,10 @@ ( eg:l1e1 eg:l1e2 - ) ( + ) + ( eg:l2e1 eg:l2e2 ) -) a eg:ExampleList . +) + a eg:ExampleList . -- cgit v1.2.1