From 7c6c3159d1804f4855d9a4e0cd52486f61fcbab6 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 13 Jan 2021 12:08:30 +0100 Subject: Add SerdCursor --- include/serd/serd.h | 81 +++++++++++++++++++++++++++++++++++++++++------ meson.build | 1 + src/byte_source.c | 25 ++++++++++----- src/byte_source.h | 17 +++++----- src/cursor.c | 75 +++++++++++++++++++++++++++++++++++++++++++ src/cursor.h | 28 ++++++++++++++++ src/reader.c | 30 ++++++++++-------- src/serdi.c | 12 ++++--- src/world.c | 14 +++++--- test/meson.build | 1 + test/test_cursor.c | 59 ++++++++++++++++++++++++++++++++++ test/test_free_null.c | 1 + test/test_read_chunk.c | 3 +- test/test_reader_writer.c | 3 +- 14 files changed, 300 insertions(+), 50 deletions(-) create mode 100644 src/cursor.c create mode 100644 src/cursor.h create mode 100644 test/test_cursor.c diff --git a/include/serd/serd.h b/include/serd/serd.h index c7dc4a17..b5c7c4f0 100644 --- a/include/serd/serd.h +++ b/include/serd/serd.h @@ -84,6 +84,9 @@ extern "C" { /// Global library state typedef struct SerdWorldImpl SerdWorld; +/// The origin of a statement in a document +typedef struct SerdCursorImpl SerdCursor; + /// Lexical environment for relative URIs or CURIEs (base URI and namespaces) typedef struct SerdEnvImpl SerdEnv; @@ -246,12 +249,10 @@ typedef struct { /// 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 SerdCursor* SERD_NULLABLE cursor; ///< Origin of error + const char* SERD_NONNULL fmt; ///< Printf-style format string + va_list* SERD_NONNULL args; ///< Arguments for fmt } SerdError; /** @@ -1110,14 +1111,15 @@ 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 @@ -1262,6 +1264,67 @@ SERD_API SerdStatus serd_writer_finish(SerdWriter* SERD_NONNULL writer); +/** + @} + @defgroup serd_cursor Cursor + @{ +*/ + +/** + Create a new cursor + + Note that, to minimise model overhead, the cursor does not own the name + node, so `name` must have a longer lifetime than the cursor for it to be + valid. That is, serd_cursor_name() will return exactly the pointer + `name`, not a copy. For cursors from models, this is the lifetime of the + model. For user-created cursors, the simplest way to handle this is to use + `SerdNodes`. + + @param name The name of the document or stream (usually a file URI) + @param line The line number in the document (1-based) + @param col The column number in the document (1-based) + @return A new cursor that must be freed with serd_cursor_free() +*/ +SERD_API +SerdCursor* SERD_ALLOCATED +serd_cursor_new(const SerdNode* SERD_NONNULL name, unsigned line, unsigned col); + +/// Return a copy of `cursor` +SERD_API +SerdCursor* SERD_ALLOCATED +serd_cursor_copy(const SerdCursor* SERD_NULLABLE cursor); + +/// Free `cursor` +SERD_API +void +serd_cursor_free(SerdCursor* SERD_NULLABLE cursor); + +/// Return true iff `lhs` is equal to `rhs` +SERD_PURE_API +bool +serd_cursor_equals(const SerdCursor* SERD_NULLABLE lhs, + const SerdCursor* SERD_NULLABLE rhs); + +/** + Return the document 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_cursor_name(const SerdCursor* SERD_NONNULL cursor); + +/// Return the one-relative line number in the document +SERD_PURE_API +unsigned +serd_cursor_line(const SerdCursor* SERD_NONNULL cursor); + +/// Return the zero-relative column number in the line +SERD_PURE_API +unsigned +serd_cursor_column(const SerdCursor* SERD_NONNULL cursor); + /** @} @} diff --git a/meson.build b/meson.build index 6b7818ff..5bfafbd5 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,7 @@ c_header = files('include/serd/serd.h') sources = [ 'src/base64.c', 'src/byte_source.c', + 'src/cursor.c', 'src/env.c', 'src/n3.c', 'src/node.c', diff --git a/src/byte_source.c b/src/byte_source.c index 2f09d634..6ba5a718 100644 --- a/src/byte_source.c +++ b/src/byte_source.c @@ -52,11 +52,9 @@ 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) { - const Cursor cur = {name, 1, 1}; - memset(source, '\0', sizeof(*source)); source->read_func = read_func; source->error_func = error_func; @@ -64,9 +62,12 @@ serd_byte_source_open_source(SerdByteSource* source, source->stream = stream; source->page_size = page_size; source->buf_size = page_size; - source->cur = cur; + source->name = serd_node_copy(name); source->from_stream = true; + const SerdCursor cur = {source->name, 1, 1}; + source->cur = cur; + if (page_size > 1) { source->file_buf = (uint8_t*)serd_allocate_buffer(page_size); source->read_buf = source->file_buf; @@ -92,13 +93,20 @@ serd_byte_source_prepare(SerdByteSource* source) } SerdStatus -serd_byte_source_open_string(SerdByteSource* source, const char* utf8) +serd_byte_source_open_string(SerdByteSource* source, + const char* utf8, + const SerdNode* name) { - const Cursor cur = {"(string)", 1, 1}; - memset(source, '\0', sizeof(*source)); - source->cur = cur; + + source->name = + name ? serd_node_copy(name) : serd_new_string(SERD_STATIC_STRING("string")); + source->read_buf = (const uint8_t*)utf8; + + const SerdCursor cur = {source->name, 1, 1}; + source->cur = cur; + return SERD_SUCCESS; } @@ -114,6 +122,7 @@ serd_byte_source_close(SerdByteSource* 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 d2c19de3..3503a4f5 100644 --- a/src/byte_source.h +++ b/src/byte_source.h @@ -17,6 +17,8 @@ #ifndef SERD_BYTE_SOURCE_H #define SERD_BYTE_SOURCE_H +#include "cursor.h" + #include "serd/serd.h" #include @@ -26,12 +28,6 @@ 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) @@ -39,7 +35,8 @@ typedef struct { 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) + SerdCursor cur; ///< Cursor 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 @@ -50,7 +47,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, @@ -58,7 +57,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 diff --git a/src/cursor.c b/src/cursor.c new file mode 100644 index 00000000..1c8dd35a --- /dev/null +++ b/src/cursor.c @@ -0,0 +1,75 @@ +/* + Copyright 2018-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "cursor.h" + +#include +#include +#include + +SerdCursor* +serd_cursor_new(const SerdNode* name, unsigned line, unsigned col) +{ + SerdCursor* cursor = (SerdCursor*)malloc(sizeof(SerdCursor)); + + cursor->file = name; + cursor->line = line; + cursor->col = col; + return cursor; +} + +SerdCursor* +serd_cursor_copy(const SerdCursor* cursor) +{ + if (!cursor) { + return NULL; + } + + SerdCursor* copy = (SerdCursor*)malloc(sizeof(SerdCursor)); + memcpy(copy, cursor, sizeof(SerdCursor)); + return copy; +} + +void +serd_cursor_free(SerdCursor* cursor) +{ + free(cursor); +} + +bool +serd_cursor_equals(const SerdCursor* l, const SerdCursor* r) +{ + return (l == r || (l && r && serd_node_equals(l->file, r->file) && + l->line == r->line && l->col == r->col)); +} + +const SerdNode* +serd_cursor_name(const SerdCursor* cursor) +{ + return cursor->file; +} + +unsigned +serd_cursor_line(const SerdCursor* cursor) +{ + return cursor->line; +} + +unsigned +serd_cursor_column(const SerdCursor* cursor) +{ + return cursor->col; +} diff --git a/src/cursor.h b/src/cursor.h new file mode 100644 index 00000000..ac65ae2c --- /dev/null +++ b/src/cursor.h @@ -0,0 +1,28 @@ +/* + Copyright 2018-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef SERD_CURSOR_H +#define SERD_CURSOR_H + +#include "serd/serd.h" + +struct SerdCursorImpl { + const SerdNode* file; + unsigned line; + unsigned col; +}; + +#endif // SERD_CURSOR_H diff --git a/src/reader.c b/src/reader.c index 76b28295..f6c5e056 100644 --- a/src/reader.c +++ b/src/reader.c @@ -35,8 +35,7 @@ r_err(SerdReader* reader, SerdStatus st, const char* fmt, ...) { va_list args; 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.cur, fmt, &args}; serd_world_error(reader->world, &e); va_end(args); return st; @@ -231,7 +230,7 @@ serd_reader_start_stream(SerdReader* reader, SerdReadFunc read_func, SerdStreamErrorFunc error_func, void* stream, - const char* name, + const SerdNode* name, size_t page_size) { return serd_byte_source_open_source( @@ -252,20 +251,25 @@ serd_reader_start_file(SerdReader* reader, const char* uri, bool bulk) return SERD_ERR_UNKNOWN; } - 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_MEASURE_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* reader, const char* utf8) +serd_reader_start_string(SerdReader* reader, + const char* utf8, + const SerdNode* 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 3f0185ec..eec58bd5 100644 --- a/src/serdi.c +++ b/src/serdi.c @@ -268,15 +268,18 @@ main(int argc, char** argv) serd_reader_add_blank_prefix(reader, add_prefix); serd_node_free(root); - 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_STATIC_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_STATIC_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); @@ -290,6 +293,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 9503c578..1b66f025 100644 --- a/src/world.c +++ b/src/world.c @@ -18,6 +18,7 @@ #include "world.h" +#include "cursor.h" #include "node.h" #include "serd_config.h" @@ -57,10 +58,13 @@ serd_world_error(const SerdWorld* world, const SerdError* 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->cursor) { + fprintf(stderr, + "%s:%u:%u: ", + serd_node_string(e->cursor->file), + e->cursor->line, + e->cursor->col); } vfprintf(stderr, e->fmt, *e->args); } @@ -72,7 +76,7 @@ serd_world_errorf(const SerdWorld* world, SerdStatus st, const char* fmt, ...) { va_list args; 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 e45af26c..82a307d2 100644 --- a/test/meson.build +++ b/test/meson.build @@ -3,6 +3,7 @@ run_test_suite = find_program('run_test_suite.py') wrapper = meson.get_cross_property('wrapper', '') unit_tests = [ + 'cursor', 'env', 'free_null', 'node', diff --git a/test/test_cursor.c b/test/test_cursor.c new file mode 100644 index 00000000..3a8ca975 --- /dev/null +++ b/test/test_cursor.c @@ -0,0 +1,59 @@ +/* + Copyright 2019-2020 David Robillard + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "serd/serd.h" + +#include +#include + +int +main(void) +{ + SerdNode* const node = serd_new_string(SERD_STATIC_STRING("node")); + SerdCursor* const cursor = serd_cursor_new(node, 46, 2); + + assert(serd_cursor_name(cursor) == node); + assert(serd_cursor_line(cursor) == 46); + assert(serd_cursor_column(cursor) == 2); + + SerdCursor* const copy = serd_cursor_copy(cursor); + + assert(serd_cursor_equals(cursor, copy)); + assert(!serd_cursor_copy(NULL)); + + SerdNode* const other_node = serd_new_string(SERD_STATIC_STRING("other")); + SerdCursor* const other_file = serd_cursor_new(other_node, 46, 2); + SerdCursor* const other_line = serd_cursor_new(node, 47, 2); + SerdCursor* const other_col = serd_cursor_new(node, 46, 3); + + assert(!serd_cursor_equals(cursor, other_file)); + assert(!serd_cursor_equals(cursor, other_line)); + assert(!serd_cursor_equals(cursor, other_col)); + assert(!serd_cursor_equals(cursor, NULL)); + assert(!serd_cursor_equals(NULL, cursor)); + + serd_cursor_free(other_col); + serd_cursor_free(other_line); + serd_cursor_free(other_file); + serd_node_free(other_node); + serd_cursor_free(copy); + serd_cursor_free(cursor); + serd_node_free(node); + + return 0; +} diff --git a/test/test_free_null.c b/test/test_free_null.c index 3977507c..05be8e4a 100644 --- a/test/test_free_null.c +++ b/test/test_free_null.c @@ -30,6 +30,7 @@ main(void) serd_sink_free(NULL); serd_reader_free(NULL); serd_writer_free(NULL); + serd_cursor_free(NULL); return 0; } diff --git a/test/test_read_chunk.c b/test/test_read_chunk.c index e15d3c50..8be4ad63 100644 --- a/test/test_read_chunk.c +++ b/test/test_read_chunk.c @@ -98,7 +98,8 @@ main(void) "eg:s2 eg:p1 eg:o1 ;\n" " eg:p2 eg:o2 .\n" "eg:s3 eg:p1 eg:o1 .\n" - "eg:s4 eg:p1 [ eg:p3 eg:o1 ] .\n")); + "eg:s4 eg:p1 [ eg:p3 eg:o1 ] .\n", + NULL)); assert(!serd_reader_read_chunk(reader) && n_prefix == 1); assert(!serd_reader_read_chunk(reader) && n_base == 1); diff --git a/test/test_reader_writer.c b/test/test_reader_writer.c index 8742289a..788b2d63 100644 --- a/test/test_reader_writer.c +++ b/test/test_reader_writer.c @@ -168,7 +168,8 @@ test_read_string(void) assert( !serd_reader_start_string(reader, " " - " .")); + " .", + NULL)); assert(!serd_reader_read_document(reader)); assert(n_statements == 1); -- cgit v1.2.1