aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/serd/serd.h81
-rw-r--r--meson.build1
-rw-r--r--src/byte_source.c25
-rw-r--r--src/byte_source.h17
-rw-r--r--src/cursor.c75
-rw-r--r--src/cursor.h28
-rw-r--r--src/reader.c30
-rw-r--r--src/serdi.c12
-rw-r--r--src/world.c14
-rw-r--r--test/meson.build1
-rw-r--r--test/test_cursor.c59
-rw-r--r--test/test_free_null.c1
-rw-r--r--test/test_read_chunk.c3
-rw-r--r--test/test_reader_writer.c3
14 files changed, 300 insertions, 50 deletions
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
@@ -1264,6 +1266,67 @@ 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 <assert.h>
@@ -27,19 +29,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)
+ 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 <d@drobilla.net>
+
+ 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 <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+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 <d@drobilla.net>
+
+ 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 <d@drobilla.net>
+
+ 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 <assert.h>
+#include <stddef.h>
+
+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,
"<http://example.org/s> <http://example.org/p> "
- "<http://example.org/o> ."));
+ "<http://example.org/o> .",
+ NULL));
assert(!serd_reader_read_document(reader));
assert(n_statements == 1);