diff options
-rw-r--r-- | doc/conf.py.in | 1 | ||||
-rw-r--r-- | include/serd/caret.h | 77 | ||||
-rw-r--r-- | include/serd/error.h | 11 | ||||
-rw-r--r-- | include/serd/reader.h | 8 | ||||
-rw-r--r-- | include/serd/serd.h | 1 | ||||
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | src/byte_source.c | 45 | ||||
-rw-r--r-- | src/byte_source.h | 25 | ||||
-rw-r--r-- | src/caret.c | 69 | ||||
-rw-r--r-- | src/caret.h | 15 | ||||
-rw-r--r-- | src/reader.c | 31 | ||||
-rw-r--r-- | src/serdi.c | 12 | ||||
-rw-r--r-- | src/world.c | 18 | ||||
-rw-r--r-- | test/meson.build | 1 | ||||
-rw-r--r-- | test/test_caret.c | 49 | ||||
-rw-r--r-- | test/test_free_null.c | 2 | ||||
-rw-r--r-- | test/test_overflow.c | 2 | ||||
-rw-r--r-- | test/test_reader_writer.c | 5 |
18 files changed, 310 insertions, 64 deletions
diff --git a/doc/conf.py.in b/doc/conf.py.in index 5f0c026a..9b1cdc70 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -27,6 +27,7 @@ except ModuleNotFoundError: # Ignore everything opaque or external for nitpicky mode _opaque = [ "FILE", + "SerdCaretImpl", "SerdEnvImpl", "SerdNodeImpl", "SerdReaderImpl", diff --git a/include/serd/caret.h b/include/serd/caret.h new file mode 100644 index 00000000..2ed77412 --- /dev/null +++ b/include/serd/caret.h @@ -0,0 +1,77 @@ +// Copyright 2011-2022 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#ifndef SERD_CARET_H +#define SERD_CARET_H + +#include "serd/attributes.h" +#include "serd/node.h" + +#include <stdbool.h> + +SERD_BEGIN_DECLS + +/** + @defgroup serd_caret Caret + @ingroup serd_data + @{ +*/ + +/// The origin of a statement in a text document +typedef struct SerdCaretImpl SerdCaret; + +/** + Create a new caret. + + Note that, to minimise model overhead, the caret does not own the document + node, so `document` must have a longer lifetime than the caret for it to be + valid. That is, serd_caret_document() will return exactly the pointer + `document`, not a copy. + + @param document The document or the caret refers to (usually a file URI) + @param line The line number in the document (1-based) + @param column The column number in the document (1-based) + @return A new caret that must be freed with serd_caret_free() +*/ +SERD_API SerdCaret* SERD_ALLOCATED +serd_caret_new(const SerdNode* SERD_NONNULL document, + unsigned line, + unsigned column); + +/// Return a copy of `caret` +SERD_API SerdCaret* SERD_ALLOCATED +serd_caret_copy(const SerdCaret* SERD_NULLABLE caret); + +/// Free `caret` +SERD_API void +serd_caret_free(SerdCaret* SERD_NULLABLE caret); + +/// Return true iff `lhs` is equal to `rhs` +SERD_PURE_API bool +serd_caret_equals(const SerdCaret* SERD_NULLABLE lhs, + const SerdCaret* SERD_NULLABLE rhs); + +/** + Return the document URI or name. + + This is typically a file URI, but may be a descriptive string node for + statements that originate from streams. +*/ +SERD_PURE_API const SerdNode* SERD_NONNULL +serd_caret_document(const SerdCaret* SERD_NONNULL caret); + +/// Return the one-relative line number in the document +SERD_PURE_API unsigned +serd_caret_line(const SerdCaret* SERD_NONNULL caret); + +/// Return the zero-relative column number in the line +SERD_PURE_API unsigned +serd_caret_column(const SerdCaret* SERD_NONNULL caret); + +/** + @} +*/ + +SERD_END_DECLS + +#endif // SERD_CARET_H diff --git a/include/serd/error.h b/include/serd/error.h index 7be1d372..7495ce3b 100644 --- a/include/serd/error.h +++ b/include/serd/error.h @@ -5,6 +5,7 @@ #define SERD_ERROR_H #include "serd/attributes.h" +#include "serd/caret.h" #include "serd/status.h" #include <stdarg.h> @@ -19,12 +20,10 @@ SERD_BEGIN_DECLS /// An error description typedef struct { - SerdStatus status; ///< Error code - const char* SERD_NULLABLE filename; ///< File with error - unsigned line; ///< Line in file with error or 0 - unsigned col; ///< Column in file with error - const char* SERD_NONNULL fmt; ///< Printf-style format string - va_list* SERD_NONNULL args; ///< Arguments for fmt + SerdStatus status; ///< Error code + const SerdCaret* SERD_NULLABLE caret; ///< File origin of error + const char* SERD_NONNULL fmt; ///< Printf-style format string + va_list* SERD_NONNULL args; ///< Arguments for fmt } SerdError; /** diff --git a/include/serd/reader.h b/include/serd/reader.h index aedc5cb3..cc19a9b8 100644 --- a/include/serd/reader.h +++ b/include/serd/reader.h @@ -5,6 +5,7 @@ #define SERD_READER_H #include "serd/attributes.h" +#include "serd/node.h" #include "serd/sink.h" #include "serd/status.h" #include "serd/stream.h" @@ -73,13 +74,14 @@ serd_reader_start_stream(SerdReader* SERD_NONNULL reader, SerdReadFunc SERD_NONNULL read_func, SerdStreamErrorFunc SERD_NONNULL error_func, void* SERD_NONNULL stream, - const char* SERD_NULLABLE name, + const SerdNode* SERD_NULLABLE name, size_t page_size); /// Prepare to read from a string SERD_API SerdStatus -serd_reader_start_string(SerdReader* SERD_NONNULL reader, - const char* SERD_NONNULL utf8); +serd_reader_start_string(SerdReader* SERD_NONNULL reader, + const char* SERD_NONNULL utf8, + const SerdNode* SERD_NULLABLE name); /** Read a single "chunk" of data during an incremental read. diff --git a/include/serd/serd.h b/include/serd/serd.h index eb33d329..8de151c6 100644 --- a/include/serd/serd.h +++ b/include/serd/serd.h @@ -57,6 +57,7 @@ @{ */ +#include "serd/caret.h" #include "serd/node.h" #include "serd/statement.h" #include "serd/uri.h" diff --git a/meson.build b/meson.build index fa183942..8b4652b1 100644 --- a/meson.build +++ b/meson.build @@ -121,6 +121,7 @@ include_dirs = include_directories('include') c_headers = files( 'include/serd/attributes.h', 'include/serd/buffer.h', + 'include/serd/caret.h', 'include/serd/env.h', 'include/serd/error.h', 'include/serd/memory.h', @@ -143,6 +144,7 @@ c_headers = files( sources = files( 'src/byte_source.c', + 'src/caret.c', 'src/env.c', 'src/n3.c', 'src/node.c', diff --git a/src/byte_source.c b/src/byte_source.c index d6510eb9..8e44d891 100644 --- a/src/byte_source.c +++ b/src/byte_source.c @@ -5,6 +5,9 @@ #include "system.h" +#include "serd/node.h" +#include "serd/string_view.h" + #include <stdbool.h> #include <stdint.h> #include <string.h> @@ -37,20 +40,21 @@ serd_byte_source_open_source(SerdByteSource* const source, const SerdStreamErrorFunc error_func, const SerdStreamCloseFunc close_func, void* const stream, - const char* const name, + const SerdNode* const name, const size_t page_size) { - const Cursor cur = {name, 1, 1}; - memset(source, '\0', sizeof(*source)); - source->read_func = read_func; - source->error_func = error_func; - source->close_func = close_func; - source->stream = stream; - source->page_size = page_size; - source->buf_size = page_size; - source->cur = cur; - source->from_stream = true; + source->read_func = read_func; + source->error_func = error_func; + source->close_func = close_func; + source->stream = stream; + source->page_size = page_size; + source->buf_size = page_size; + source->name = serd_node_copy(name); + source->caret.document = source->name; + source->caret.line = 1U; + source->caret.col = 1U; + source->from_stream = true; if (page_size > 1) { source->file_buf = (uint8_t*)serd_allocate_buffer(page_size); @@ -82,14 +86,20 @@ serd_byte_source_prepare(SerdByteSource* const source) SerdStatus serd_byte_source_open_string(SerdByteSource* const source, - const char* const utf8) + const char* const utf8, + const SerdNode* const name) { - const Cursor cur = {"(string)", 1, 1}; - memset(source, '\0', sizeof(*source)); - source->page_size = 1; - source->cur = cur; - source->read_buf = (const uint8_t*)utf8; + + source->name = + name ? serd_node_copy(name) : serd_new_string(serd_string("string")); + + source->page_size = 1U; + source->read_buf = (const uint8_t*)utf8; + source->caret.document = source->name; + source->caret.line = 1U; + source->caret.col = 1U; + return SERD_SUCCESS; } @@ -105,6 +115,7 @@ serd_byte_source_close(SerdByteSource* const source) serd_free_aligned(source->file_buf); } + serd_node_free(source->name); memset(source, '\0', sizeof(*source)); return st; } diff --git a/src/byte_source.h b/src/byte_source.h index e2697bd2..cc6bc840 100644 --- a/src/byte_source.h +++ b/src/byte_source.h @@ -4,7 +4,11 @@ #ifndef SERD_SRC_BYTE_SOURCE_H #define SERD_SRC_BYTE_SOURCE_H +#include "caret.h" // IWYU pragma: keep + #include "serd/attributes.h" +#include "serd/caret.h" +#include "serd/node.h" #include "serd/status.h" #include "serd/stream.h" @@ -16,19 +20,14 @@ typedef int (*SerdStreamCloseFunc)(void*); typedef struct { - const char* filename; - unsigned line; - unsigned col; -} Cursor; - -typedef struct { SerdReadFunc read_func; ///< Read function (e.g. fread) SerdStreamErrorFunc error_func; ///< Error function (e.g. ferror) SerdStreamCloseFunc close_func; ///< Function for closing stream void* stream; ///< Stream (e.g. FILE) size_t page_size; ///< Number of bytes to read at a time size_t buf_size; ///< Number of bytes in file_buf - Cursor cur; ///< Cursor for error reporting + SerdNode* name; ///< Name of stream (referenced by cur) + SerdCaret caret; ///< Caret for error reporting uint8_t* file_buf; ///< Buffer iff reading pages from a file const uint8_t* read_buf; ///< Pointer to file_buf or read_byte size_t read_head; ///< Offset into read_buf @@ -39,7 +38,9 @@ typedef struct { } SerdByteSource; SerdStatus -serd_byte_source_open_string(SerdByteSource* source, const char* utf8); +serd_byte_source_open_string(SerdByteSource* source, + const char* utf8, + const SerdNode* name); SerdStatus serd_byte_source_open_source(SerdByteSource* source, @@ -47,7 +48,7 @@ serd_byte_source_open_source(SerdByteSource* source, SerdStreamErrorFunc error_func, SerdStreamCloseFunc close_func, void* stream, - const char* name, + const SerdNode* name, size_t page_size); SerdStatus @@ -73,11 +74,11 @@ serd_byte_source_advance(SerdByteSource* source) switch (serd_byte_source_peek(source)) { case '\n': - ++source->cur.line; - source->cur.col = 0; + ++source->caret.line; + source->caret.col = 0; break; default: - ++source->cur.col; + ++source->caret.col; } const bool was_eof = source->eof; diff --git a/src/caret.c b/src/caret.c new file mode 100644 index 00000000..1b0de444 --- /dev/null +++ b/src/caret.c @@ -0,0 +1,69 @@ +// Copyright 2018-2020 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#include "caret.h" + +#include "serd/caret.h" + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +SerdCaret* +serd_caret_new(const SerdNode* const document, + const unsigned line, + const unsigned column) +{ + SerdCaret* caret = (SerdCaret*)malloc(sizeof(SerdCaret)); + + if (caret) { + caret->document = document; + caret->line = line; + caret->col = column; + } + + return caret; +} + +SerdCaret* +serd_caret_copy(const SerdCaret* const caret) +{ + if (!caret) { + return NULL; + } + + SerdCaret* copy = (SerdCaret*)malloc(sizeof(SerdCaret)); + memcpy(copy, caret, sizeof(SerdCaret)); + return copy; +} + +void +serd_caret_free(SerdCaret* const caret) +{ + free(caret); +} + +bool +serd_caret_equals(const SerdCaret* const l, const SerdCaret* const r) +{ + return (l == r || (l && r && serd_node_equals(l->document, r->document) && + l->line == r->line && l->col == r->col)); +} + +const SerdNode* +serd_caret_document(const SerdCaret* const caret) +{ + return caret->document; +} + +unsigned +serd_caret_line(const SerdCaret* const caret) +{ + return caret->line; +} + +unsigned +serd_caret_column(const SerdCaret* const caret) +{ + return caret->col; +} diff --git a/src/caret.h b/src/caret.h new file mode 100644 index 00000000..85b4b4b1 --- /dev/null +++ b/src/caret.h @@ -0,0 +1,15 @@ +// Copyright 2018-2020 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#ifndef SERD_SRC_CARET_H +#define SERD_SRC_CARET_H + +#include "serd/node.h" + +struct SerdCaretImpl { + const SerdNode* document; + unsigned line; + unsigned col; +}; + +#endif // SERD_SRC_CARET_H diff --git a/src/reader.c b/src/reader.c index 2ef3b66a..a9c483c0 100644 --- a/src/reader.c +++ b/src/reader.c @@ -11,6 +11,7 @@ #include "world.h" #include "serd/stream.h" +#include "serd/string_view.h" #include "serd/uri.h" #include <errno.h> @@ -27,8 +28,7 @@ r_err(SerdReader* const reader, const SerdStatus st, const char* const fmt, ...) { va_list args; // NOLINT(cppcoreguidelines-init-variables) va_start(args, fmt); - const Cursor* const cur = &reader->source.cur; - const SerdError e = {st, cur->filename, cur->line, cur->col, fmt, &args}; + const SerdError e = {st, &reader->source.caret, fmt, &args}; serd_world_error(reader->world, &e); va_end(args); return st; @@ -230,7 +230,7 @@ serd_reader_start_stream(SerdReader* const reader, const SerdReadFunc read_func, const SerdStreamErrorFunc error_func, void* const stream, - const char* const name, + const SerdNode* const name, const size_t page_size) { return serd_byte_source_open_source( @@ -251,20 +251,25 @@ serd_reader_start_file(SerdReader* reader, const char* uri, bool bulk) return SERD_BAD_STREAM; } - return serd_byte_source_open_source(&reader->source, - bulk ? (SerdReadFunc)fread - : serd_file_read_byte, - (SerdStreamErrorFunc)ferror, - (SerdStreamCloseFunc)fclose, - fd, - uri, - bulk ? SERD_PAGE_SIZE : 1); + SerdNode* const name = serd_new_uri(serd_string(uri)); + const SerdStatus st = serd_byte_source_open_source( + &reader->source, + bulk ? (SerdReadFunc)fread : serd_file_read_byte, + (SerdStreamErrorFunc)ferror, + (SerdStreamCloseFunc)fclose, + fd, + name, + bulk ? SERD_PAGE_SIZE : 1U); + serd_node_free(name); + return st; } SerdStatus -serd_reader_start_string(SerdReader* const reader, const char* const utf8) +serd_reader_start_string(SerdReader* const reader, + const char* const utf8, + const SerdNode* const name) { - return serd_byte_source_open_string(&reader->source, utf8); + return serd_byte_source_open_string(&reader->source, utf8, name); } static SerdStatus diff --git a/src/serdi.c b/src/serdi.c index 4649f57a..0932348c 100644 --- a/src/serdi.c +++ b/src/serdi.c @@ -298,15 +298,18 @@ main(int argc, char** argv) serd_writer_chop_blank_prefix(writer, chop_prefix); serd_reader_add_blank_prefix(reader, add_prefix); - SerdStatus st = SERD_SUCCESS; + SerdStatus st = SERD_SUCCESS; + SerdNode* input_name = NULL; if (from_string) { - st = serd_reader_start_string(reader, input); + input_name = serd_new_string(serd_string("string")); + st = serd_reader_start_string(reader, input, input_name); } else if (from_stdin) { - st = serd_reader_start_stream(reader, + input_name = serd_new_string(serd_string("stdin")); + st = serd_reader_start_stream(reader, serd_file_read_byte, (SerdStreamErrorFunc)ferror, stdin, - "(stdin)", + input_name, 1); } else { st = serd_reader_start_file(reader, input, bulk_read); @@ -320,6 +323,7 @@ main(int argc, char** argv) serd_reader_free(reader); serd_writer_finish(writer); serd_writer_free(writer); + serd_node_free(input_name); serd_env_free(env); serd_node_free(base); serd_world_free(world); diff --git a/src/world.c b/src/world.c index ececd646..21cdffab 100644 --- a/src/world.c +++ b/src/world.c @@ -3,10 +3,13 @@ #include "world.h" +#include "caret.h" #include "node.h" #include "serd_config.h" #include "system.h" +#include "serd/node.h" + #include "serd/string_view.h" #if defined(USE_POSIX_FADVISE) @@ -45,10 +48,13 @@ serd_world_error(const SerdWorld* const world, const SerdError* const e) if (world->error_func) { world->error_func(world->error_handle, e); } else { - if (e->filename) { - fprintf(stderr, "error: %s:%u:%u: ", e->filename, e->line, e->col); - } else { - fprintf(stderr, "error: "); + fprintf(stderr, "error: "); + if (e->caret) { + fprintf(stderr, + "%s:%u:%u: ", + serd_node_string(e->caret->document), + e->caret->line, + e->caret->col); } vfprintf(stderr, e->fmt, *e->args); } @@ -64,7 +70,7 @@ serd_world_verrorf(const SerdWorld* const world, va_list args_copy; va_copy(args_copy, args); - const SerdError e = {st, NULL, 0, 0, fmt, &args_copy}; + const SerdError e = {st, NULL, fmt, &args_copy}; serd_world_error(world, &e); va_end(args_copy); return st; @@ -78,7 +84,7 @@ serd_world_errorf(const SerdWorld* const world, { va_list args; // NOLINT(cppcoreguidelines-init-variables) va_start(args, fmt); - const SerdError e = {st, NULL, 0, 0, fmt, &args}; + const SerdError e = {st, NULL, fmt, &args}; serd_world_error(world, &e); va_end(args); return st; diff --git a/test/meson.build b/test/meson.build index efcb00f0..c3f29637 100644 --- a/test/meson.build +++ b/test/meson.build @@ -119,6 +119,7 @@ subdir('headers') ############## unit_tests = [ + 'caret', 'env', 'free_null', 'node', diff --git a/test/test_caret.c b/test/test_caret.c new file mode 100644 index 00000000..a239c242 --- /dev/null +++ b/test/test_caret.c @@ -0,0 +1,49 @@ +// Copyright 2019-2020 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#undef NDEBUG + +#include "serd/caret.h" +#include "serd/node.h" +#include "serd/string_view.h" + +#include <assert.h> +#include <stddef.h> + +int +main(void) +{ + SerdNode* const node = serd_new_string(serd_string("node")); + SerdCaret* const caret = serd_caret_new(node, 46, 2); + + assert(serd_caret_equals(caret, caret)); + assert(serd_caret_document(caret) == node); + assert(serd_caret_line(caret) == 46); + assert(serd_caret_column(caret) == 2); + + SerdCaret* const copy = serd_caret_copy(caret); + + assert(serd_caret_equals(caret, copy)); + assert(!serd_caret_copy(NULL)); + + SerdNode* const other_node = serd_new_string(serd_string("other")); + SerdCaret* const other_file = serd_caret_new(other_node, 46, 2); + SerdCaret* const other_line = serd_caret_new(node, 47, 2); + SerdCaret* const other_col = serd_caret_new(node, 46, 3); + + assert(!serd_caret_equals(caret, other_file)); + assert(!serd_caret_equals(caret, other_line)); + assert(!serd_caret_equals(caret, other_col)); + assert(!serd_caret_equals(caret, NULL)); + assert(!serd_caret_equals(NULL, caret)); + + serd_caret_free(other_col); + serd_caret_free(other_line); + serd_caret_free(other_file); + serd_node_free(other_node); + serd_caret_free(copy); + serd_caret_free(caret); + serd_node_free(node); + + return 0; +} diff --git a/test/test_free_null.c b/test/test_free_null.c index 72783328..87dfa7db 100644 --- a/test/test_free_null.c +++ b/test/test_free_null.c @@ -3,6 +3,7 @@ #undef NDEBUG +#include "serd/caret.h" #include "serd/env.h" #include "serd/memory.h" #include "serd/node.h" @@ -23,6 +24,7 @@ main(void) serd_sink_free(NULL); serd_reader_free(NULL); serd_writer_free(NULL); + serd_caret_free(NULL); return 0; } diff --git a/test/test_overflow.c b/test/test_overflow.c index a304ae1c..f4c5a863 100644 --- a/test/test_overflow.c +++ b/test/test_overflow.c @@ -23,7 +23,7 @@ test_size(SerdWorld* const world, return SERD_BAD_STACK; } - serd_reader_start_string(reader, str); + serd_reader_start_string(reader, str, NULL); const SerdStatus st = serd_reader_read_document(reader); serd_reader_free(reader); serd_sink_free(sink); diff --git a/test/test_reader_writer.c b/test/test_reader_writer.c index 062f65fc..39bde443 100644 --- a/test/test_reader_writer.c +++ b/test/test_reader_writer.c @@ -370,7 +370,8 @@ test_read_string(void) assert( !serd_reader_start_string(reader, "<http://example.org/s> <http://example.org/p> " - "<http://example.org/o> .")); + "<http://example.org/o> .", + NULL)); assert(!serd_reader_read_document(reader)); assert(rt->n_base == 0); @@ -433,7 +434,7 @@ test_write_errors(void) const SerdSink* const sink = serd_writer_sink(writer); SerdReader* const reader = serd_reader_new(world, SERD_TRIG, sink, 4096U); - SerdStatus st = serd_reader_start_string(reader, doc_string); + SerdStatus st = serd_reader_start_string(reader, doc_string, NULL); assert(!st); st = serd_reader_read_document(reader); assert(st == SERD_BAD_WRITE); |